Marco
Marco

Reputation: 4395

Rails forms for has_many through association with additional attributes?

How can I generate form fields for a has_many :through association that has additional attributes?

The has_many :through relationship has an additional column called weight.

Here's the migration file for the join table:

create_table :users_widgets do |t|
  t.integer :user_id
  t.integer :widget_id
  t.integer :weight

  t.timestamps
end

The models look like this:

User
  has_many :widgets, :through => :users_widgets,
           :class_name => 'Widget',
           :source => :widget
  has_many :users_widgets
  accepts_nested_attributes_for :widgets # not sure if this is necessary


Widget
  has_many :users, :through => :users_widgets,
           :class_name => 'User',
           :source => :user
  has_many :users_widgets
  accepts_nested_attributes_for :users # not sure if this is necessary


UsersWidget
  belongs_to :user
  belongs_to :widget

For the sake of simplicity, Widget and User only have one field of their own called name, ergo User.first.name and Widget.first.name

Questions:


Quick note.. I'm not interested in creating new Widgets in the User form or new Users in the Widget form, I only want the ability to select from existing objects.

I'm running Rails 3.1 and simple_form 2.0.0dev for generating my forms.

Upvotes: 8

Views: 6607

Answers (3)

helmerj
helmerj

Reputation: 11

Thanks a lot for the cocoon pointer nathanvda. I have been scratching my head about some problems I had when trying to implement this under rails 4.0.0-rc1 and I thought I would share my findings just in case someone has the same problems when attempting this udner rails4.

Using the above code as an example, I did add user_id and widget_id to the permitted parameters as they are saved in the connecting table user_widgets. In rails 3 you did have to add them to attr_accesible in the user model but in rails 4 you have to add them to the allowed parameters in the controller of the main model you use for nesting, so here that would be the users_controller:

params.require(:user).permit(...user_fields...,  
  user_widgets_attributes: [:user_id, :widget_id])

Doing only this you end up with several of problems:

  1. Every association (widget) gets multiplied when updating a user record. 1 becomes 2, 4, 8, and so on, when updating and saving the record.
  2. removing an association does not work, the field is removed from the form but the association remains in the DB.

To fix these problems you also need to add :id and :_destroy to the list of permitted attributes:

params.require(:user).permit(...user_fields...,  
  user_widgets_attributes: [:user_id, :widget_id, :id, :_destroy])

after that it works flawlessly.

Juergen

PS: For now you have to use the git repository in your Gemfile to use cocoon under rails 4 until a rails 4 compatible gem is released. Thanks for the email nathanvda on my bug report!!

Upvotes: 0

nathanvda
nathanvda

Reputation: 50057

I will be solving your problem using cocoon, a gem I created to handle dynamically nested forms. I also have an example project to show examples of different types of relationships.

Yours is not literally included, but is not that hard to derive from it. In your model you should write:

class User 
  has_many :users_widgets
  has_many :widgets, :through -> :user_widgets

  accepts_nested_attributes_for :user_widgets, :reject_if => :all_blank, :allow_destroy => true

  #...
end

Then you need to create a partial view which will list your linked UserWidgets. Place this partial in a file called users/_user_widget_fields.html.haml:

.nested-fields
  = f.association :widget, :collection => Widget.all, :prompt => 'Choose an existing widget'
  = f.input :weight, :hint => 'The weight will determine the order of the widgets'
  = link_to_remove_association "remove tag", f

In your users/edit.html.haml you can then write:

= simple_form_for @user do |f|
  = f.input :name

  = f.simple_fields_for :user_widgets do |user_widget|
    = render 'user_widget_fields', :f => user_widget
  .links
    = link_to_add_association 'add widget', f, :user_widgets

Hope this helps.

Upvotes: 6

Ankun
Ankun

Reputation: 444

base your question. I made a simple App. source is here: https://github.com/yakjuly/nest_form_example I deployed it to heroku, you can open page: http://glowing-lightning-1954.heroku.com/users/new

answers

  1. you want user form can select widget with weight, need do more work.dropdown selection can not satisfy your requirement.

  2. I mix "nested_form" in a "bootstrap-rails" plugin, you can add the nested_fields easier

  3. in my example, you need add a action calls select, and make WidgetsController#create can responsed_with :js

the code is based simple_form, you can have a try.

Upvotes: 6

Related Questions