Overriding model name in Rails

Rails automatically generates url paths for redirect_to, form_for methods based on the provided object (via ActionDispatch::Routing::UrlFor#url_for). Sometimes there are situations when you don’t want that - i.e. object is namespaced or extended.

Let’s look at simple Identity::Session object:

module Identity
  class Session
    ...
  end
end

Passing Identity::Session instance to form_for helper will call session_identities_path to get a path for action attribute.

This can be changed by overriding Identity::Session.model_name method with a little help from ActiveModel::Name.

ActiveModel::Name can automatically detect namespace and name based on the provided klass for us:

naming = ActiveModel::Name.new(Identity::Session)
naming.name
#> "Identity::Session"

naming.route_key
#> "session_identities"

It also accepts klass, namespace and name as separate arguments to override its default behavior:

naming = ActiveModel::Name.new(Identity::Session, nil, "Session")
naming.name
#> "Session"

naming.route_key
#> "sessions"

Now we need to override Identity::Session.model_name method:

module Identity
  class Session
    def self.model_name
      ActiveModel::Name.new(self, nil, "Session")
    end
  end
end

After that Identity::Session instance can be safely passed to form_for or redirect_to methods.

Correct behavior can be verified with ActionDispatch::Routing::UrlFor#url_for method:

include ActionDispatch::Routing::UrlFor
include Rails.application.routes.url_helpers
Rails.application.routes.default_url_options = { host: 'localhost:3000' }
url_for(session)
#> "http://localhost:3000/sessions"

Thank you @amatchneer for spotting typos!