Models can quickly become a grab-bag of unrelated methods if behavior is constantly being pushed from the controller. Here I show how to refactor this using Concerns and Service Objects.
This episode is great. There is something weird however : the class authentication is calling authenticate on the user class. I think password authentication should be handle by the authentication class
This is due to the has_secure_password behavior. We could extract this into the Authentication class however I don't know if that tradeoff is worth it.
I really like this approach. I hate when the models get to fat ("ball of methods"). Many times they have to deal with tings that should not concern them. I use the lib folder and add workers for the complex controllers. One advantage is that you can run tests isolated form rails and tests get really fast.
If you like to see more screencasts on refactoring, go to destroyallsoftware
We call it rails porn (Yes, it's really that good!)
Sorry, Ryan for promoting another paid rails screencast series here, but I think that it's more complementing than competing with you.
I agree. But for me railscasts is more a "Hi, check this new gem and learn how to use it in 10 minutes" series. While Gary Bernhardt's screencasts is more like "So, you think you know how to do standard rails stuff, well let me blow your mind" Alert => mindblowing: burn your controllers. They both are great but for different reasons.
I agree! Gary's Destroy All Software is awesome and a good compliment to RailsCasts. :)
I usually do not cover theory but try to focus more on practical solutions to common problems. This episode is more of the former - I may do more of these occasionally.
I love both approaches. I relate well to Gary's DAS site because I've used Unix, Vim, and other scripting languages for a really long time, and am especially interested in how to improve my workflow using those tools. However, your approach is usually just what I need to get started using a new gem or technique for my Rails projects.
I greatly appreciate you spending more time on Refactoring, though, especially with a more Ruby or OO-centric approach. It has become the central theme at my work, where we've had enormous technical debt accrue over a 2 year period. IMHO, the traditional approach that many developers have learned initially for Rails is the root cause of why many codebases have grown to a barely maintainable level. Anything you can do to help developers learn better habits and produce more readable, elegant and stable code will certainly pay huge dividends when others inherit projects.
Please continue this work, if not for the free RailsCasts side, at least for the Pro subscribers.
Thank you for highlighting the use of Service Objects, it's a technique that I have adopted and felt has improved the codebase for the projects I have worked on. There's probably one piece of code I would push back down into the model:
This query method exposes the ActiveRecord Query API and I would rather have that encapsulated in the ActiveRecord model. find_by_username is debatable, but I typically let those slip.
Can you explain why find_by_user name is different than than the User.where example? I guess I don't understand why one should belong there and the other one is debatable...
When unit testing the Service Object I can easily stub the method find_by_username. However the where would required an ugly stub chain. If the API changes the where clause is more costly than the find_by_username.
Having said that, there maybe a day when find_by_username is deprecated like it's cousin, find_all_by_ in Rails 4 and I would regret that choice :-)
If working on a team and they wanted to move the method find_by_username to the User I would be be totally cool with that, perhaps using the ActiveRelation syntax to build the query.
I know some folks advocate even hide the all method within their model, for example:
ruby
classPost
alias_method :all_posts, :allend
Anyway, I hope that gives you some idea into my thought process behind that :-)
ActiveSupport Concern is nice way to extend some class with module mixins, but to keep my controllers clean I rather use kindergarten. It is more convenient way to deal with controllers cleanup. Kindergarten additionally use CanCan for easier authorization (no more Ability.rb) and some more useful features.
Thanks for giving some samples to the recent Tweet-Stream about this topic.
Concerns let you group functionality - nice. But in the end it all ends up mixed into the consumers class (User). I know it's nice Ruby magic - and there might be reasons or use cases to use Concerns. But somehow I like the service approach more. It's more explicit, better testable and more OO style - less 'Ruby meta style'.
Grails which is inspired by Rails and written in Groovy, explicitly accounts for service objects just as Ryan explains here. I think for a few methods here and there, the "concern" or module mix-in approach is fine. But for something more substantial, a service object is better. It is a judgment call really.
Yes, I also came to Rails from Grails. Grails has a strong service layer concept, and encourages to push a lot of business logic into stateless (and transactional) service layer. Something I've missed in Rails, so I started doing something like this implicitly by creating non-db backed model classes.
Also, I would suggest if you are going to use service objects, to use similar convention as with controllers, i.e. call an object authentication_service vs service. That will help to know where the original definition lives - i.e. is it a model or a service.
I recently looked into Grails and liked the service object approach. May I ask - which framework do you prefer at this point? (Apologies for this being off topic)
Great episode Ryan!
Could you tell us your thoughts about what to do when an app has too many models?
Do you think namespacing or subfolders are good ideas?
Thanks!
Id be very interested in an answer to this. We run a large data warehouse and the amount of models talking to it is becoming to much. I have name spaced it under a Dw class which handles the connection to the DB and any other shared logic. But I wonder if there is a better way to handle such a large quantity of models without cluttering the app models..?
I love the service object approach. Your episode on Presenters from scratch really expanded my mind on how to simplify view code using models. I've come to really hate helper modules because they are so hard to test and easy to make a mess with.
For controllers I've been using a set of classes I call ControllerAssistants. I set them up through a before filter and give them info like the params, etc. They are really easy to test and debug.
My models are huge and could really use some help.
Views need Presenters
Controllers need Assistants
Models need Services
Using service objects definitely seems cleaner than concerns. As it is, if you have many concerns your model will become fat with include statements :).
Need help understanding Ryan's code for the services implementation!
I am reading Ryans code for Service Objects, specifically the PasswordReset class code. It has both the class methods and instance methods plus one instance variable @user which can be accessed via attr_reader class method, so far so good.
Consider the following class method in this service:
password_reset.rb
defself.from_email(email)
new User.find_by_email(email)
end
It shows that the class method is returning a new instance of the "User" class, correct? See how he calls and uses it further in the PasswordResetsController class as shown below:
password_resets_controller.rb
defcreate
password_reset = PasswordReset.from_email(params[:email])
if password_reset.user
password_reset.send_email
redirect_to root_url, notice:"Email sent with password reset instructions."else
redirect_to new_password_reset_url, alert:"Email address does not match a user account."endend
it seems like his call to PassworReset's class method from_email is returning a new instance of the PasswordReset class itself. The method implementation shows that it is returning a new instance of the "User" class however. He then accesses the associated instance variable user via the newly created instance as shown in "if password_reset.user" line of the create method shown above. I am totally confused as to what is going on here. Is this an error? or some "quack quack" (duck typing mumbo jumbo) stuff going on here?
I have never seen it done this way before so threw me as well. But one thing still gets me why in the other class methods you are not using the attribute @user ie,
Coming from .NET/Java this was the first concern I had with Rails. Where does business logic go? I was baffled to find out there's no standard way of doing that. Sorry, but putting it into model code always seemed like a hack to me.
Service layer has been the industry standard of coding complex business logic for quite a while now. This vid is great, but it just scratched the surface. Imagine dealing with trees of business logic or testing parts of it in complete isolation by stubbing out the context. Exceptions, transactions, logging all managed automagically using AOP. Sigh... those were the days :)
Next up - moving the instantiation and lifetime management of these service objects to config or DSL using some dependency injection framework.
If you execute your test library with the rake command the tests in the service directory are automatically included if you are using RSpec, but not if you are using Test::Unit. Is this right?
If yes, what is the best way to auto include the tests in the service directory if you are using Test::Unit?
I'm not sure if this is the best practice. But I moved the services tests from 'test/services' to 'test/unit/services' to make them part of 'rake test:units'. This works good enough for me.
Really nice article Ryan. I remember I in the asp.net world the concept of services is really strong. Beyond nicer readability Services are considered a fundamental component of an application for handling business logic.
In Ruby achieving great code readability is often harder due to developers having multiple possible ways of separating responsibility based on taste. But separating business logic isn't exactly emphasised as well as it could be which leads often hard to read code. Although mostly a nice feature I sometimes see the flexibility offered by Ruby language as its curse too.
The article Ryan linked to might shed some light. It gives an overview of all the different strategies for dealing with fat models (including Service Objects and Decorators).
This episode is great. There is something weird however : the class authentication is calling authenticate on the user class. I think password authentication should be handle by the authentication class
This is due to the
has_secure_passwordbehavior. We could extract this into the Authentication class however I don't know if that tradeoff is worth it.I really like this approach. I hate when the models get to fat ("ball of methods"). Many times they have to deal with tings that should not concern them. I use the lib folder and add workers for the complex controllers. One advantage is that you can run tests isolated form rails and tests get really fast.
If you like to see more screencasts on refactoring, go to destroyallsoftware
We call it rails porn (Yes, it's really that good!)
Sorry, Ryan for promoting another paid rails screencast series here, but I think that it's more complementing than competing with you.
+1 for destroyallsoftware
agree that is more complementing, though i would love to have more "true pro" episodes like this on railscasts!
I agree. But for me railscasts is more a "Hi, check this new gem and learn how to use it in 10 minutes" series. While Gary Bernhardt's screencasts is more like "So, you think you know how to do standard rails stuff, well let me blow your mind" Alert => mindblowing: burn your controllers. They both are great but for different reasons.
Just for fun wat
I agree! Gary's Destroy All Software is awesome and a good compliment to RailsCasts. :)
I usually do not cover theory but try to focus more on practical solutions to common problems. This episode is more of the former - I may do more of these occasionally.
These are also good on the subject of refactoring
http://www.confreaks.com/videos/1115-gogaruco2012-go-ahead-make-a-mess
http://www.confreaks.com/videos/1233-aloharuby2012-refactoring-from-good-to-great
One of my favorites is Katrina Owen's "Therapeutic Refactoring": http://www.youtube.com/watch?v=J4dlF0kcThQ
I love both approaches. I relate well to Gary's DAS site because I've used Unix, Vim, and other scripting languages for a really long time, and am especially interested in how to improve my workflow using those tools. However, your approach is usually just what I need to get started using a new gem or technique for my Rails projects.
I greatly appreciate you spending more time on Refactoring, though, especially with a more Ruby or OO-centric approach. It has become the central theme at my work, where we've had enormous technical debt accrue over a 2 year period. IMHO, the traditional approach that many developers have learned initially for Rails is the root cause of why many codebases have grown to a barely maintainable level. Anything you can do to help developers learn better habits and produce more readable, elegant and stable code will certainly pay huge dividends when others inherit projects.
Please continue this work, if not for the free RailsCasts side, at least for the Pro subscribers.
Thank you for all of your hard work!
Thank you for highlighting the use of Service Objects, it's a technique that I have adopted and felt has improved the codebase for the projects I have worked on. There's probably one piece of code I would push back down into the model:
This query method exposes the ActiveRecord Query API and I would rather have that encapsulated in the ActiveRecord model.
find_by_usernameis debatable, but I typically let those slip.Thoughts?
Can you explain why find_by_user name is different than than the User.where example? I guess I don't understand why one should belong there and the other one is debatable...
When unit testing the Service Object I can easily stub the method
find_by_username. However thewherewould required an ugly stub chain. If the API changes thewhereclause is more costly than thefind_by_username.Having said that, there maybe a day when
find_by_usernameis deprecated like it's cousin,find_all_by_in Rails 4 and I would regret that choice :-)If working on a team and they wanted to move the method
find_by_usernameto the User I would be be totally cool with that, perhaps using the ActiveRelation syntax to build the query.I know some folks advocate even hide the
allmethod within their model, for example:Anyway, I hope that gives you some idea into my thought process behind that :-)
ActiveSupport Concern is nice way to extend some class with module mixins, but to keep my controllers clean I rather use kindergarten. It is more convenient way to deal with controllers cleanup. Kindergarten additionally use CanCan for easier authorization (no more
Ability.rb) and some more useful features.Thanks for giving some samples to the recent Tweet-Stream about this topic.
Concerns let you group functionality - nice. But in the end it all ends up mixed into the consumers class (User). I know it's nice Ruby magic - and there might be reasons or use cases to use Concerns. But somehow I like the service approach more. It's more explicit, better testable and more OO style - less 'Ruby meta style'.
Grails which is inspired by Rails and written in Groovy, explicitly accounts for service objects just as Ryan explains here. I think for a few methods here and there, the "concern" or module mix-in approach is fine. But for something more substantial, a service object is better. It is a judgment call really.
+1
Yes, I also came to Rails from Grails. Grails has a strong service layer concept, and encourages to push a lot of business logic into stateless (and transactional) service layer. Something I've missed in Rails, so I started doing something like this implicitly by creating non-db backed model classes.
http://grails.org/doc/latest/guide/services.html
Also, I would suggest if you are going to use service objects, to use similar convention as with controllers, i.e. call an object authentication_service vs service. That will help to know where the original definition lives - i.e. is it a model or a service.
I recently looked into Grails and liked the service object approach. May I ask - which framework do you prefer at this point? (Apologies for this being off topic)
Great episode Ryan!
Could you tell us your thoughts about what to do when an app has too many models?
Do you think namespacing or subfolders are good ideas?
Thanks!
Id be very interested in an answer to this. We run a large data warehouse and the amount of models talking to it is becoming to much. I have name spaced it under a Dw class which handles the connection to the DB and any other shared logic. But I wonder if there is a better way to handle such a large quantity of models without cluttering the app models..?
+1!
+1!
I love the service object approach. Your episode on Presenters from scratch really expanded my mind on how to simplify view code using models. I've come to really hate helper modules because they are so hard to test and easy to make a mess with.
For controllers I've been using a set of classes I call ControllerAssistants. I set them up through a before filter and give them info like the params, etc. They are really easy to test and debug.
My models are huge and could really use some help.
Views need Presenters
Controllers need Assistants
Models need Services
How great is that?
+1
Using service objects definitely seems cleaner than concerns. As it is, if you have many concerns your model will become fat with include statements :).
And what is wrong with that?
If the business logic is related to that model class and only that model class, then it should go there.
A fat model is not a bad model, so long as your model isn't fat because it's including functionality that belongs to another class.
Need help understanding Ryan's code for the services implementation!
I am reading Ryans code for Service Objects, specifically the PasswordReset class code. It has both the class methods and instance methods plus one instance variable @user which can be accessed via attr_reader class method, so far so good.
Consider the following class method in this service:
It shows that the class method is returning a new instance of the "User" class, correct? See how he calls and uses it further in the PasswordResetsController class as shown below:
it seems like his call to PassworReset's class method from_email is returning a new instance of the PasswordReset class itself. The method implementation shows that it is returning a new instance of the "User" class however. He then accesses the associated instance variable user via the newly created instance as shown in "if password_reset.user" line of the create method shown above. I am totally confused as to what is going on here. Is this an error? or some "quack quack" (duck typing mumbo jumbo) stuff going on here?
Kindly explain.
Thanks.
Bharat
The contents of the from_email method is short-hand for
Ryan uses implicit parentheses and the fact that you can call other class-level methods without a prefix in a class-level method.
Thanks. That makes sense.
I have never seen it done this way before so threw me as well. But one thing still gets me why in the other class methods you are not using the attribute @user ie,
Is it because of the attr_reader?
Great episode!
However, has anyone considered the use of "protocols" like Smalltalk does? Any thoughts on pros and cons about using it?
On point (+1) reminds me of my java days.. soon we could be singing value objects!!
My first thought also.
Coming from .NET/Java this was the first concern I had with Rails. Where does business logic go? I was baffled to find out there's no standard way of doing that. Sorry, but putting it into model code always seemed like a hack to me.
Service layer has been the industry standard of coding complex business logic for quite a while now. This vid is great, but it just scratched the surface. Imagine dealing with trees of business logic or testing parts of it in complete isolation by stubbing out the context. Exceptions, transactions, logging all managed automagically using AOP. Sigh... those were the days :)
Next up - moving the instantiation and lifetime management of these service objects to config or DSL using some dependency injection framework.
I see that Uncle Bob's talk about architecture in ruby apps is giving an impact on our community !
http://www.confreaks.com/videos/759-rubymidwest2011-keynote-architecture-the-lost-years
If you execute your test library with the rake command the tests in the service directory are automatically included if you are using RSpec, but not if you are using Test::Unit. Is this right?
If yes, what is the best way to auto include the tests in the service directory if you are using Test::Unit?
I'm not sure if this is the best practice. But I moved the services tests from 'test/services' to 'test/unit/services' to make them part of 'rake test:units'. This works good enough for me.
Really nice article Ryan. I remember I in the asp.net world the concept of services is really strong. Beyond nicer readability Services are considered a fundamental component of an application for handling business logic.
In Ruby achieving great code readability is often harder due to developers having multiple possible ways of separating responsibility based on taste. But separating business logic isn't exactly emphasised as well as it could be which leads often hard to read code. Although mostly a nice feature I sometimes see the flexibility offered by Ruby language as its curse too.
How does a Service Object differs from a Decorator ?
The article Ryan linked to might shed some light. It gives an overview of all the different strategies for dealing with fat models (including Service Objects and Decorators).
http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/
Got bug when using #120 Thinking Sphinx and models what include concerns like in #398-service-objects
card.rb
concerns/userable.rb
Shouldn't that be
include Concerns::Userableandmodule Concerns::Userable? Rails autoloading assumes directories correlate to namespaces.I am namespacing my concerns as demonstrated in the tutorial.
I have a class
Portraitand a concernPortrait::ToHashdefined asbut I get this error:
superclass mismatch for classThe error goes away if I do this:
I have 5 models and I do not need to inherit the class from
ActiveRecord::Basewhen defining the concern in any of the others. Just this one.Any clues what could be happening?
You're defining a class name Portrait as descending from ActiveRecord::Base.
Then you redefining it without ActiveRecord. You can't do that.
Suggestion: wrap your Portrait class (non ActiveRecord) in a module with the name of you app. If you app is called Store, so
Also, don't put a module inside the class like this. Module are not meant to be used as classes.
Thanks. The
moduleinsideclasswas suggested by @rbates himself.Is it true most of time service objects has only one public method for the action and class name could be verb? Any resources to learn more about it?
First sign in through GitHub to post a comment.