Custom Active Admin form inputs for has_and_belongs_to_many relationship

10,991

Solution 1

After some research, I am ready to answer my own question.

First, I have to say thanks to @Lichtamberg for suggesting the fix. However, that only complicates things (also regarding security, though not an issue in this case), and doesn't help me reach my ideal solution.

Digging more, I found out that this is a very common scenario in Rails, and it's actually explained in Ryan Bates' screencast no #17.

Therefore, in Rails, if you have a has_and_belongs_to_many (short form HABTM) association, you can easily set the ids of the other associated object through this method:

profile.lifestyle_ids = [1,2]

And this obviously works for forms if you've set the attr_accessible for lifestyle_ids:

class Profile < ActiveRecord::Base
  attr_accessible :lifestyle_ids
end

In ActiveAdmin, because it uses Formtastic, you can use this method to output the correct fields (in this case checkboxes):

f.input :lifestyles, as: :check_boxes, collection: Lifestyle.all

Also, I have simplified my form view so it's now merely this:

  form do |f|
    f.inputs # Include the default inputs
    f.inputs "Lifestlyes" do # Make a panel that holds inputs for lifestyles
      f.input :lifestyles, as: :check_boxes, collection: Lifestyle.all # Use formtastic to output my collection of checkboxes
    end
    f.actions # Include the default actions
  end

Ok, now this rendered perfectly in the view, but if I try and submit my changes, it gives me this database error:

PG::Error: ERROR:  null value in column "created_at" violates not-null constraint
: INSERT INTO "lifestyles_profiles" ("profile_id", "lifestyle_id") VALUES (2, 1) RETURNING "id"

I found out that this is due to the fact that Rails 3.2 doesn't automatically update the timestamps for a HABTM association table (because they are extra attributes, and Rails only handles the _id attributes.

There are 2 solutions to fix this:

  1. Either convert the association into a hm:t (has_many, :through =>)
  2. Or remove the timestamps from the table

I'm going to go for 2) because I will never need the timestamps or any extra attributes.

I hope this helps other people having the same problems.

Edit: @cdesrosiers was closest to the solution but I already wrote this answer before I read his. Anyway, this is great nevertheless. I'm learning a lot.

Solution 2

Active Admin creates a thin DSL (Domain-Specific Language) over formtastic, so it's best to look at the formastic doc when you need form customization. There, you'll find that you might be able to use f.input :lifestyles, :as => :check_boxes to modify a has_and_belongs_to_many relationship.

I say "might" because I haven't tried this helper myself for your particular case, but these things have a tendency to just work automagically, so try it out.

Also, you probably won't need accepts_nested_attributes_for :lifestyles unless you actually want to modify the attributes of lifestyles from profiles, which I don't think is particularly useful when using active admin (just modify lifestyles directly).

Share:
10,991
Cristian
Author by

Cristian

Updated on June 05, 2022

Comments

  • Cristian
    Cristian almost 2 years

    I have a very simple model

    class Lifestyle < ActiveRecord::Base
      attr_accessible :name
      has_and_belongs_to_many :profiles
    end
    

    that has a has_and_belongs_to_many relationship with Profile

    class Profile < ActiveRecord::Base
      attr_accessible ...
    
      belongs_to :occupation
    
      has_and_belongs_to_many :lifestyles
      accepts_nested_attributes_for :lifestyles
    end
    

    I want to use ActiveAdmin to edit the Profile object, but also assign Lifestyles to a profile. It should be similar to dealing with belongs_to :occupation, as this is sorted out automatically by ActiveAdmin to a dropbox with the options pre-filled with available occupations.

    I've tried to use the has_many form builder method, but that only got me to show a form to type in the name of the Lifestyle and on submission, it returned an error.

        f.object.lifestyles.build
        f.has_many :lifestyles do |l|
          l.input :name
        end
    

    Error I get:

    Can't mass-assign protected attributes: lifestyles_attributes
    

    The perfect way for me would be to build several checkboxes, one for each Lifestyle in the DB. Selected means that the lifestyle is connected to the profile, and unselected means to delete the relation.

    I'm having great doubts that this is possible using ActiveAdmin and without having to create very complex logic to deal with this. I would really appreciate it if you'd give your opinion and advise me if I should go this way or approach it differently.

  • Cristian
    Cristian over 11 years
    Nice, very good answer. I was just writing my own answer and didn't see this. I figured it out in the end, and Ryan Bates' screencast helped me learn the stuff you're talking about here. I knew there was a smarter way in Rails :)
  • cdesrosiers
    cdesrosiers over 11 years
    +1 for taking the time to answer your own question in detail.
  • asymmetric
    asymmetric over 10 years
    Interesting that I didn't have to specify the collection manually (activeadmin 0.5.1, formtastic 2.2.1). Great answer anyway!