Getting names of ActiveRecord classes

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 post = Post.find(1)
2 
3 post.class.name # => returns "Posts"

However this is redundant since ActiveRecord::Base by default extends ActiveModel::Naming:

1 # activerecord/lib/activerecord/base.rb
2 
3 module ActiveRecord
4   class Base
5     extend ActiveModel::Naming
6 
7     ...
8   end
9 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 post = Post.find(1)
2 
3 post.model_name
4 
5 #<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 post = Post.find(1)
 2 
 3 
 4 dynamic_processor(post)
 5 
 6 ....
 7 
 8 def dynamic_processor(obj, opts={})
 9   # previous type checking code which includes calls like respond_to? etc
10   # this can now be replaced by a simple hash map of class names to classes
11   processors = {
12     "Post" => Post,
13     "Report" => Report
14   }
15 
16   klass = processors[obj.model_name.name]
17   klass.new(opts)
18 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!