
Where the logic hides in rails apps - dogas
http://gammons.github.com/architecture/2012/12/22/where-the-logic-hides/
======
LaGrange
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.

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.

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.

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.

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.

(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).

~~~
dgregd
> give them electric shock

even better, force them to use COmmon Business Oriented Language

~~~
tenderlove
They should also use principles from the Object Management Group.

~~~
dgregd
and after they finish building the system with separated business logic,
suggest their business client to add a formal business requirement that all
data should be persistently stored. just in case.

------
moe
This is the sanest Rails design-advice that I've read in a long time.

It counters the misguided "implicit over explicit"-mantra that is both very
prevalent in the rails-community and also the source of most problems.

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"!

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.

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.

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.

This eases the migration to the rails-successor when it manifests, and helps
preserve the sanity of your older self and _his_ successor.

~~~
julian37
_steer clear from most of the entrenched practices and packages (e.g. devise
and related trainwrecks)_

I agree with your sentiment in general, but what do you suggest to use in
place of devise? Surely you're not suggesting to write all the code for
handling mail confirmation, password changes etc. yourself?

~~~
moe
In fact for serious apps that are expected to grow I do recommend to write the
auth yourself. It's not a lot of code, from the second time it's mostly
copy/paste, and most importantly you'll fully understand what your code does
and when, there will be no guesswork in one of the most important areas of
your app.

Furthermore you usually end up heavily customizing whatever auth-code you
start with anyway. The shrinkwrapped gems never cover even half of what an app
eventually needs. Thus another advantage is that you won't have to reverse
engineer devise or authlogic when you arrive at that point (and trust me, you
_really_ don't want to look at their code and the SQL they emit).

That all said, in a recent project I've found the 'sorcery'-gem to provide a
reasonable baseline for rolling your own. It's one of the first "second
generation" Rails-gems that refrain from most of the magic and instead provide
a set of primitives that you wire up yourself.

It still suffers from a bit of rails-smell (code-generators...), but overall
the level of abstraction looks about right.

~~~
nthj
So you're one of those guys. I inherited a Rails app that used roll-your-own
auth instead of Devise or similar — it added a significant amount of mental
overhead, plus a bunch of additional logic for Facebook login.

Task that with another product I just launched: over 100K users, on Devise, no
customization needed, and Login with Facebook took 1 additional library, 3
lines of code and 15 minutes.

I used to be the roll-your-own-everything guy, years ago. After maintaining
dozens and dozens of Rails apps, I'm not anymore. I'm much happier when logic
is pushed into libraries and I can ignore it until it matters.

> In fact for serious apps that are expected to grow I do recommend to write
> the auth yourself.

No, no, no, just no. If you don't grow, you just wasted time you should have
spent shipping real features. If you do grow, refactor in your own
authentication later if you have to.

> you'll fully understand what your code does and when

You will, for sure. When you leave for another project and I'm brought into
maintain $app, I won't, straight away.

> there will be no guesswork in one of the most important areas of your app.

There is no guesswork with 3rd-party libraries. `bundle open gem-name` is your
friend. If you read through the code, you'll understand what it's doing, just
like I'll have to read through your code if you roll-your-own library.

One of the major benefits to standardized libraries is that I only have to
read the library for Devise or AuthLogic once across a dozen apps. I have to
learn each customized solution 100% of the time.

> It still suffers from a bit of rails-smell (code-generators...)

I get the feeling that you work on really large apps with a lot of custom
logic, and end up wishing you had complete control over everything. In such
situations I'd probably end up agreeing with you 90% of the time if you
suggested throwing out a 3rd-party library and rolling your own.

I just strongly disagree that you should start out that way. Your app should
have a single selling point: "Schedule my tweets", "Remind me to pay bills",
"keep track of my tasks", or whatever. Not "Log in with our unique
authentication code."

No app ever became a million dollar company because a developer thought the
most important job was to roll their own authentication scheme before they
even scored a thousand users.

~~~
moe
I think our opinions are not as far apart as it seems.

You are of course right that rolling your own auth won't be the first priority
in your initial PoC. Neither is it needed for very simple apps or when you're
dead-certain that you won't need more flexibility than the common gems
provide.

However in my experience the latter almost never applies in a commercial app.
Suddenly you need OmniAuth in addition to devise, and some form of ACLs. Then
you grow an API that also needs some sort of auth-tokens. Then there's this
other site you want to interface with which needs yet another bridge. Then one
day you run that ad on TV and learn the hard way that those extra-lookups
devise makes on every request are not free after all...

So what I'm saying is that the design of (in particular) devise and authlogic
is just not a very good one to start from if you can already predict that
you'll need customizations (beyond templating) in the future.

A frankensteined devise can be a lot harder to understand than a
straightforward impl from scratch - but in the end it of course also boils
down to who wrote it and whether he wrote it for the first time.

------
5vforest
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.

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?

~~~
LaGrange
It's okay if that's the only case where the email should be sent, and other
means of user creations should never, ever do that. It might be an issue if,
for example, the email should be sent always, no matter how the user was
created.

Edit: basically, the question is "which process involves sending the email".
It might be "creating a user," or it might be "using the 'create user' form".
At some point you might need a more abstract solution, of course — see things
like Listeners, DHH's Concerns, AOP or whatever you like. But the thing is, if
you don't delay too long, you can retrofit your codebase with it, so don't
worry too much.

Unless you're coding up a nuclear plant or storm barrier or the moon-carving
laser. Please don't use webappy principles to code drivers for the moon-
carving laser.

~~~
lucaspiller
We had a case at work for not doing this. We had a section of our app where
users could set up their profile, for example /user/profile (scoped to them by
their session). Imagine it is more than just CRUD, and has a few bits of
complicated logic (in reality there are multiple controllers - it's a big
profile :D).

We then wanted to add the ability for admins to edit profiles, so we created
/admin/users/[id]/profile. If the logic wasn't in the model we would have to
repeat it in both controllers (the views can easily be reused by having the
form elements in partials).

~~~
LaGrange
Of course — complicated logic is a good hint that code maybe should be moved
somewhere else, be it the model or some manager class. Just like you moved (or
maybe had from the start) the form elements into partials. It's probably not
something to obsess about too early.

The thing that bugs me the most is how both the article and some responses
here go into heavy absolutes. Different domains have different needs, and what
is "readable" changes between them, not just by size, but by shape as well.
And yes, shape and size might change as the app lives (my current codebase
being a great example), but you can't really predict everything.

------
0x0
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.

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.

It's like someone read <http://en.wikipedia.org/wiki/COMEFROM> and took it
seriously.

~~~
nthj
Anybody who doesn't have something like mail_safe [1] installed is asking for
trouble, no matter where he stores his business logic.

[1] <https://github.com/myronmarston/mail_safe>

~~~
0x0
That's a nice solution to the "emails gone wild" problem, but - no offense -
it's a bit besides the point.

We could just as well be talking about S3 data, facebook posts, tweets,
anything with (side) effects beyond the active database record in question.

------
hayksaakian
The author seems to contradict himself.

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.

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.

The whole point of rails is convention above all.

------
typicalrunt
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).

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.

    
    
        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

