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:
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
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 actually was running into the same issues which was resulting in me having to change the way my application functioned to get around this, which I did not like one bit.
So...I wrote a little gem that covers generating these slugs that works very well with activeadmin and the rest of my application. Available at https://github.com/HuffMoody/has_unique_slug if anyone is interested.
anyone found a solution to this?
Just submitted pull request #785 for this issue here: https://github.com/gregbell/active_admin/pull/785
Hi Matt. That's a clean fix, and submitted with tests too. Nice one.
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
+1
Any solution to this yet?
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
+1 for armstrjare's solution.
+1 thanks for the solution, was the only thing that worked for me
+1 thanks armstrjare's
+1 for mdoyle13 (UPDATE 2). That works well when to_param returns something find_by-able.
+1 for Update 2 by mdoyle13. Worked perfectly.
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!
just adding my +1 here
one thing though my application doesn't respond to
defaults finder: :find_by_id
at all
@gilsilas 's patch worked like a charm though
Setting a defaults finder works in some cases.
But it won't work if you have a validation error on the to_param field.
E.g.
class MyModel < ActiveRecord::Base
validates_format_of :slug, with: /^mandatory_prefix[0-9]+/
end
irb > MyModel.create!(slug: "mandatory_prefix_1")
Go to /admin/my_models/mandatory_prefix_1/edit
Set the slug to "wrong_prefix_2" and save
Page reloads, with the slug validation error (as expected)
Change the slug to "mandatory_prefix_2" submit again and you'll get:
ActiveRecord::RecordNotFound
Couldn't find MyModel with slug = wrong_prefix_2
Because the form_for was rendered using the invalid (unsaved) MyModel#slug
:+1: for solving this issue
Does someone want to open a pull request for this?
controller do
defaults finder: :find_by_slug
end
:+1:
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
.
@stevegrossi that behavior could definitely happen if you're using a multithreaded server.
You might have better luck with the "defaults finder: :find_by_slug" approach.
Indeed, the site's served up by Puma. I didn't realize the threading connection here, so thanks for the tip.
I don't think any of the above solutions will work if you're using friendly_id's "scope" feature, as the slug for a resource is not guaranteed to be unique. Has anyone found a decent way to deal with this?
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.
@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
@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
This works perfectly for me :+1:
Thanks @mdoyle13
controller do
defaults :finder => :find_by_slug
end
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
I think there's more than one problem being discussed on this issue, and defaults finder: :find_by_slug
only addresses one of them.
As I see it:
to_param
which they need to tie back into their ActiveAdmin pages, so that things can be looked up properly.to_param
but don't want it to have any effect on ActiveAdmin pages, so that everything is based on ID.(2) causes a bunch of problems. It means that you have to specify .id
on any link_to
s in the view templates, but it also means that for stuff like index
blocks, you have to reimplement the link to "View" a particular record.
column "" do |foo|
link_to "View", admin_foo_path(foo.id)
end
That's very hacky and isn't very sustainable in the face of future changes to how ActiveAdmin renders the index table, or handling permissions for which links are visible by a particular admin user. (It's also fairly cringeworthy that other solutions posted here include dynamically changing method definitions on the fly.)
So basically while (1) is solved by specifying a :finder
, I don't think (2) has been addressed. Does it not belong in this issue?
@aprescott If you use a custom to_param
you need to use it everywhere. To build a config.use_to_param = false
would be a massiv code blow up, that we can't do. Thats way we use the Rails url helpers under the hood and they use the to_param
method. You have two choices, customize to_param
or not. Customize to_param
, but not for ActiveAdmin is not and probably will not be supported way.
Here are some DIY ideas:
url_for
in ActiveAdmin to use .id
instate of .to_params
.User
, a FrontendUser
and a BackendUser
class. Then you can define to_params
only on the FrontendUser
. (depend on what you wan't to do, you don't need all three classes)BTW: It's not only a ActiveAdmin problem, we use Formtastic and inherited_resources which both need to change too.
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
+1 for mdoyle13 (UPDATE 2). Works for me.
defaults finder: :find_by_slug
works, however, if I have a child belongs_to
resource within the parent resource, I again get an error:
Couldn't find Foo with 'id'=bar
This happens in:
def scoped_collection
super.includes(:bazes)
end
This method is defined within the controller
block of the child resource. So I guess, there must be something like:
defaults parent_finder: :find_by_slug
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
Most helpful comment
This worked flawlessly for me. Thanks watson.
UPDATE: It works sporadically.
UPDATE 2: The following seems to be more stable.