0

This is in Ruby 1.9.3p194, with Rails 3.2.8

app/models/owner.rb:

class Owner < ActiveRecord::Base
  attr_accessible :name
  has_many :pets
end

app/models/pet.rb:

class Pet < ActiveRecord::Base
  attr_accessible :name, :owner
  belongs_to :owner
end

db/migrate/20120829184126_create_owners_and_pets.rb:

class CreateOwners < ActiveRecord::Migration
  def change
    create_table :owners do |t|
      t.string :name
      t.timestamps
    end
    create_table :pets do |t|
      t.string :name
      t.integer :owner_id
      t.timestamps
    end
  end
end

Alright, now watch what happens...

# rake db:migrate
# rails console
irb> shaggy = Owner.create(:name => 'Shaggy')
irb> shaggy.pets.build(:name => 'Scooby Doo')
irb> shaggy.pets.build(:name => 'Scrappy Doo')
irb> shaggy.object_id
  => 70262210740820
irb> shaggy.pets.map{|p| p.owner.object_id}
  => [70262210740820, 70262210740820]
irb> shaggy.name = 'Shaggie'
irb> shaggy.name
  => "Shaggie"
irb> shaggy.pets.map{|p| p.owner.name}
  => ["Shaggie", "Shaggie"]
irb> shaggy.save
irb> shaggy.reload
irb> shaggy.object_id
  => 70262210740820
irb> shaggy.pets.map{|p| p.owner.object_id}
  => [70262211070840, 70262211079640]
irb> shaggy.name = "Fred"
irb> shaggy.name
  => "Fred"
irb> shaggy.pets.map{|p| p.ower.name}
  => ["Shaggie", "Shaggie"]

My question: How can I get rails to initialize the elements of shaggy.pets to have their owners set to shaggy (the exact object), not only when the pet objects are first built/created, but even when they are auto-loaded from the database via the association?

Bonus points: Make it work in Rails 2.3.5 as well.

Chris
  • 632
  • 4
  • 11
  • 18

3 Answers3

1

If you don't care about 2.3.5 support, the following is much simpler:

app/models/owner.rb:

class Owner < ActiveRecord::Base
  attr_accessible :name
  has_many :pets, :inverse_of => :owner
end

app/models/pet.rb:

class Pet < ActiveRecord::Base
  attr_accessible :name, :owner
  belongs_to :owner, :inverse_of => :pets
end
Chris
  • 632
  • 4
  • 11
  • 18
0

Chris,

What you are missing is this;

  • When you query the database, you take a snapshot of the data in that exact moment.
  • Your associations do not point to the object reference, they are a shallow copy of the object queried from the database.
  • To make your code works, you need to save the object back to the database, so pets will appropriately fetch the updates.

I would also show more code, because I can clearly see you taking the wrong direction in what you're doing.

What you are showing is a desktop application pattern example, using patterns designed for web applications.

Rodrigo Dellacqua
  • 268
  • 1
  • 3
  • 12
0

Figured it out. This works for me in Rails 3.2.8 and Rails 2.3.5:

class Owner < ActiveRecord::Base
  attr_accessible :name
  has_many :pets do
    def load_target
      super.map do |pet|
        pet.owner = (proxy_association.owner rescue proxy_owner)
        pet
      end
    end
  end
end

Note that in Rails 2.3.5, the object_ids of shaggy.pets.map{|p| p.owner} are still different, since they are different ProxyAssociation objects, but they at least point at the same underlying object.

If you do this often, you may want to generalize...

lib/remember_parent_extension.rb:

class RememberParentExtension < Module
  def initialize(parent_name)
    parent_setter = "#{parent_name}="
    super() do
      define_method(:load_target) do
        super.map do |child|
          parent_proxy = (proxy_association.owner rescue proxy_owner)
          child.send(parent_setter, parent_proxy)
          child
        end
      end
    end
  end
end

app/models/owner.rb:

class Owner < ActiveRecord::Base
  attr_accessible :name
  has_many :pets, :extend => RememberParentExtension.new(:owner)
end
Chris
  • 632
  • 4
  • 11
  • 18