Search code examples
ruby-on-railsrails-admin

Using namespaced models with polymorphic assocations in Rails Admin


In RailsAdmin I am trying to manage my polymorphic Region with two classes, one namespaced and one not.

class Region < ApplicationRecord
  belongs_to :contentable, polymorphic: true
end

class Event::Exhibition < ApplicationRecord
  has_many :regions, as: :contentable
end

class Post < ApplicationRecord
  has_many :regions, as: :contentable
end

Everything works bar the ajax fetch of instances of namespaced models.

When I try and select an Event::Exhibition, for example, I see this in my browser's console.

Error: Syntax error, unrecognized expression: #event::exhibition-js-options rails_admin.js:1502:8
    error http://localhost:3000/assets/rails_admin/rails_admin.js:1502

When I select Post all of my posts are returned as expected.

Is this a bug or should I be able to work around this with different settings? The only config I have for this is to tell Rails Admin to use the field.

edit do
  field :contentable
end

Digging into the HTML and JavaScript a bit more (thanks @Guillermo) I noticed the generated dropdown looks like this.

<option value=""></option>
<option value="Event::ArtFair">Art fair</option>
<option value="Event::Exhibition">Exhibition</option>
<option selected="selected" value="Post">News article</option>

When either option with a value Event:: is selected, Sizzle complains, throwing a syntax error.

In my code inspector, if I escape the colons, making the values Event\:\:ArtFair and Event\:\:Exhibition things work as expected.


Solution

  • I see, those are not valid js identifiers. I think you can scape the values if you defined the content of the select by adding the _enum instance method.

    First you need to get a list of possible contentables for the model.

    I'm shooting from the hip here but it should be something like:

      def self.contentable_models
        ActiveRecord::Base.descendants.select do |model|
          model.reflect_on_all_associations(:has_many).any? do |has_many_association|
            has_many_association.options[:as] == :contentable
          end
        end
      end
    
      def contentable_enum
        self.class.contentable_models.map |model|
          [
            model.name.humanize,
            model.class.name.gsub(':','\:')
          ]
        end
      end
    

    I'm not sure about the performance of the contentable_models if you have many models it will iterate on all of them to find the contentable you might want to memoize that value.

    You might need to define the contentable field as an enum or even the contentable_type field. Not sure