Question: rails - left shift "<<" operator saves record automatically

Question

rails - left shift "<<" operator saves record automatically

Answers 3
Added at 2017-01-01 10:01
Tags
Question

Need help understanding this code, as what to my knowledge I know "<<" append to a collection but here it saves the record correctly, how come it does without calling .save method?

#user.rb
 has_many :saved_properties, through: :property_saves, source: :property

#users_controller.rb
def update
  if @user.saved_properties << Property.find(params[:saved_property_id])
        render plain: "Property saved"
end
Answers
nr: #1 dodano: 2017-01-01 11:01

In the has_many documentation it says:

Adds one or more objects to the collection by setting their foreign keys to the collection's primary key. Note that this operation instantly fires update SQL without waiting for the save or update call on the parent object, unless the parent object is a new record.

nr: #2 dodano: 2017-01-01 11:01

In a has_many relationship the link information is saved in the target record. This means that << would have to modify that record in order to add it to the set.

Perhaps intending convenience, ActiveRecord automatically saves these for you when making an assignment if the assignment was successful. The exception is for new records, the record they're being associated with doesn't have any identifier so that has to be delayed. They are saved when the record they're associated with is finally created.

This can be a little confusing, perhaps unexpected, but it's actually the thing you'd want to happen 99% of the time. If you don't want that to happen you should manipulate the linkage manually:

 property = Property.find(params[:saved_property_id])
 property.user = @user
 property.save!

That's basically equivalent but a lot more verbose.

nr: #3 dodano: 2017-01-01 11:01

Maybe looking at the source code will help you. This is my trail of searches based on the << method in activerecord:

def <<(*records)
  proxy_association.concat(records) && self
end

rails/collection_proxy.rb at 5053d5251fb8c03e666f1f8b765464ec33e3066e · rails/rails · GitHub

def concat(*records)
  records = records.flatten
  if owner.new_record?
    load_target
    concat_records(records)
  else
    transaction { concat_records(records) }
  end
end

rails/collection_association.rb at 5053d5251fb8c03e666f1f8b765464ec33e3066e · rails/rails · GitHub

def concat_records(records, should_raise = false)
  result = true

  records.each do |record|
    raise_on_type_mismatch!(record)
    add_to_target(record) do |rec|
      result &&= insert_record(rec, true, should_raise) unless owner.new_record?
    end
  end

  result && records
end

rails/collection_association.rb at 5053d5251fb8c03e666f1f8b765464ec33e3066e · rails/rails · GitHub

  def insert_record(record, validate = true, raise = false)
    set_owner_attributes(record)
    set_inverse_instance(record)

    if raise
      record.save!(validate: validate)
    else
      record.save(validate: validate)
    end
  end

https://github.com/rails/rails/blob/5053d5251fb8c03e666f1f8b765464ec33e3066e/activerecord/lib/active_record/associations/has_many_association.rb#L32

  def insert_record(record, validate = true, raise = false)
    ensure_not_nested

    if record.new_record? || record.has_changes_to_save?
      if raise
        record.save!(validate: validate)
      else
        return unless record.save(validate: validate)
      end
    end

    save_through_record(record)

    record
  end

https://github.com/rails/rails/blob/5053d5251fb8c03e666f1f8b765464ec33e3066e/activerecord/lib/active_record/associations/has_many_through_association.rb#L38

As you can see, in the end, it does call the save method.

Disclaimer: I'm not that familiar with Rails souce code, but you have interesting question.

Source Show
◀ Wstecz