Spring cleanup for object creation in Rails
This is an archive of blog post I wrote during my third venture (PullReview).
TL;DR A complex object creation can clutter a controller. It's better to move it into a dedicated method of the corresponding model.
Cut your Weeds asap.
As software grows, what was once good solution can grow to become a small nuisance - like inoffensive weeds at the side of your driveway. Leaving those weeds unchecked, they become a festering plague. Time to act.
Today, I'll cover one of those cases in Rails: the creation of a new resource.
My goal into this post is to underline the responsibility of the controller and to not clutter it with creation concern. I will offer a first simple solution that allows to fix it.
Blog application as the one illustrating the Rails guide. Nothing is complex in the beginning, and the Post controller looks like a scaffold.
class PostController < ApplicationController #... def create @post = Post.new(params[:post]) if @post.save redirect_to @post else render action: 'new' end end #... end
A Few Weeks Later...🔗
Blog application has grown: to each post, some statistics are attached such as the number of words, and the used language is automatically detected.
class PostController < ApplicationController #... def create @post = Post.new(params[:post]) @post.number_of_links = count_links(@post.body) @post.number_of_chars = count_chars(@post.body) @post.number_of_words = count_words(@post.body) @post.language = detect_language(@post.body) if @post.save redirect_to @post else render action: 'new' end end #... private def count_links(text) # … end def count_chars(text) # … end def count_words(text) # … end def detect_language(text) # … end #... end
This code wasn't produced in one change. It's the result of several changes - made at different moments. It looked like the natural place to add everything concerning the creation of a new post. One more line didn't seem to be a problem. Now there are 4 - without counting all the lines for the private methods - 4 more reasons to change the code when creating a new post.
The responsibility of your controller is to make sense of the request and to produce the appropriate output (as well expressed in the Rails Guides) - not to take care of the dirty details of a post creation.
The Cut - The Cleanup🔗
A better place to put those details is the Post model. You could, for instance, use one of the Rails callbacks called when creating an object. Even if it is a totally valid option, I don't use it - as it depends on the Rails framework. I prefer to have a model that could live without it - it helps to have a better OO design and faster tests (Avdi Grimm's book Objects on Rails gives a great overview of that approach).
The idea is to put all the creation details into a same class method that will return an instance ready to be saved:
class Post < ActiveRecord::Base #... def self.build(params) post = Post.new(params) post.number_of_links = count_links(post.body) post.number_of_chars = count_chars(post.body) post.number_of_words = count_words(post.body) post.language = detect_language(post.body) post end #... private def self.count_links(text) # … end def self.count_chars(text) # … end def self.count_words(text) # … end def self.detect_language(text) # … end end
The Post controller is much simpler now:
class PostController < ApplicationController #... def create @post = Post.build(params[:post]) if @post.save redirect_to @post else render action: 'new' end end #... end
Now, everything about the creation of a Post instance is in one dedicated class method of the model. After all, it's totally the responsibility of the model. This is commonly called a factory method. There is a very common and known method that already does that: the constructor, i.e. the method initialize. Outside of Rails, it's better to put it in the constructor ( except for some specific reasons, e.g. giving it a specific name). But this is Rails - and overriding the constructor will get rid of the ActiveRecords magics (yet it's still possible by explicitly reproducing the magics but don't do this at home).
This is the first step to the Factory Method Pattern . It’s goel is is to isolate complex process of creation from the object itself - especially when it goes beyond its responsibility or could result into different types. As this is not (yet) our case, I won't go develop further on this.
Having said that, we can finish our cleanup by refactoring our private methods. As we are now into the Post model, we can change them into object method.
class Post < ActiveRecord::Base #... def self.build(params) post = Post.new(params) post.calculate_the_text_stats post.detect_language post end #... private def calculate_the_text_stats # counting chars, words, and links # setting the corresponding attributes end def detect_language # detecting the language and setting the eponym attribute end end
Some references if you want go beyond.
- Class Static Instance Initializers, i.e. factory methods, in Ruby (SO)
- Constructor vs Factory Methods (SO)
- Factory Method Pattern (wikipedia)
- Factory Method Pattern (c2 wiki)
- How to initialize an ActiveRecord with values in Rais (SO)
- Creational Design Patterns
- Design Patterns in Ruby (first article)
Don't let your Rails overwhelmed by weeds.
When creation becomes complex, it clutters the controller with details that are not its responsibility. It's necessary to move it elsewhere. We've elaborated on one possible solution: dedicating a class method of the model to that complex creation. That method is commonly called a factory method (but it's not a complete implementation of the Factory Method Pattern).
Don't be overwhelmed by the weeds on your coding driveway. Ask a reviewer to have a look at your code, or use a tool such as RailsBestPractices to automatically detect it.
If you have any comment, question, or feedback, please share them with me.
Subscribe to the blog!