weixin_39782500 2020-11-22 01:58
浏览 0

STI association not accessible on certain reloaded Resources

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

  • 写回答

6条回答 默认 最新

  • weixin_39782500 2020-11-22 01:58
    关注

    According to our DataMapper bug canary specs, this is fixed in 0.10RC2.

    by Ashley Moran

    评论

报告相同问题?