This appears to be a hybrid of #960 and #1001. I don't understand it, but I can explain the behaviour we have seen.
In a GrandParent-Parent-Child relationship, when using a subclass of the Parent class, if you reload the Parent and try to save it implicitly via the GrandParent, inside a save call - and only inside a save call (we are using a hook) - you get the association Collection back instead of the Resource itself.
It can be fixed by explicitly accessing the failing Child before the save. (Although you must touch all children, and bizarrely you can't use #each to do so Equally bizarrely, it can also be fixed by moving the association definition to the Parent superclass.
Any ideas?
ruby
require 'rubygems'
USE_DM_0_9 = false
if USE_DM_0_9
DM_GEMS_VERSION = "0.9.11"
DO_GEMS_VERSION = "0.9.12"
else
DM_GEMS_VERSION = "0.10.0"
DO_GEMS_VERSION = "0.10.0"
end
gem "data_objects", DO_GEMS_VERSION
gem "do_sqlite3", DO_GEMS_VERSION # If using another database, replace this
gem "dm-core", DM_GEMS_VERSION
gem "dm-types", DM_GEMS_VERSION
gem "dm-validations", DM_GEMS_VERSION
require "data_objects"
require "dm-core"
require "dm-types"
require "dm-validations"
require 'spec'
SQLITE_FILE = File.join(`pwd`.chomp, "test.db")
DataMapper.setup(:default, "sqlite3:#{SQLITE_FILE}")
DataMapper.setup(:reloaded, "sqlite3:#{SQLITE_FILE}")
module DataMapper
module Validate
extend Chainable
# NOTE: this is different to the current next branch, as of writing:
# http://github.com/datamapper/dm-more/blob/0c58330f1ee3b27978fec1e3331c1b68417a738a/dm-validations/lib/dm-validations.rb
def save(context = :default)
return false unless save_people && (context.nil? || valid?(context))
save_self && save_children
end
end
module Resource
def save_people
parent_relationships.all? do |relationship|
parent = relationship.get!(self)
if parent.new? || parent.dirty?
next unless parent.save_people && parent.save_self
end
relationship.set(self, parent) # set the FK values
end
end
end
end
class Farm
include DataMapper::Resource
after :save, :dm_bug_save_children
property :id, Serial
has n, :people
def dm_bug_save_children
people.each { |p| p.save }
end
end
class Person
include DataMapper::Resource
property :id, Serial
property :type, Discriminator
# has 1, :cow # Also fixes the first example
belongs_to :farm
end
class Farmer < Person
after :save, :save_cow
has 1, :cow # Move to Person to fix
def initialize(*)
super
self.cow = Cow.new
end
def save_cow
cow.save
end
end
class Cow
include DataMapper::Resource
property :id, Serial
belongs_to :farmer, :child_key => [ :person_id ]
end
module IdentityMapHelper
def reload(object)
object.class.get(object.id)
end
def with_db_reconnect(&blk)
original_repository = DataMapper::Repository.context.pop
repository(:reloaded, &blk)
DataMapper::Repository.context << original_repository
end
end
Spec::Runner.configure do |config|
include IdentityMapHelper
config.before(:each) do
Farm.auto_migrate!
Person.auto_migrate!
Cow.auto_migrate!
end
config.before(:each) do
DataMapper::Repository.context << repository(:default)
end
config.after(:each) do
DataMapper::Repository.context.pop
end
end
describe "STI GrandParent (1), Parent (n), Child (1)" do
before(:each) do
= Farm.new
= Farmer.new
.people <<
end
# Fails with:
# undefined method `cow' for #<:associations::onetomany::collection:0x17c65f4>
it "can't save an unaccessed child after reloading" do
.save
with_db_reconnect do
farm_reloaded = reload()
farm_reloaded.people << Farmer.new # Necessary to cause the breakage
farm_reloaded.save
end
end
it "fails if you have >= 2 resources in the association before the initial save" do
.people << Farmer.new
.save
with_db_reconnect do
farm_reloaded = reload()
farm_reloaded.people << Farmer.new
farm_reloaded.save
end
end
it "can't be fixed by touching all the children using #each" do
.people << Farmer.new
.save
with_db_reconnect do
farm_reloaded = reload()
farm_reloaded.people << Farmer.new
farm_reloaded.people.each { |person| puts "MOO"; person.cow } # Fails here, doesn't fix it
farm_reloaded.save
end
end
it "can be fixed by touching all children using #[]" do
.people << Farmer.new
.save
with_db_reconnect do
farm_reloaded = reload()
farm_reloaded.people << Farmer.new
farm_reloaded.people[0].cow # Fixes it
farm_reloaded.people[1].cow # Fixes it
farm_reloaded.save
end
end
it "can be fixed if you have one child and use #first" do
.save
with_db_reconnect do
farm_reloaded = reload()
farm_reloaded.people << Farmer.new
farm_reloaded.people.first.cow # Fixes it
farm_reloaded.save
end
end
it "works if you save a new object" do
.save
with_db_reconnect do
farm_reloaded = reload()
= Farmer.new
farm_reloaded.people <<
.save
end
end
it "works if you save a reloaded child" do
.save
with_db_reconnect do
farm_reloaded = reload()
= Farmer.new
farm_reloaded.people <<
.save
reload().save
end
end
end
</:associations::onetomany::collection:0x17c65f4>
Created by Ashley Moran - 2009-08-05 11:13:13 UTC
Original Lighthouse ticket: http://datamapper.lighthouseapp.com/projects/20609/tickets/1002
该提问来源于开源项目:datamapper/dm-core