Code Concerns in Rails 4 Models
Warm up Different models in your Rails application will often share a set of cross-cutting concerns and you may have not noticed that there's a folder called 'concerns' was added from Rails 4. In fact, this feature has been here for a long time and it is pretty simple but powerful concept. ...
Warm up
Different models in your Rails application will often share a set of cross-cutting concerns and you may have not noticed that there's a folder called 'concerns' was added from Rails 4. In fact, this feature has been here for a long time and it is pretty simple but powerful concept. Basically, every chunks of code which are common and show up in multiple models should be extracted and reused as much as possible, it helps to prevent models from getting fat and messy.
Here is a simple example. Let say we have a model called User and typically, it would have some methods like below:
class User < ActiveRecord::Base has_secure_password def self.authenticate(email, password) user = find_by_email(email) user if !user.nil? && user.authenticate(password) end def authenticate(password) self.password == password end end
Then we can create new file like app/models/concerns/authenticate.rb and extract those methods of User model inside:
module Authentication extend ActiveSupport::Concern included do has_secure_password end module ClassMethods def authenticate(email, password) user = find_by_email(email) user if user && user.authenticate(password) end end def authenticate(password) self.password == password end end-
Now, let's see how far the model can be refactored with new code:
class User < ActiveRecord::Base include Authentication end
So the functionality hasn't changed while the code was in other fashion and you may noticed that app/models/concerns is automatically part of load path, including its children can't be easier. This is just one of many benefits concerns bring for us, as we can see, it is DRYing up model codes and of course, it is useful for one want to have skinny models like Kate Moss.
How It Works
Let's walk through what happens when you use Concern, starting with extend Concern:
module Concern def self.extended(base) #:nodoc: base.instance_variable_set("@_dependencies", []) end end
Just like included, modules have an extended callback that is called whenever the module extends something. Concern uses this to stash an instance variable on the base class being extended. The instance_variable_set and instance_variable_get methods allow you to access an object's internal instance variables:
rank = Ranking.new rank.instance_variables_ #=> [:@ranking_type, :@period, :@category_id, :@num_found, :@products, ...] rank.instance_variable_get(:@ranking_type) #=> nil # Update the internal state: rank.instance_variable_set(:@rank_type, "Diamond") rank.rank_type #=> "Diamond"
We'll see that @_dependencies is used to identify modules using Concern, and to hold information needed later.
Another Point Of View
Clothes don't make a man! Let's take a deep breath and isn't ActiveSupport::Concern just an alternate syntax for include/extend? ?
- Using concerns, it means seperating your concerns, definitely, codebase will be actually harder to navigate with old those concerns around.
- Your classes are no less dry. If you stuff 50 public methods in various modules and include them, your class still has 50 public methods, it's just that you hide that code smell, sort of put your garbage in the drawers.
- Honestly, are the concerns some kinds of service, module or whatever layer in fancy describe. Does it help to improve performance of Rails app or just code/visualization performance ?
I don't use them. I find that they add complexity to my system without providing too much value. Adding dependencies and hiding complexity is a choice that you should make according to your desires and experience. Just ask yourself those above questions, try to find the answers and watch my next series about Rails's facts. Hopefully I can find something interesting to represent to you.