16

I started using ActiveAdmin recently in a project and almost everything works great but I'm having a problem when using it in combination with the friendly_id gem. I'm getting ActiveRecord::ReadOnlyRecord thrown for my forms [i believe] because of the friendly_id attribute whose ID is readonly:

{"utf8"=>"✓",
"_method"=>"put",
"authenticity_token"=>"Rc5PmUYZt3BiLvfPQr8iCPPXlbfgjoe/n+NhCwXazNs=",
"space"=>{"name"=>"The Kosmonaut",
"address"=>"8 Sichovykh Striltsiv 24",
"email"=>"info@somedomain.com"},
"commit"=>"Update Space",
"id"=>"the-kosmonaut"}  <--- culprit

I'm guessing the last line is the culprit as it's a readonly attribute, it's not in my form but rather in the PATH

http://localhost:5000/manage/spaces/the-kosmonaut/edit

How can I fix this from trying to update the ID?

Form from in ActiveAdmin looks like this:

  form do |f|
    f.inputs "Details" do
      f.input :name
      f.input :address
      f.input :email
      f.input :phone
      f.input :website
    end
    f.inputs "Content" do
      f.input :description
      f.input :blurb
    end
    f.buttons
  end

UPDATE: This doesn't work either so it's not the friendly_id?

I tried using @watson's suggestion which should have worked but still got the same error ;-(

{"utf8"=>"✓",
 "_method"=>"put",
 "authenticity_token"=>"Rc5PmUYZt3BiLvfPQr8iCPPXlbfgjoe/n+NhCwXazNs=",
 "space"=>{"name"=>"The Kosmonaut 23"},
 "commit"=>"Update Space",
 "id"=>"6933"}

http://localhost:5000/manage/spaces/6933/edit

When I check the record in the console with record.readonly? it returns false

UPDATE UPDATE: removing the scope_to fixes the problem.

scope_to :current_user, :unless => proc{ current_user.admin? }

Only problem is I need the scope_to to prevent users from seeing records they do not own. My guess is (as I'm assuming scope_to normally works with has_many) that my HABTM association causes some weirdness? Ie Users <-- HABTM --> Spaces?

holden
  • 13,471
  • 22
  • 98
  • 160

5 Answers5

29

If you only want friendly ID's in the front end and don't care about them inside Active Admin, you can revert the effects of the friendly_id gem for your Active Admin controllers.

I don't know exactly how friendly_id overrides the to_param method, but if it's doing it the normal way, re-overriding it inside all of your Active Admin controllers should fix it, e.g.:

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

Even better you could create a before filter in the base Active Admin controller ActiveAdmin::ResourceController so that it is automatically inherited into all your Active Admin controllers.

First add the filter to the config/initializers/active_admin.rb setup:

ActiveAdmin.setup do |config|
  # ...
  config.before_filter :revert_friendly_id
end

The open up ActiveAdmin::ResourceController and add a revert_friendly_id method, E.g. by adding the following to the end of config/initializers/active_admin.rb:

ActiveAdmin::ResourceController.class_eval do
  protected

  def revert_friendly_id
    model_name = self.class.name.match(/::(.*)Controller$/)[1].singularize

    # Will throw a NameError if the class does not exist
    Module.const_get model_name

    eval(model_name).class_eval do
      def to_param
        id.to_s
      end
    end
  rescue NameError
  end
end

Update: I just updated the last code example to handle controllers with no related model (e.g. the Active Admin Dashboard controller)

Update 2: I just tried using the above hack together with the friendly_id gem and it seems to work just fine :)

Update 3: Cleaned up the code to use the standard way of adding Active Admin before filters to the base controller

