Rails Has_Many Through Form with Additional Attributes

Rails 4 nested attributes and has_many :through associaton in a form

When employing a has_many :through relationship, you need to pass the nested_attributes through the different models, like this:

Models

class Goal < ActiveRecord::Base
has_many :milestones, inverse_of: :goal, dependent: :destroy
accepts_nested_attributes_for :milestones, :allow_destroy => true

def self.build
goal = self.new
goal.milestones.build.milestone_programs.build_program
end
end

class Milestone < ActiveRecord::Base
belongs_to :goal, inverse_of: :milestones

has_many :milestone_programs
has_many :programs, through: :milestone_programs

accepts_nested_attributes_for :milestone_programs
end

class MilestoneProgram < ActiveRecord::Base
belongs_to :milestone
belongs_to :program

accepts_nested_attributes_for :program
end

class Program
has_many :milestone_programs
has_many :milestones, through: :milestone_programs
end

Controller

#app/controllers/goals_controller.rb
def new
@goal = Goal.build
end

def create
@goal = Goal.new(goal_params)
@goal.save
end

private

def goal_params
params.require(:goal).permit(milestones_attributes: [milestone_programs_attributes: [program_attributes:[]]])
end

Form

#app/views/goals/new.html.erb
<%= form_for @goal do |f| %>
<%= f.fields_for :milestones do |m| %>
<%= m.fields_for :milestone_programs do |mp| %>
<%= mp.fields_for :program do |p| %>
<%= p.text_field :name %>
<% end %>
<% end %>
<% end %>
<%= f.submit %>
<% end %>

I appreciate this might not be exactly what you're looking for, but tbh I didn't read all your prose. I just gathered you needed help passing nested_attributes through a has_many :through relationship

Rails forms for has_many through association with additional attributes?

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.

Rails 5 - has_many through: and nested fields_for in forms

You're making a classic newbie misstake and using fields_for when you just want to create an association by passing an id.

<%= form_with model: @category, local: true do |f| %>

# ...

<%= f.fields_for :category_infos do |cif| %>
<%= cif.collection_select(:language_id, Language.all, :name, :id) %>
<%= cif.text_field :label %>
<% end %>
<% end %>

While you could also pass the attributes to let a users create languages at the same time its very much an anti-pattern as it adds a crazy amount of responsibilities to a single controller. It will also create an authorization problem if the user is allowed to create categories but not languages.

Use ajax to send requests to a seperate languages controller instead if you need the feature.

Rails form with nested attributes in has_many through relationship

Try setting the inverse_of option on the has_many associations

  has_many :admin_host_users, class_name: 'Admin::HostUser', foreign_key: 'admin_host_id', dependent: :destroy, inverse_of: 'Admin::Host'
has_many :users, through: :admin_host_users, inverse_of: 'Admin::Host'

has_many through form and adding attribute to join table

Account model:

class Account < ActiveRecord::Base
attr_accessible :name, :description, :user_accounts_attributes
has_many :user_accounts, :dependent => :destroy
has_many :users, :through => :user_accounts
has_many :user_without_accounts, :class_name => 'User', :finder_sql => Proc.new {
%Q{
SELECT *
FROM users where id NOT IN (Select user_id from user_accounts where account_id = #{id})
}
}

accepts_nested_attributes_for :user_accounts, reject_if: proc { |attributes| attributes['user_id'] == '0' }

def without_accounts
new_record? ? User.all : user_without_accounts
end
end

In form:

= simple_form_for @account do |f|
= f.input :name
= f.input :description
- @account.user_accounts.each do |user_account|
= f.simple_fields_for :user_accounts, user_account do |assignment|
= assignment.check_box :user_id
= assignment.label :user_id, user_account.user.name rescue raise user_account.inspect
= assignment.input :account_admin
%hr
- @account.without_accounts.each do |user|
= f.simple_fields_for :user_accounts, @account.user_accounts.build do |assignment|
= assignment.check_box :user_id
= assignment.label :user_id, user.name
= assignment.input :account_admin
%hr
= f.button :submit

Rails 5 - has_many through and nested attributes forms

Explanations:

  • Your request_params permits table_locations: [:id], but this only permits the following format:

    Parameters: {"utf8"=>"✓", ... "request"=>{"table_locations"=>{"id"=>"1"}, "comments"=>""}, "commit"=>"Submit"}

    but yours is showing to be:

    Parameters: {"utf8"=>"✓", ... "request"=>{"table_locations"=>["1"], "comments"=>""}, "commit"=>"Submit"}
    • therefore, try this: puts request_params inside the create method, and you'll notice that it doesn't have table_locations values (even though you thought it's there, but it's not), because it is not "properly" whitelisted in your strong params request_params.
  • To be able to associate multiple TableLocations objects to a newly built Request object, the format should be something like below

    request = Request.new(table_location_ids: [1,2,3,4,5])

    but from your implementation, you're doing it like this (which won't work):

    request = Request.new(table_locations: [1,2,3,4,5])
    # => this will raise an error:
    # ActiveRecord::AssociationTypeMismatch: TableLocation expected, got Fixnum
    # however yours didn't raise an error, because it was not whitelisted in the request_params in the first place

Solution:

requests_controller.rb

def request_params
params.require(:request).permit(..., table_location_ids: [])
end

_form.html.erb

<% TableLocation.all.each do |t| %>
<%= check_box_tag "request[table_location_ids][]", t.id %>
<%= t.location %>
<br />
<% end %>

Recommendation:

  • just in case you don't know yet that you can do the following in this way, I'll be refactoring your code to look something like this:

    requests_controller.rb

    def create
    @table_locations = TableLocation.all
    end

    _form.html.erb

    <%= form_for @request do |f| %>
    <% @table_locations.each do |table_location| %>
    <%= f.check_box :table_location_ids, multiple: true, table_location.id %>
    <% end %>
    <% end %>

accept nested attributes for has_many & has_many through in same form_for

You have many pluralization errors. Check these changes:

class User < ActiveRecord::Base
has_many :tasks
has_many :task_items, through: :tasks #not task
accepts_nested_attributes_for :tasks #not task
accepts_nested_attributes_for :task_items #not task_item
end

class Task < ActiveRecord::Base
belongs_to :user
has_many :task_items #not task_item
end

class TaskItem < ActiveRecord::Base
belongs_to :task
end

Then, the view:

<%= fields_for :user, user do |f| %>
<%= f.fields_for :tasks do |builder| %>
<%= builder.text_field :name %>
<%= builder.fields_for :task_items do |ti| %>
<%= ti.check_box :completed %>
<% end %>
<% end %>
<% end %>


Related Topics



Leave a reply



Submit