Reputation: 864
I've read through many other topics here (1, 2, 3...) but none really solved my problem.
Here are my 3 models.
User
has_many :memberships
has_many :accounts, :through => :memberships
accepts_nested_attributes_for :memberships
end
Account
has_many :memberships
has_many :users, :through => :memberships
accepts_nested_attributes_for :memberships
end
Membership
attr_accessible :account_id, :url, :user_id
belongs_to :account
belongs_to :user
end
As you can see, my join model Membership has an additional attribute: :url
.
In my Accounts table, I store names of online services, such as GitHub, Stack Overflow, Twitter, Facebook, LinkedIn.. I have 9 in total. It's a fixed amount of accounts that I don't tend to update very often.
In my User form, I'd like to create this:
The value entered in any of these field should be submitted in the Memberships table only, using 3 values:
url
(the value entered in the text field)user_id
(the id of the current user form)account_id
(the id of the related account, e.g. LinkedIn is '5')I have tried 3 options. They all work but only partially.
<% for account in @accounts %>
<%= f.fields_for :memberships do |m| %>
<div class="field">
<%= m.label account.name %><br>
<%= m.text_field :url %>
</div>
<% end %>
<% end %>
I want to have 9 text field, one for each account. So I loop through my accounts, and create a url field related to my memberships model.
It shows my fields correctly on the first time, but the next time it'll display 81 fields:
<% @accounts.each do |account| %>
<p>
<%= label_tag(account.name) %><br>
<%= text_field_tag("user[memberships_attributes][][url]") %>
<%= hidden_field_tag("user[memberships_attributes][][account_id]", account.id) %>
<%= hidden_field_tag("user[memberships_attributes][][user_id]", @user.id) %>
</p>
<% end %>
I'm trying to manually enter the 3 values in each column of my Memberships tables.
It works but :
<%= f.fields_for :memberships do |m| %>
<div class="field">
<%= m.label m.object.account.name %><br>
<%= m.text_field :url %>
</div>
<% end %>
I'm creating a nested form in my User form, for my Membership model.
It works almost perfectly:
But, it only works if my Memberships table is already populated! (Using Option #2 for example).
So I tried building some instances using the UsersController:
if (@user.memberships.empty?)
@user.memberships.build
end
But I still get this error for my m.label m.object.account.name line.
undefined method `name' for nil:NilClass
Anyway, I'm probably missing something here about has_many through models. I've managed to create has_and_belongs_to_many associations but here, I want to work on that join model (Membership), through the first model (User), using information about the third model (Account).
I'd appreciate your help. Thank you.
Upvotes: 3
Views: 4892
Reputation: 64363
I would use the following data-design approach. All users in your system should have the
memebership entries for all possible accounts. The active configurations will have a value for the url
field.
User
has_many :memberships
has_many :accounts, :through => :memberships
has_many :active_accounts, :through => :memberships,
:source => :account, :conditions => "memberships.url IS NOT NULL"
accepts_nested_attributes_for :memberships
end
Now
curent_user.active_accounts # will return the accounts with configuration
curent_user.accounts # will return all possible accounts
Add a before_filter
to initialize all the memberships that a user can have.
class UsersController
before_filter :initialize_memberships, :only => [:new, :edit]
private
def initialize_memberships
accounts = if @user.accounts.present?
Account.where("id NOT IN (?)", @user.account_ids)
else
Account.scoped
end
accounts.each do |account|
@user.memberships.build(:account_id => account.id)
end
end
end
In this scenario you need to initialize the memeberships before the new
action and all the memberships should
be saved in the create
action ( even the ones without url
).
Your edit action doesn't need to perform any additional data massaging.
Note:
I am suggesting this approach as it makes the management of the form/data straight forward. It should only
be used if the number of Account
's being associated is handful.
Upvotes: 2
Reputation: 29599
in the controller, fetch the list of memberships for a particular user
# controller
# no need to make this an instance variable since you're using fields_for in the view
# and we're building additional memberships later
memberships = @user.memberships
then loop through each account and build a membership if the user has no membership for an account yet.
# still in the controller
Account.find_each do |account|
unless memberships.detect { |m| m.account_id == account.id }
@user.memberships.build account_id: account.id
end
end
then in your view, you change nothing :)
Upvotes: 2