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.

IntroductionπŸ”—

Weeds

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.

The Set-UpπŸ”—

The 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

Nothing exceptional.

A Few Weeks Later...πŸ”—

The 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

ReferencesπŸ”—

Some references if you want go beyond.

ConclusionπŸ”—

Rail track

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.

Enhanced by Zemanta


If you have any comment, question, or feedback, please share them with me.


Atom feed icon Subscribe to the blog!