Reputation: 355
In my application, I have a entity called User, that has one Talent. A talent is a kind a user can be on my system (a model, a photographer, a videomaker, a client)
#user.rb
class User < ApplicationRecord
has_one :talent, dependent: :destroy
A Talent belongs to a user, and have many TalentAbilities.
#talent.rb
class Talent < ApplicationRecord
belongs_to :user
has_many :talent_talent_abilities, dependent: :destroy
has_many :talent_abilities, through: :talent_talent_abilities
So, we can create, for example, a TalentAbility that belongs to a Talent. Like this:
TalentType.create!([
{name: "Model", is_model: true, is_photo: false, is_makeup: false, is_hair: false},
{name: "Photographer", is_model: false, is_photo: true, is_makeup: false, is_hair: false
])
TalentAbility.create!([
{name: "Scuba diving", requires_description: false, talent_type_id: 1}
{name: "River rafting", requires_description: false, talent_type_id: 1},
I want to add a View, where the user, based on your TalentType, edit his profile, with a lot of checkboxes where he can click for example, "Scuba Diving -> true" . "River rafting -> false".
My question is: How is the better way to update those relationships this on my controller? First remove all the TalentTalentAbility that belongs to this talent, and then add all based on my form?
#profiles_controler.rb
def update
##delete all the entities
TalentTalentAbility.where(talent: u.talent).each do |tt|
tt.destroy
end
##some code to add the new relationships
end
Thanks
Upvotes: 1
Views: 171
Reputation: 5313
The problem with the accepted answer:
Say you have a user with 5 talents and he chooses that he's learned a sixth one. If you go the way of calling .destroy_all
, this is what will happen:
DELETE
statements sent to DBINSERT
statements sent to DBThe correct solution for such problem is to render the checkboxes with their respective ids and make use of _destroy
(read more here) attribute.
In short, if you get a hash (you may need to clean it up after you get it from your form, I'm sorry, but I don't have anything on hand to give you a A-Z example) containing something like this:
..., talent_talent_abilities_attributes: [
{id: 1, _destroy: true},
{id: nil, talent_ability_id: 1}
{id: 2, talent_ability_id: 2, some_attribute: "value"}
]
And you save your user, ActiveRecord will:
DELETE
statement for first item.INSERT
statement for second item.id: 2
, it might or might not issue an UPDATE
statement.I suggest you tinker a bit in rails console
with creating a User and updating his abilities manually using the nested attribute array (which again, please read more about at the aforementioned link) and observing how many SQL queries it generates. If you have any further questions, leave a comment under this or create a separate, more specific question.
Upvotes: 2
Reputation: 7011
I've done something similar in the past and it's worked really well. I would move it out to the model though, so you'd have something like this.
class User < ActiveRecord::Base
ActiveModel::Dirty # if you need to track changes and only delete on attrs changed
before_save :update_talents
# ...
private
def update_talents
talents.destroy_all if talents_changed?
end
end
Then the new talents will be saved with the form data. Also, that way your controller won't be needlessly messy.
You should be careful, though, to ensure that that's the behaviour you want. You could add in a check to only update if the talents are being changed with ActiveModel::Dirty
, too. Might wanna check the syntax on the *_changed
method for associations.
Caveat
I tend to advocate this approach because my nested form used JS positioning to re-order items therein. There might be a saner approach.
Upvotes: 1