activeadmin: Routing Errors When to_param is Overwritten

I overwrote to_param in a model to implement friendly urls. Active Admin can no longer link to the admin urls that require ids without throwing a routing error.

party.rb:

# Should give me urls like this: /parties/23/name-of-party
def to_param
  "#{id}/#{slug}"
end

def slug
  self.name.parameterize
end

routes.rb:

resources :parties, :constraints => { :id => /[0-9]+\/.+/ }

I believe this a different problem than the other to_param issues listed below as I’m getting a routing error, not a active record loading error. Specifically, I get a routing error when any active admin page attempts to build a link that requires an id.

Is there any way to force active admin to ignore the overwritten to_param method?

Other issue that involve to_param: #35: https://github.com/gregbell/active_admin/issues/35 #62: https://github.com/gregbell/active_admin/issues/62 #191: https://github.com/gregbell/active_admin/issues/191

About this issue

  • Original URL
  • State: closed
  • Created 13 years ago
  • Comments: 35 (6 by maintainers)

Most upvoted comments

This worked flawlessly for me. Thanks watson.

ActiveAdmin.register Post do
  before_filter do
    Post.class_eval do
      def to_param
        id.to_s
      end
    end
  end
end

UPDATE: It works sporadically.

UPDATE 2: The following seems to be more stable.

ActiveAdmin.register Category do
    controller do
        defaults :finder => :find_by_slug
    end
end

OK, so it seems that the resources configurations for a parent resources are set by the options of the belongs_to. So in order to be able to use a custom finder for the parent resource, you need to do this:

belongs_to :foo, finder: :find_by_slug

It doesn’t sound that different, but here’s a different solution: Run-time redefine the to_param method like this:

ActiveAdmin.register Post do
  before_filter do
    Post.class_eval do
      def to_param
        id.to_s
      end
    end
  end
end

@tomgrim this also worked for me, but I also needed to add an after_filter so that it reverted the change. Otherwise, the change remains active and it can affect the rest of the application (that is, links on the public site using ID for the param).

Better than that, I preferred to use an around_filter, like this:

# app/models/foobar.rb
# In my app I use a generated token as the ID
class Foobar < ActiveRecord::Base
  def to_param
    token
  end
end

# app/admin/foobars.rb
ActiveAdmin.register Foobar do
  controller do

    around_filter :use_id_instead_of_token_as_param

    def use_id_instead_of_token_as_param
      Foobar.class_eval do
        def to_param
          id
        end
      end

      yield

      Foobar.class_eval do
        def to_param
          token
        end
      end
    end

  end
end

@elsurudo I had the same problem and fixed it with the following code:

ActiveAdmin.register Foobar do
  before_filter do
    Foobar.class_eval do
      def to_param
        id.to_s
      end
    end
  end
end

Source: http://stackoverflow.com/questions/7684644/activerecordreadonlyrecord-when-using-activeadmin-and-friendly-id

I encountered a similar problem to this, a code base I was working with had a slug on a Foo model (provided by friendly_id) that was defined as being scoped to another model:

class Foo < ActiveRecord::Base
  friendly_id :name, use: :scoped, scope: :bar_category
  # ...
end

I won’t go into why this isn’t being treated as a nested resource in the active admin setup we are using, but because it isn’t we found that active admin ended up using the non-unique slug for its urls, i.e. a url for a ‘foo’ object might be http://localhost:3000/admin/foos/not-a-unique-slug/edit and this meant that which object actually got modified was unpredictable.

My solution was to override the inherited_resources resource path/url helpers in the controller so that the urls instead used the id of the model, i.e:

ActiveAdmin.register Foo do

  # ...

  controller do
    # Overriding resource url helpers so that this admin controller uses the id
    # for urls, rather than the slug (which can have duplicates):
    def resource_path(*given_args)
      given_options = given_args.extract_options!
      admin_foo_path((given_args.first || @foo).id, given_options)
    end
    def edit_resource_path(*given_args)
      given_options = given_args.extract_options!
      edit_admin_foo_path((given_args.first || @foo).id, given_options)
    end
    def resource_url(*given_args)
      given_options = given_args.extract_options!
      admin_foo_url((given_args.first || @foo).id, given_options)
    end
  end
end

This is a bit brittle as it relies on knowledge of the inner workings of both active_admin and inherited_resources, but it does fix the problem for us.

I hope this might help others looking at this issue, but I’m open to suggestions of a better way to handle this.

Until the patch above is merged, here is an improvement on the overriding approach which doesn’t require redefining the custom to_param semantics:

  around_filter do |controller, action|
    MyModel.class_eval do
      alias :__active_admin_to_param :to_param
      def to_param() id.to_s end
    end

    begin
      action.call
    ensure
      MyModel.class_eval do
        alias :to_param :__active_admin_to_param
      end
    end
  end

In production, rails cache the classes. therefore, when you override the to_param method it’s affect the whole site.

The best solution I found is to use around_filter to override to_param again to the original state

around_filter do |controller, action|
  User.class_eval do
    def to_param
      id.to_s
    end
  end

  begin
    action.call
  ensure
    User.class_eval do
      def to_param
        username
      end
    end
  end
end

I see this is an old thread but I wanted to comment on a solution that worked for me while hitting this same problem. I had redefined to_param on a Job model and what I did was make an AdminJob model that inherits from Job and defines to_param the standard way (return instance id). That is:

class Job < ActiveRecord::Base
end

class AdminJob < Job
  def to_param
    id.to_s
  end
end

Then on Active Admin’s side I did:

ActiveAdmin.register AdminJob, as: "Job" do
end

I currently haven’t find any problems with this, it seems ActiveRecord is quite smart with inheritance.

Cheers

I’m closing this, while it’s a inherited_resources bug and not a ActiveAdmin one.

This should be the correct way to handle it:

controller do
  defaults finder: :find_by_slug
end

This works perfectly for me 👍

Thanks @mdoyle13

controller do
  defaults :finder => :find_by_slug
end

I’m working on an app that makes use of @armstrjare’s solution with class_eval and we’re noticing some very strange behavior. Occasionally, visitors are reporting seeing links like

/pages/3 (which 404s)

instead of

/pages/about

I suspect this may be related to the fact that class_eval is temporarily redefining Page#to_param to use the id instead of the slug.

The issue roots all the way down to inherited resources which offers a solution to this built in.

The best way to solve this is to add defaults for inherited resources in the controller block provided by active admin.


ActiveAdmin.register Article do

  controller do
    defaults finder: :find_by_slug
  end

end

Where slug is the name of the column I am using to store the slug/permalink/etc. You can pass any method on the model into here to allow active admin to find the resource.

Hope this helps!