Skip to content

Instantly share code, notes, and snippets.

@slaskis
Created May 23, 2011 16:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save slaskis/986945 to your computer and use it in GitHub Desktop.
Save slaskis/986945 to your computer and use it in GitHub Desktop.
An example of how DataMapper fails to destroy a model that has a many-to-many relation to the same class (as demonstrated in the docs)
require "rubygems"
require "datamapper"
DataMapper::Logger.new(STDOUT,:debug)
DataMapper.setup(:default, "sqlite3::memory:")
#DataMapper.setup(:default, "postgres://localhost/dm-test")
class Entry
include DataMapper::Resource
default_scope.update( :order => [ :name.asc ] )
property :id, Serial
property :name, String, :required => true
has 1, :related, :model => Entry, :constraint => :destroy
belongs_to :related, Entry, :required => false
belongs_to :collection
before :destroy do
puts "Destroying Entry (#{name})"
end
end
class Collection
include DataMapper::Resource
default_scope.update( :order => [ :name.asc ] )
property :id, Serial
property :name, String, :required => true
has n, :entries, :constraint => :destroy
belongs_to :person
before :destroy do
puts "Destroying Collection (#{name})"
end
end
class Person
include DataMapper::Resource
default_scope.update( :order => [ :name.asc ] )
property :id, Serial
property :name, String, :length => 0...1024, :default => ""
has n, :collections, :constraint => :destroy
has n, :connections, :child_key => [:source_id]
has n, :related, self, :through => :connections, :via => :target
before :destroy do
puts "Destroying Person (#{name})"
end
end
############################################
# Connection between two people, usually the
# relation between an Agent and an Artist.
############################################
class Connection
include DataMapper::Resource
belongs_to :source, 'Person', :key => true
belongs_to :target, 'Person', :key => true
end
DataMapper.finalize
DataMapper.auto_migrate!
p1 = Person.create(:name => "A")
p1.collections.create(:name => "A1", :entries => [{:name => "A1E1"},{:name => "A1E2"},{:name => "A1E3"},{:name => "A1E4"}])
p1.collections.create(:name => "A2", :entries => [{:name => "A2E1"},{:name => "A2E2"},{:name => "A2E3"},{:name => "A2E4"}])
p1.collections.create(:name => "A3", :entries => [{:name => "A3E1"},{:name => "A3E2"},{:name => "A3E3"},{:name => "A3E4"}])
p1.collections.create(:name => "A4", :entries => [{:name => "A4E1"},{:name => "A4E2"},{:name => "A4E3"},{:name => "A4E4"}])
p2 = Person.create(:name => "B")
p2.collections.create(:name => "B1", :entries => [{:name => "B1E1"},{:name => "B1E2"},{:name => "B1E3"},{:name => "B1E4"}])
p2.collections.create(:name => "B2", :entries => [{:name => "B2E1"},{:name => "B2E2"},{:name => "B2E3"},{:name => "B2E4"}])
p2.collections.create(:name => "B3", :entries => [{:name => "B3E1"},{:name => "B3E2"},{:name => "B3E3"},{:name => "B3E4"}])
p2.collections.create(:name => "B4", :entries => [{:name => "B4E1"},{:name => "B4E2"},{:name => "B4E3"},{:name => "B4E4"}])
# Connecting the two will make #destroy fail
p2.related << p1
p2.save
# 1. Clearing it first will make it destroyable...
#p2.related.clear
#p2.save # Saving is important!
# 2. Or reloading it (when it hasn't been persisted)
#p2.reload
# All Entry and Collection that's related to p2 is being destroyed, but not p2 itself (and the destroy hook is never fired)
p "Was destroyed?", p2.destroy # => true if either 1 or 2 is commented out, otherwise false
puts DataMapper::VERSION # => 1.1.0
@slaskis
Copy link
Author

slaskis commented May 23, 2011

I found that if Person.related and Person.connections gets a :constaints => :skip it works, and then if I set related.clear; save in the before destroy hook it's the way I want it to be.

In other word I believe that's where the destroy chain silently fails in many-to-many associations...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment