I have three associated classes in Rails v. 3.2.15, with Ruby 2.1.1, and a join-table class between two of them:
class Grandarent < ActiveRecord::Base
has_many :parents, autosave: true
end
class Parent
belongs_to :grandparent
has_many :children, :through => :parent_children, autosave: true
end
class ParentChild
belongs_to :parent
belongs_to :child
end
class Child
has_many :parent_children
has_many :parents, :through => :parent_children
end
If I execute the following, then changes to child are not saved:
gp = Grandparent.find(1)
gp.parents.first.children.first.first_name = "Bob"
gp.save
gp.parents.first.children.first.first_name ## -> Whatever name was to begin with (i.e. NOT Bob)
But if I force Rails to evaluate and return data from each connection, then the save is successful
gp = Grandparent.find(1)
gp.parents
gp.parents.first
gp.parents.first.children
gp.parents.first.children.first
gp.parents.first.children.first.first_name = "Bob"
gp.save
gp.parents.first.children.first.first_name ## -> "Bob"
If I subsequently execute gp = Grandparent.find(1) again, then I've reset the whole thing, and have to force the evaluation of associations again.
Is this intentional behavior, or have I done something wrong? Do I need to hang an autosave on the join table connections as well as (or instead of) the has_many :through connection?
From the documentation, I see that "loaded" members will be saved. Is this what is necessary to load them? Can someone define exactly what "loaded" is, and how to achieve that state?
It is happening because gp.parents
caches the parents
into a results Array
, then parents.first
is actually calling Array.first
. However, gp.parents.first
performs a query with LIMIT 1
every time, and so returns a new object every time.
You can confirm like so:
gp.parents.first.object_id # performs new query (LIMIT 1)
=> 1
gp.parents.first.object_id # performs new query (LIMIT 1)
=> 2
gp.parents # performs and caches query for parents
gp.parents.first.object_id # returns first result from parents array
=> 1
gp.parents.first.object_id # returns first result from parents array
=> 1
You can chain an update with your query like so:
gp.parents.first.children.first.update_attributes(first_name: "Bob")