~~~
programminggeek
In the Obvious architecture, it would be something more like..

    
    
        class CreateContact
          def initialize contact_jack, email_jack
            @contact_jack = contact_jack
            @email_jack = email_jack
          end
        
          def do input
            # validate input
            
            contact = Contact.new
            contact.populate input
    
            contact_jack.save contact.to_hash
    
            email = ContactEmail.new
            email.populate contact.to_hash
    
            email_jack.send email.to_hash
    
            contact.to_hash
          end
        end
    

With that structure you can call CreateContact with:

    
    
        action = CreateContact.new
        result = action.do ContactJack.new, EmailJack.new
    

In that structure you can totally test the action and logic without hitting
the db or the mail system at all. Your ContactJack and EmailJack can be easily
swapped out for various pluggable data stores. Fileystem, MySQL, Mongo,
Postgres, Cassandra, could be swapped out for the ContactJack. EmailJack could
send through standard mail servers, SendGrid, Amazon AWS, Mandrill, etc.

Obvious is on github, you can read more at <http://obvious.retromocha.com>

------
daniel-levin
I like the idea of one-class-one-responsibility but have a number of
questions:

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?

"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:

2) My expectations have been subverted. My understanding of Rails conventions
is that business logic _does_ live inside the model which _will_ 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 __third __class in non-obvious location to
understand the updated example(app/use_cases)?

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?

4) Doesn't creating this new class exacerbate the problem of code/logic being
'scattered all over the place'?

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.

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 _actually_ does obfuscation
itself?

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.

~~~
programminggeek
Hey, I'm not the OP, but I have some thoughts based on my experience creating
the Obvious Architecture.

1) Hiding persistance from the programmer by tying it to your models is a bad
idea. It's where a lot of problems start. First, how do you test without
hitting the database? How long do your tests take? Minutes? Hours? Or do you
skip them?

