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
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!