In a recent Rails application I am working on I stumbled upon the problem of having to dynamically instantiate a particular processing class based on the type of object being passed into a particular method.
My first attempt involved getting the instance’s class name using the class’s name:
1
2
3
post = Post.find(1)
post.class.name # => returns "Posts"
However this is redundant since ActiveRecord::Base by default extends ActiveModel::Naming:
1
2
3
4
5
6
7
8
9
# activerecord/lib/activerecord/base.rb
module ActiveRecord
class Base
extend ActiveModel::Naming
...
end
end
Rather than call class.name, if you call model_name on an ActiveRecord instance or its class, you get an ActiveModel::Naming object back which contains useful naming metadata:
1
2
3
4
5
post = Post.find(1)
post.model_name
#<ActiveModel::Name:0x007fe2ea756cf8 @name="Post", @klass=Post(id: integer, title: string, body: text, created_at: datetime, updated_at: datetime), @singular="post", @plural="posts", @element="post", @human="Post", @collection="posts", @param_key="post", @i18n_key=:post, @route_key="posts", @singular_route_key="post">
From the output above, I was able to just pass the instance’s model_name object into the method and since this object has a predictable api I was able to remove type checking completely:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
post = Post.find(1)
dynamic_processor(post)
....
def dynamic_processor(obj, opts={})
# previous type checking code which includes calls like respond_to? etc
# this can now be replaced by a simple hash map of class names to classes
processors = {
"Post" => Post,
"Report" => Report
}
klass = processors[obj.model_name.name]
klass.new(opts)
end
After all this time, I am still amazed by Rails and the pleasant surprises it brings to the table.
You can check the following docs for more information on ActiveModel::Naming
Happy Hacking!