Fernando Maymone
Fernando Maymone

Reputation: 355

How to make an update on a Nested form in ruby?

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

Answers (2)

Marcin Kołodziej
Marcin Kołodziej

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:

  • 5 separate, sequential DELETE statements sent to DB
  • 6 separate, sequential INSERT statements sent to DB

The 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:

  • Issue one DELETE statement for first item.
  • Issue one INSERT statement for second item.
  • Depending on whether your records are loaded and whether something changed for ability with 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

t56k
t56k

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

Related Questions