I don't think your models should do persistance at all. They should model your
data for you. Do validation against your business rules. That sort of thing.

Would you tie your models to the filesystem? If not, then why would you tie
them to the database?

Pulling your persistance mechanism out of your models is the first step to
writing fast, maintainable, highly testable code, even if you are still using
rails.

2) Rails conventions want you to tie your DB to your objects, which is a bad
idea. Rails does a lot of things well, like it gives you a nice structure for
controllers, views, routing, asset management, so use it for those things.
Rails is a delivery mechanism. Use it as one and leave data modeling and
persistance to something else.

3) I think the name issue could be solved by treating it as "action" object.
Think of actions as more like actors. They do things. They integrate
models/entities and persistance mechanisms along with minor amounts of logic
that don't fit inside of entities.

As an action object, you could all it CreateContact and it would be called
create_contact.rb in the app/actions folder. Then, when you look in the
app/actions folder, the file name communicates what it does - it creates a
contact.

4) Actually, it helps a lot with testing. Creating a new class makes
dependency injection super easy, making testing super easy and fast. Also, it
can help with clarity and glance factor. Imagine you have an app/actions
folder with files like create_contact.rb, remove_contact.rb, send_email.rb,
get_contact.rb, compose_email.rb and so on. You look at that folder and you
can see that it is probably an contact management and email app. Maybe an
contact or newsletter management app?

That kind of glance factor you can't get with a bunch of higher level
controller or service containers. It also avoids the problem of "what
controller/module/service does this method belong to?"

5) I actually agree that the code that the OP did was not the best in that the
persistence and email sending mechanisms aren't passed in to the action, so
that you don't know what it is going to try and save and where. If the code
explicitly passed in the persistence and email mechanisms it would be more
apparent that an email is going to be sent as part of the CreateContact
action. That being said, I would probably make those two separate actions for
clarity's sake.

I've written some apps using the Obvious Architecture and it really does help
solve a lot of the problems that default Rails MVC creates.

------
programminggeek
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.

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.

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.

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.

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 <http://obvious.retromocha.com/>

When I have better documentation and examples available, I plan on posting to
HN, but all questions are welcome.

------
obilgic
Rails's conventions are great, and that is what makes it rails. Move on...

------
ollysb
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).

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.

------
jtms
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.

------
bbwharris
Learn your tools. New developers will need to do the same. There's zero
substitute for experience. Rails solves the most common problems easily.

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.

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.

------
stevewilhelm
We have been moving the complex logic out of the models into libraries. But we
are not happy with this approach.

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.

Would love to hear about how other wiser people have deal with these
situations.

~~~
inancgumus
I think what you are in need is nothing to do with model separation. But, you
need to be careful about which code belongs to your business logic. For the
views you mentioned, I don't know your code but I think you may use something
interim, like a presenter code which takes data from the models and helps
views to show it.

~~~
0x0
> something interim, like a presenter code which takes data from the models
> and helps views to show it

..something like a "controller"? :P

------
rurounijones
Anyone intersted in this style of programming needs to read "Clean Ruby" by
Jim Gay. (Currently being written with beta access)

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.)

~~~
dogas
I just tore through that book just a few days ago! Definitely a lot of eye-
opening things happening in relation to rails and architecture.

I think a lot of people/projects/companies have been burned attempting to
upgrade rails, or have a slow test suite, or having a terrible time trying to
figure out where to fit core application logic into the rails stack.

And then you have an epiphany and say, wait, rails should not define my app.
You say, I should be able to upgrade to rails 4 on day 1. Rails does not
define my application, my application defines my application! So if you do
just a wee bit of partitioning, it goes a very long way. (much to the chagrin
of DHH, who for some reason evangelizes having your core logic in ActiveRecord
models, to a fault)

------
andybak
Disappointed not to see some comments from Django/Python folks here.

Or node, mvc.net, Cake, CodeIgnitor etc etc.

Is every language/framework community doomed to repeat these debates in
isolation ad infinitum?

~~~
bradwestness
I commented in here a couple times and I do ASP.NET MVC development in C#. It
just seemed like the concepts are so similar that whether you write it in C#
or Ruby doesn't particularly matter.

------
graywh
Avdi Grimm has a "book" on separating business logic from Rails -
<http://objectsonrails.com/>

~~~
inancgumus
Yes, I bought the book earlier, although it contains some valuable things but
it needs an editorial review. It shows kind of things wich not simple, just
complex (I said simple, not easy).

------
shawndumas
from Spaghetti code to Ravioli code

