Reputation: 105
I'm having some trouble to implement a nested form with a has_one :through
association.
# model: member.rb
belongs_to :user
has_one :academic
# model: user.rb
has_one :member
has_one :academic, through: :member
accepts_nested_attributes_for :member, reject_if: :all_blank
accepts_nested_attributes_for :academic, reject_if: :all_blank
# model: academic.rb
belongs_to :member
belongs_to :user
# users_controller.rb
def new
@user = User.new
@user.build_member
@user.build_academic
end
I also have tried with:
@user.member.build_academic
# new.html.erb
<%= simple_form_for(resource, as: resource_name, url: registration_path(resource_name)) do |ff| %>
<%= ff.text_field :email %>
# member belongs to user so I can call a fields_for
<% ff.fields_for :member do |f| %>
<%= f.text_field :name %>
# this part is not shown. What is wrong with my association?
<% f.fields_for :academic do |a| %>
<%= a.text_field :major %>
<% end %>
<% end %>
<% end %>
I've taken a look into the Rails documentation. The first fields_for
is shown in the page (:member
), but the second one (:academic
), which has the has_one :through
association, is not shown in the page.
Any help will be appreciated. Thank you.
Upvotes: 2
Views: 1451
Reputation: 105
For future reference or for anyone that has the same problem, I found a way around to fix this problem, in case you have same structure.
I could fix this by going to the view
, before the form, and writing:
<% resource.member.build_academic %>
In my case resource
is User
, set by devise
, a Rails gem used for authentication.
And you don't need to reference any :through
or whatsoever in your model.
It is not the most efficient way, but I haven't found any other solution. Hope it helps.
Upvotes: 1
Reputation: 76774
If you want to build your data through a relation, you have to pass the associated data as it's constructed:
#app/models/member.rb
class Member < ActiveRecord::Base
belongs_to :user
has_one :academic
accepts_nested_attributes_for :academic
end
#app/models/user.rb
class User < ActiveRecord::Base
has_one :member
has_one :academic, through: :member
accepts_nested_attributes_for :member
end
#app/models/academic.rb
class Academic < ActiveRecord::Base
belongs_to :member
belongs_to :user
end
This will allow you to do the following:
#app/controllers/members_controller.rb
class MembersController < ApplicationController
def new
@member = Member.new
@member.build_member.build_academic
end
def create
@member = Member.new member_params
@member.save
end
private
def member_params
params.require(:member).permit(:x, :y, :z, academic_attributes: [:some, :attributes, member_attributes:[...]])
end
end
This would permit the following:
#app/views/users/new.html.erb
<%= form_for @user do |f| %>
<%= f.fields_for :member do |m| %>
<%= f.text_field :name %>
...
<%= m.fields_for :academic do |a| %>
<% a.text_field :name %>
...
<% end %>
<% end %>
<%= f.submit %>
<% end %>
This works to build a new member
object, and a new academic
object from the user
. Although not strictly what you're asking, it looks like it could benefit you in some form.
If you want to do what you're asking (IE build_member
and build_academic
exclusively), you'll need to get rid of the has_one :through
relationship...
#app/models/user.rb
class User < ActiveRecord::Base
has_many :memberships
has_many :academics, through: :memberships
end
#app/models/membership.rb
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :academic
end
#app/models/academic.rb
class Academic < ActiveRecord::Base
has_many :memberships
has_many :users, through: :memberships
end
The problem is you're basically trying to build a relationship for a direct association (member
) and an indirect relationship (academic
).
If you want to build both exclusively, you have to make them have a direct association with your main model. The above should allow you to do the following:
#app/controllers/users_controller.rb
class UsersController < ApplicationController
def new
@user = User.new
@user.members.build.academics.build
end
end
Much like my top example, this will pass nested data through your form - if you wanted to have it completely exclusively, do this:
#app/models/user.rb
class User < ActiveRecord::Base
has_one :member
has_one :academic
has_and_belongs_to_many :academics
end
#app/models/member.rb
class Member < ActiveRecord::Base
belongs_to :user
end
#app/models/academic.rb
class Academic < ActiveRecord::Base
belongs_to :user
has_and_belongs_to_many :users
end
This will allow the following:
#app/controllers/users_controller.rb
class UsersController < ApplicationController
def new
@user = User.new
@user.build_member
@user.build_academic
end
def create
@user = User.new user_params
@user.save
end
private
def user_params
params.require(:user).permit(:user, :params, member_attributes: [], academic_attributes:[])
end
end
Upvotes: 3