Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom Active Admin form inputs for has_and_belongs_to_many relationship

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.

like image 314
Cristian Avatar asked Oct 29 '12 22:10

Cristian


2 Answers

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.

like image 84
Cristian Avatar answered Oct 05 '22 00:10

Cristian


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).

like image 44
cdesrosiers Avatar answered Oct 05 '22 00:10

cdesrosiers