
STI association not accessible on certain reloaded Resources
Reported by Ashley Moran | August 5th, 2009 @ 06:13 AM
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?
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 = Farm.new
@giles = Farmer.new
@farm.people << @giles
end
# Fails with:
# undefined method `cow' for #<DataMapper::Associations::OneToMany::Collection:0x17c65f4>
it "can't save an unaccessed child after reloading" do
@farm.save
with_db_reconnect do
farm_reloaded = reload(@farm)
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
@farm.people << Farmer.new
@farm.save
with_db_reconnect do
farm_reloaded = reload(@farm)
farm_reloaded.people << Farmer.new
farm_reloaded.save
end
end
it "can't be fixed by touching all the children using #each" do
@farm.people << Farmer.new
@farm.save
with_db_reconnect do
farm_reloaded = reload(@farm)
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
@farm.people << Farmer.new
@farm.save
with_db_reconnect do
farm_reloaded = reload(@farm)
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
@farm.save
with_db_reconnect do
farm_reloaded = reload(@farm)
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
@farm.save
with_db_reconnect do
farm_reloaded = reload(@farm)
@jones = Farmer.new
farm_reloaded.people << @jones
@jones.save
end
end
it "works if you save a reloaded child" do
@farm.save
with_db_reconnect do
farm_reloaded = reload(@farm)
@jones = Farmer.new
farm_reloaded.people << @jones
@jones.save
reload(@jones).save
end
end
end
Comments and changes to this ticket
-
Dan Kubb (dkubb) October 7th, 2009 @ 12:43 PM
- Milestone set to 0.10.2
- State changed from new to accepted
- Assigned user set to Dan Kubb (dkubb)
-
Dan Kubb (dkubb) February 1st, 2010 @ 04:33 PM
- Milestone changed from 0.10.2 to 1.0.0
-
-
-
Dan Kubb (dkubb) February 2nd, 2010 @ 02:47 AM
- State changed from accepted to confirmed
Please Sign in or create a free account to add a new ticket.
With your very own profile, you can contribute to projects, track your activity, watch tickets, receive and update tickets through your email and much more.
Create your profile
Help contribute to this project by taking a few moments to create your personal profile. Create your profile »