I'll give a proposal now: every chair of every developer shall from now on give them electric shock whenever they use the term "business logic". Ouch.<p>Seriously, "CreatesContact" is not really a class. It's a procedure, function with side effects, whatever you call it. Just with a class wrapper that boosts the developer ego almost as much as an AbstractFactoryManagerFactory.<p>No, modules are actually great. If you have billions of tiny modules all over the place, you probably (note the "probably") optimized way too early by splitting things out into them without a clear reason.<p>And guess what, if a new developer comes into a project and investigate stuff, they will see the model class start with all sorts of metadata, including callbacks, including those fancy ones called "before_save". If they can't guess what "before_save" means, you might want to either pay for an English class, or switch the framework to something that speaks in German.<p>And as for using rails as a web delivery mechanism, can you please, please, please investigate things like Sinatra and plain old rack, because those are delivery mechanisms. Rails is a framework, and it's entire point is being opinionated. If you disagree with the opinions, you probably should investigate other frameworks, or just use the libraries that you like (including those that are parts of Rails). Or maybe whack Rails into whatever shape you would like you to be in.<p>(Also, monolithic applications happen to the best of us, but the problems in them actually often come from the fact that they're monolithic. Stop looking for other developers to blame).
This is the sanest Rails design-advice that I've read in a long time.<p>It counters the misguided "implicit over explicit"-mantra that is both very prevalent in the rails-community and also the source of most problems.<p>I.e. when you watch any random RailsCast it is usually filled to the rim with obscure incantations and "look how we need only one LoC to perform $excessive_magic"!<p>The end-result are those deeply entangled "piles of rails" that we've all seen and suffered from. Hopelessly overloaded models and a dense mesh of hidden interdependencies that nobody grasps anymore because many of them are not even explicitly declared.<p>Personally I've largely given up hope on rails and am waiting for the successor. The rails-team just seems too fixated on digging their rabbit hole ever deeper, rather than re-visiting design mistakes that were made early on.<p>However, if you are stuck with a Rails-app for the time being (and who isn't..) I'd definitely recommend to follow a pattern like the one outlined in this post. The best way to use Rails nowadays is to steer clear from most of the entrenched practices and packages (e.g. devise and related trainwrecks) and to use it like a library rather than a framework - as much as that is possible.<p>This eases the migration to the rails-successor when it manifests, and helps preserve the sanity of your older self and <i>his</i> successor.
Maybe this is naive, but what's wrong with just putting `UserMailer.welcome_email(user).deliver` in the controller, right after the user is created? In my mind, delegating an email to be sent should be a job for the controller.<p>IIRC, the Rails ideology says that "Models should not know about any part of the application except for their own datastore." Even though the :after_create hook is the "Rails way", doesn't this violate their own practices?
These kinds of callbacks, which remind me about the "aspect oriented programming" that was hyped for a short while a few years ago, look super dangerous.<p>I could easily imagine someone unaware of this hook running a test to create a bunch of user entries, sending emails all over the place without even realizing.<p>It's like someone read <a href="http://en.wikipedia.org/wiki/COMEFROM" rel="nofollow">http://en.wikipedia.org/wiki/COMEFROM</a> and took it seriously.
The author seems to contradict himself.<p>First he claims that sending an email after user creation is conventionally done in the model. Then in his hypothetical scenario a new dev comes in and looks at the controller to discern that same logic.<p>He completely misses the point, if its conventional to put the welcome email logic in the model then we can expect the new dev to look for the logic in the model.<p>The whole point of rails is convention above all.
While I don't like the name of the service object "CreatesContact", I disagree with the statement that the sending of an email should reside within the create_contact! method. The author's intent is to shield any new developers from methods that say one thing and do another, yet he adds email delivery into the create_contact! method without informing the developer what may happen (unless some magic flag is set somewhere).<p>A simpler, more contrived example, would be to create a new function create_and_send_email! and place the creation logic and email sending logic in there. And now, when the developer uses the service object, she has the ability to choose between just creating the contact, or creating it and sending an email.<p><pre><code> class CreatesContact
def initialize(contact)
@contact = contact
end
def create_contact!
@contact.save
end
def create_contact_and_send_email!
create_contact!
deliver_contact_created_email
end
end</code></pre>
I like the idea of one-class-one-responsibility but have a number of questions:<p>1) Isn't the whole idea of ActiveRecord that persistence is hidden from the model programmer? Surely the model doesn't actually have 'triple duty' because it doesn't contain any persistence code?<p>"Imagine you are a brand new developer on the team that supports this app. You see the @contact.save call but now, the fact that it performs business logic is even harder to see, since the logic is placed in another module, somewhere in some other directory." - I will imagine I am a new developer on the project:<p>2) My expectations have been subverted. My understanding of Rails conventions is that business logic <i>does</i> live inside the model which <i>will</i> be inside app/models. Therefore, the first question I'd ask myself would be: "What business logic happens when this an instance of this model is created?" Surely Rails supports this paradigm by virtue of the existence of after_create? Wouldn't I have to visit a <i></i>third<i></i> class in non-obvious location to understand the updated example(app/use_cases)?<p>3) Doesn't CreatesContact now have two responsibilities as well? Namely Creation and validation? And the name doesn't communicate that it performs validation as well, does it?<p>4) Doesn't creating this new class exacerbate the problem of code/logic being 'scattered all over the place'?<p>5.1) Doesn't the issue of: "[In the controller], To an outside observer, it is not entirely clear that an email will be sent." remain just as problematic as the model containing the code for sending the email? Because you'd have to mentally context switch (the single biggest obstacle to understanding code IMHO) from the controller to the CreatesContact class to see what is happening. when CreatesContact.new(@contact).create_contact! is called.<p>5.2) Continuing the thread of the last question, CreatesContact.new(@contact).create_contact! certainly doesn't say to the programmer: "I send an email if a certain flag was checked". Isn't this unintentionally self-defeating? The aim is a reduction of 'obfuscation', but isn't hiding what a 'single-responsibility' class <i>actually</i> does obfuscation itself?<p>I don't mean to be dismissive, but, broadly, I disagree with the way you have tried to compartmentalise this application. The purpose of my questions is to help me understand your opinion.
Ok, I've been working on an architecture pattern that is very similar to this idea and it's basically inspired by Uncle Bob's Clean Architecture and Screaming Architecture posts. I call it the Obvious Architecture.<p>What the OP writes about is a good first step and frankly, is very close to right, but why stop there? Why not ditch ActiveRecord models all together and totally decouple persistance from the "app"? Honestly, the coupling of your models and logic to a database is really where most of this trouble starts.<p>In the Obvious architecture the app is separate from the delivery mechanism (rails/sinatra/whatever) and also from the external persistance mechanisms like databases, queues, caching, and so on. You can mix and match both delivery mechanism and persistance methods. So, you could write a CLI app against the filesystem and then make a rails web app with a mysql persistance mechanism without changing your "app" at all.<p>The whole thing is built from day one to be TDD and in a recent app I wrote, it has some 78 rspec tests that run in 0.03 seconds.<p>I'm in the process of documenting and open sourcing the whole thing. There is a project generator gem available now called 'obvious' and you can read more at <a href="http://obvious.retromocha.com/" rel="nofollow">http://obvious.retromocha.com/</a><p>When I have better documentation and examples available, I plan on posting to HN, but all questions are welcome.
If you have a use case that doesn't fit into your basic CRUD why not just create a new REST resource for it? For instance in my current app we have an InstallsController. This creates a new installation of the plugin, updates the user's account and send out an email. I've always steered clear of model callbacks as they don't play well with FactoryGirl/Machinist etc. As such the controller's create action describes everything that happens when our plugin is installed(it's still only 4 lines long).<p>This strategy of using new REST resources combined with modules for models where appropriate seems a very comfortable way to structure a large application. It fits into the rails conventions well and doesn't require new developers to do anything they're not familiar with. The other nice advantage is that your routes configuration gives a great overview of everything that can happen in your app.
No, really, just use modules - they are testable and reusable and any truly professional Rails dev is going to have zero problems understanding and working with them. This smells of someone very new to Ruby/Rails.
Learn your tools. New developers will need to do the same. There's zero substitute for experience. Rails solves the most common problems easily.<p>It's extremely common to do things upon saving. Once you need to do more than one thing upon saving you then move to a more "event" based approach.<p>Rails was mystifying when I first picked it up, and parts of it are mystifying today. But something as trivial as the callback chain will be second nature after some time and several apps later.
We have been moving the complex logic out of the models into libraries. But we are not happy with this approach.<p>We also find it difficult to cleanly develop views where data is needed from several different models. The whole Rails REST and MVC model gets in the way.<p>Would love to hear about how other wiser people have deal with these situations.
Anyone intersted in this style of programming needs to read "Clean Ruby" by Jim Gay. (Currently being written with beta access)<p>To be honest; it looks like this post might have been inspired by that book (or the concepts behind it) but I think the book does a better job explaining it (Obviously, a book vs a single blog post.)
Disappointed not to see some comments from Django/Python folks here.<p>Or node, mvc.net, Cake, CodeIgnitor etc etc.<p>Is every language/framework community doomed to repeat these debates in isolation ad infinitum?