Thomas Watson
  • 6,507
  • 5
  • 33
  • 43
  • 1
    I may have been wrong in my assumptions, your solution should have worked, but I still can't modify the record. I changed the question to reflect this. – holden Oct 07 '11 at 09:29
  • Friendly_id seems to be a red herring. Without my current_user scope_to it works fine with friendly_id... ;-( – holden Oct 07 '11 at 09:45
  • I can modify records fine using friendly_id and my above approach. You have updated the question with something that, as far as I understand it, is not related to the friendly_id problem, but is related to authorization. If you need authorization I suggest you take a look at CanCan. – Thomas Watson Oct 07 '11 at 11:46
  • 1
    This works fine in development mode since rails reloads itself with each request. On production however, it only works until I first use active admin. The reopened class (via class_eval) seems to be the problem. The solution from Denny works form me. – sunsations Jan 05 '14 at 20:19
  • Maybe I missed something but I didn't see how this fixed the dashboard controller. I added an empty `revert_friendly_id` to the `controller` block of `app/admin/dashboard.rb` to fix the error. – pix0r Mar 17 '14 at 21:47
  • It works well, but in the index page: ´undefined method revert_friendly_id for Admin::DashboardController´ – Miguel Peniche Feb 17 '16 at 20:06
  • I recommend you avoid this technique, it will modify the `Foobar` class with inconsistent results in a multithreaded environment. – Eliot Sykes Aug 15 '17 at 09:06
16

You can customize the resource retrieval according to http://activeadmin.info/docs/2-resource-customization.html#customizing_resource_retrieval. Note that you want to use the find_resource method instead of resource, or you won't be able to create new records.

(Check https://github.com/gregbell/active_admin/blob/master/lib/active_admin/resource_controller/data_access.rb for more details)

In your ActiveAdmin resource class write:

controller do
  def find_resource
    scoped_collection.where(slug: params[:id]).first!
  end
end

You can also overwrite the behavior for all resources by modyfing the ResourceController in the active_admin.rb initializer.

ActiveAdmin::ResourceController.class_eval do
  def find_resource
    if scoped_collection.is_a? FriendlyId
      scoped_collection.where(slug: params[:id]).first! 
    else
      scoped_collection.where(id: params[:id]).first!
    end
  end
end

Hope that helps!

Side note: When creating new records through the admin interface make sure you don't include the slug field in the form, or FriendlyId will not generate the slugs. (I believe that's for version 5+ only)

Denny
  • 1,210
  • 3
  • 15
  • 26
  • I don't think this would work if using "scoped" slugs in my model. ie. slugs are only guaranteed to be unique when taken in conjunction with some other property/association. Any tips in this situation? – elsurudo Feb 20 '14 at 17:34
4

This method works for me. add this code in app/admin/model_name.rb

ActiveAdmin.register model_name do
  controller do
    defaults finder: :find_by_slug
  end
end
sequielo
  • 1,541
  • 1
  • 18
  • 27
Mashpy Rahman
  • 698
  • 8
  • 16
2

To add to Denny's solution, a more "friendly" solution would be to use friendly_id's finders.

controller do
  def find_resource
    scoped_collection.friendly.find_by_friendly_id(params[:id])
  end
end

Source

Community
  • 1
  • 1
Mavoir
  • 25
  • 5
1

Here is my solution based on @Thomas solution

ActiveAdmin.setup do |config|
  # ...
  config.before_filter :revert_friendly_id, :if => -> { !devise_controller? && resource_controller? }
end

# override #to_param method defined in model in order to make AA generate
# routes like /admin/page/:id/edit
ActiveAdmin::BaseController.class_eval do

  protected
  def resource_controller?
    self.class.superclass.name == "ActiveAdmin::ResourceController"
  end

  def revert_friendly_id
    model_name = self.class.name.match(/::(.*)Controller$/)[1].singularize
    # Will throw a NameError if the class does not exist
    Module.const_get model_name

    eval(model_name).class_eval do
      def to_param
        id.to_s
      end
    end
  rescue NameError
  end
end
Timur
  • 19
  • 2
  • This works for me when I have a registered_page. For example: ActiveAdmin.register_page "Dashboard" Thank @Timur you saved my day. – RoundOutTooSoon Oct 15 '16 at 01:06
  • I recommend you avoid this technique, it will modify the model class with inconsistent results in a multithreaded environment. – Eliot Sykes Aug 15 '17 at 09:07