dm-constraints 0.9.9 → 0.9.10
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +6 -0
- data/Manifest.txt +1 -0
- data/lib/dm-constraints/data_objects_adapter.rb +15 -4
- data/lib/dm-constraints/delete_constraint.rb +59 -0
- data/lib/dm-constraints/version.rb +1 -1
- data/lib/dm-constraints.rb +41 -1
- data/spec/integration/constraints_spec.rb +173 -5
- data/spec/spec_helper.rb +1 -1
- data/tasks/spec.rb +1 -1
- metadata +4 -3
data/History.txt
CHANGED
data/Manifest.txt
CHANGED
@@ -13,7 +13,18 @@ module DataMapper
|
|
13
13
|
foreign_table = parent.storage_name(repository_name)
|
14
14
|
foreign_keys = parent.key.map { |key| property_to_column_name(parent.repository(repository_name), key, false) }
|
15
15
|
|
16
|
-
|
16
|
+
one_to_many_relationship = parent.relationships.values.select { |rel| rel.child_model == model }.first
|
17
|
+
delete_constraint_type = case one_to_many_relationship.delete_constraint
|
18
|
+
when :protect, nil
|
19
|
+
"NO ACTION"
|
20
|
+
when :destroy, :destroy!
|
21
|
+
"CASCADE"
|
22
|
+
when :set_nil
|
23
|
+
"SET NULL"
|
24
|
+
when :skip
|
25
|
+
nil
|
26
|
+
end
|
27
|
+
create_constraints_statement(table_name, constraint_name, keys, foreign_table, foreign_keys, delete_constraint_type) if delete_constraint_type
|
17
28
|
end.compact
|
18
29
|
end
|
19
30
|
|
@@ -29,14 +40,14 @@ module DataMapper
|
|
29
40
|
|
30
41
|
private
|
31
42
|
|
32
|
-
def create_constraints_statement(table_name, constraint_name, keys, foreign_table, foreign_keys)
|
43
|
+
def create_constraints_statement(table_name, constraint_name, keys, foreign_table, foreign_keys, delete_constraint_type)
|
33
44
|
<<-EOS.compress_lines
|
34
45
|
ALTER TABLE #{quote_table_name(table_name)}
|
35
46
|
ADD CONSTRAINT #{quote_constraint_name(constraint_name)}
|
36
47
|
FOREIGN KEY (#{keys * ', '})
|
37
48
|
REFERENCES #{quote_table_name(foreign_table)} (#{foreign_keys * ', '})
|
38
|
-
ON DELETE
|
39
|
-
ON UPDATE
|
49
|
+
ON DELETE #{delete_constraint_type}
|
50
|
+
ON UPDATE #{delete_constraint_type}
|
40
51
|
EOS
|
41
52
|
end
|
42
53
|
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Constraints
|
3
|
+
module DeleteConstraint
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
DELETE_CONSTRAINT_OPTIONS = [:protect, :destroy, :destroy!, :set_nil, :skip]
|
11
|
+
def check_delete_constraint_type(cardinality, name, options = {})
|
12
|
+
constraint_type = options[:constraint]
|
13
|
+
return if constraint_type.nil?
|
14
|
+
delete_constraint_options = DELETE_CONSTRAINT_OPTIONS.map { |o| ":#{o}" }
|
15
|
+
if !DELETE_CONSTRAINT_OPTIONS.include?(constraint_type)
|
16
|
+
raise ArgumentError, ":constraint option must be one of #{delete_constraint_options * ', '}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# TODO: that should be moved to a 'util-like' module
|
21
|
+
def with_changed_method_visibility(method, from_visibility, to_visibility, &block)
|
22
|
+
send(to_visibility, method)
|
23
|
+
yield
|
24
|
+
send(from_visibility, method)
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
def add_delete_constraint_option(name, repository_name, child_model, parent_model, options = {})
|
30
|
+
@delete_constraint = options[:constraint]
|
31
|
+
end
|
32
|
+
|
33
|
+
def check_delete_constraints
|
34
|
+
model.relationships.each do |rel_name, rel|
|
35
|
+
children = self.send(rel_name)
|
36
|
+
case rel.delete_constraint
|
37
|
+
when nil, :protect
|
38
|
+
# only prevent deletion if the resource is a parent in a relationship and has children
|
39
|
+
throw(:halt, false) if children && children.respond_to?(:empty?) && !children.empty?
|
40
|
+
when :destroy
|
41
|
+
if children && children.respond_to?(:each)
|
42
|
+
children.each { |child| child.destroy }
|
43
|
+
end
|
44
|
+
when :set_nil
|
45
|
+
if children && children.respond_to?(:each)
|
46
|
+
children.each do |child|
|
47
|
+
child.class.many_to_one_relationships.each do |mto_rel|
|
48
|
+
child.send("#{mto_rel.name}=", nil) if child.send(mto_rel.name).eql?(self)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end # case
|
53
|
+
end # relationships
|
54
|
+
end # check_delete_constraints
|
55
|
+
|
56
|
+
|
57
|
+
end # DeleteConstraint
|
58
|
+
end # Constraints
|
59
|
+
end # DataMapper
|
data/lib/dm-constraints.rb
CHANGED
@@ -3,15 +3,55 @@ require 'rubygems'
|
|
3
3
|
require 'pathname'
|
4
4
|
|
5
5
|
# Add all external dependencies for the plugin here
|
6
|
-
gem 'dm-core', '~>0.9.
|
6
|
+
gem 'dm-core', '~>0.9.10'
|
7
7
|
require 'dm-core'
|
8
8
|
|
9
9
|
# Require plugin-files
|
10
10
|
require Pathname(__FILE__).dirname.expand_path / 'dm-constraints' / 'data_objects_adapter'
|
11
11
|
require Pathname(__FILE__).dirname.expand_path / 'dm-constraints' / 'postgres_adapter'
|
12
12
|
require Pathname(__FILE__).dirname.expand_path / 'dm-constraints' / 'mysql_adapter'
|
13
|
+
require Pathname(__FILE__).dirname.expand_path / 'dm-constraints' / 'delete_constraint'
|
13
14
|
|
14
15
|
module DataMapper
|
16
|
+
module Associations
|
17
|
+
class Relationship
|
18
|
+
include Extlib::Hook
|
19
|
+
include DataMapper::Constraints::DeleteConstraint
|
20
|
+
|
21
|
+
attr_reader :delete_constraint
|
22
|
+
OPTIONS << :constraint
|
23
|
+
|
24
|
+
# initialize is a private method in Relationship
|
25
|
+
# and private methods can not be "advised" (hooked into)
|
26
|
+
# in extlib.
|
27
|
+
with_changed_method_visibility(:initialize, :private, :public) do
|
28
|
+
before :initialize, :add_delete_constraint_option
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
module DataMapper
|
35
|
+
module Constraints
|
36
|
+
|
37
|
+
include DeleteConstraint
|
38
|
+
|
39
|
+
module ClassMethods
|
40
|
+
include DeleteConstraint::ClassMethods
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.included(model)
|
44
|
+
model.extend(ClassMethods)
|
45
|
+
model.class_eval do
|
46
|
+
before_class_method :has, :check_delete_constraint_type
|
47
|
+
if method_defined?(:destroy)
|
48
|
+
before :destroy, :check_delete_constraints
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
15
55
|
class AutoMigrator
|
16
56
|
include Extlib::Hook
|
17
57
|
include DataMapper::Constraints::DataObjectsAdapter::Migration
|
@@ -5,11 +5,14 @@ ADAPTERS.each do |adapter|
|
|
5
5
|
|
6
6
|
describe 'DataMapper::Constraints' do
|
7
7
|
|
8
|
+
# load_models_for_metaphor :stable, :farmer, :cow
|
9
|
+
|
8
10
|
before do
|
9
11
|
DataMapper::Repository.adapters[:default] = DataMapper::Repository.adapters[adapter]
|
10
12
|
|
11
|
-
class Stable
|
13
|
+
class ::Stable
|
12
14
|
include DataMapper::Resource
|
15
|
+
include DataMapper::Constraints
|
13
16
|
|
14
17
|
property :id, Serial
|
15
18
|
property :location, String
|
@@ -18,8 +21,9 @@ ADAPTERS.each do |adapter|
|
|
18
21
|
has n, :cows
|
19
22
|
end
|
20
23
|
|
21
|
-
class Farmer
|
24
|
+
class ::Farmer
|
22
25
|
include DataMapper::Resource
|
26
|
+
include DataMapper::Constraints
|
23
27
|
|
24
28
|
property :first_name, String, :key => true
|
25
29
|
property :last_name, String, :key => true
|
@@ -27,7 +31,7 @@ ADAPTERS.each do |adapter|
|
|
27
31
|
has n, :cows
|
28
32
|
end
|
29
33
|
|
30
|
-
class Cow
|
34
|
+
class ::Cow
|
31
35
|
include DataMapper::Resource
|
32
36
|
include DataMapper::Constraints
|
33
37
|
|
@@ -61,5 +65,169 @@ ADAPTERS.each do |adapter|
|
|
61
65
|
lambda { @c1 = Cow.create(:name => "Bea", :stable_id => s.id + 1) }.should raise_error
|
62
66
|
end
|
63
67
|
|
64
|
-
|
65
|
-
|
68
|
+
# :constraint associations
|
69
|
+
# value | on deletion of parent...
|
70
|
+
# ---------------------------------
|
71
|
+
# :protect | raises exception if there are child records
|
72
|
+
# :destroy | deletes children
|
73
|
+
# :destroy! | deletes children directly without instantiating the resource, bypassing any hooks
|
74
|
+
# :set_nil | sets parent id to nil in child associations
|
75
|
+
# :skip | does not do anything with children (they'll become orphan records)
|
76
|
+
|
77
|
+
describe "constraint options" do
|
78
|
+
describe "when no constraint options are given" do
|
79
|
+
|
80
|
+
it "should destroy the parent if there are no children in the association" do
|
81
|
+
@f1 = Farmer.create(:first_name => "John", :last_name => "Doe")
|
82
|
+
@f2 = Farmer.create(:first_name => "Some", :last_name => "Body")
|
83
|
+
@c1 = Cow.create(:name => "Bea", :farmer => @f2)
|
84
|
+
@f1.destroy.should == true
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should not destroy the parent if there are children in the association" do
|
88
|
+
@f = Farmer.create(:first_name => "John", :last_name => "Doe")
|
89
|
+
@c1 = Cow.create(:name => "Bea", :farmer => @f)
|
90
|
+
@f.destroy.should == false
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
describe "when :constraint => :protect is given" do
|
96
|
+
before do
|
97
|
+
class ::Farmer
|
98
|
+
has n, :cows, :constraint => :protect
|
99
|
+
end
|
100
|
+
class ::Cow
|
101
|
+
belongs_to :farmer
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
it "should destroy the parent if there are no children in the association" do
|
106
|
+
@f1 = Farmer.create(:first_name => "John", :last_name => "Doe")
|
107
|
+
@f2 = Farmer.create(:first_name => "Some", :last_name => "Body")
|
108
|
+
@c1 = Cow.create(:name => "Bea", :farmer => @f2)
|
109
|
+
@f1.destroy.should == true
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should not destroy the parent if there are children in the association" do
|
113
|
+
@f = Farmer.create(:first_name => "John", :last_name => "Doe")
|
114
|
+
@c1 = Cow.create(:name => "Bea", :farmer => @f)
|
115
|
+
@f.destroy.should == false
|
116
|
+
end
|
117
|
+
|
118
|
+
it "the child should be destroyable" do
|
119
|
+
@f = Farmer.create(:first_name => "John", :last_name => "Doe")
|
120
|
+
@c = Cow.create(:name => "Bea", :farmer => @f)
|
121
|
+
@c.destroy.should == true
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
|
126
|
+
describe "when :constraint => :destroy is given" do
|
127
|
+
before do
|
128
|
+
class ::Farmer
|
129
|
+
has n, :cows, :constraint => :destroy
|
130
|
+
end
|
131
|
+
class ::Cow
|
132
|
+
belongs_to :farmer
|
133
|
+
end
|
134
|
+
DataMapper.auto_migrate!
|
135
|
+
end
|
136
|
+
|
137
|
+
it "should destroy the parent and the children, too" do
|
138
|
+
#NOTE: the repository wrapper is needed in order for
|
139
|
+
# the identity map to work (otherwise @c1 in the below two calls
|
140
|
+
# would refer to different instances)
|
141
|
+
repository do
|
142
|
+
@f = Farmer.create(:first_name => "John", :last_name => "Doe")
|
143
|
+
@c1 = Cow.create(:name => "Bea", :farmer => @f)
|
144
|
+
@c2 = Cow.create(:name => "Riksa", :farmer => @f)
|
145
|
+
@f.destroy.should == true
|
146
|
+
@f.should be_new_record
|
147
|
+
@c1.should be_new_record
|
148
|
+
@c2.should be_new_record
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
it "the child should be destroyable" do
|
153
|
+
@f = Farmer.create(:first_name => "John", :last_name => "Doe")
|
154
|
+
@c = Cow.create(:name => "Bea", :farmer => @f)
|
155
|
+
@c.destroy.should == true
|
156
|
+
end
|
157
|
+
|
158
|
+
end
|
159
|
+
|
160
|
+
describe "when :constraint => :set_nil is given" do
|
161
|
+
before do
|
162
|
+
class ::Farmer
|
163
|
+
has n, :cows, :constraint => :set_nil
|
164
|
+
end
|
165
|
+
class ::Cow
|
166
|
+
belongs_to :farmer
|
167
|
+
end
|
168
|
+
DataMapper.auto_migrate!
|
169
|
+
end
|
170
|
+
|
171
|
+
it "destroying the parent should set children foreign keys to nil" do
|
172
|
+
@f = Farmer.create(:first_name => "John", :last_name => "Doe")
|
173
|
+
@c1 = Cow.create(:name => "Bea", :farmer => @f)
|
174
|
+
@c2 = Cow.create(:name => "Riksa", :farmer => @f)
|
175
|
+
cows = @f.cows
|
176
|
+
@f.destroy.should == true
|
177
|
+
cows.all? { |cow| cow.farmer.should be_nil }
|
178
|
+
end
|
179
|
+
|
180
|
+
it "the child should be destroyable" do
|
181
|
+
@f = Farmer.create(:first_name => "John", :last_name => "Doe")
|
182
|
+
@c = Cow.create(:name => "Bea", :farmer => @f)
|
183
|
+
@c.destroy.should == true
|
184
|
+
end
|
185
|
+
|
186
|
+
end # describe
|
187
|
+
|
188
|
+
describe "when :constraint => :skip is given" do
|
189
|
+
before do
|
190
|
+
class ::Farmer
|
191
|
+
has n, :cows, :constraint => :skip
|
192
|
+
end
|
193
|
+
class ::Cow
|
194
|
+
belongs_to :farmer
|
195
|
+
end
|
196
|
+
DataMapper.auto_migrate!
|
197
|
+
end
|
198
|
+
|
199
|
+
it "destroying the parent should be allowed, children should become orphan records" do
|
200
|
+
@f = Farmer.create(:first_name => "John", :last_name => "Doe")
|
201
|
+
@c1 = Cow.create(:name => "Bea", :farmer => @f)
|
202
|
+
@c2 = Cow.create(:name => "Riksa", :farmer => @f)
|
203
|
+
@f.destroy.should == true
|
204
|
+
@c1.farmer.should be_new_record
|
205
|
+
@c2.farmer.should be_new_record
|
206
|
+
end
|
207
|
+
|
208
|
+
it "the child should be destroyable" do
|
209
|
+
@f = Farmer.create(:first_name => "John", :last_name => "Doe")
|
210
|
+
@c = Cow.create(:name => "Bea", :farmer => @f)
|
211
|
+
@c.destroy.should == true
|
212
|
+
end
|
213
|
+
|
214
|
+
end # describe
|
215
|
+
|
216
|
+
describe "when an invalid option is given" do
|
217
|
+
before do
|
218
|
+
end
|
219
|
+
|
220
|
+
it "should raise an error" do
|
221
|
+
lambda do
|
222
|
+
class ::Farmer
|
223
|
+
has n, :cows, :constraint => :chocolate
|
224
|
+
end
|
225
|
+
end.should raise_error(ArgumentError)
|
226
|
+
end
|
227
|
+
|
228
|
+
end
|
229
|
+
|
230
|
+
end # describe 'constraint options'
|
231
|
+
|
232
|
+
end # DataMapper::Constraints
|
233
|
+
end # ADAPTERS.each
|
data/spec/spec_helper.rb
CHANGED
data/tasks/spec.rb
CHANGED
@@ -8,7 +8,7 @@ begin
|
|
8
8
|
desc 'Run specifications'
|
9
9
|
Spec::Rake::SpecTask.new(:spec) do |t|
|
10
10
|
t.spec_opts << '--options' << 'spec/spec.opts' if File.exists?('spec/spec.opts')
|
11
|
-
t.spec_files = Pathname.glob((ROOT + 'spec/**/*_spec.rb').to_s)
|
11
|
+
t.spec_files = Pathname.glob((ROOT + 'spec/**/*_spec.rb').to_s).map { |f| f.to_s }
|
12
12
|
|
13
13
|
begin
|
14
14
|
gem 'rcov', '~>0.8'
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dm-constraints
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.9.
|
4
|
+
version: 0.9.10
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dirkjan Bussink
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-01-
|
12
|
+
date: 2009-01-19 00:00:00 -08:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -20,7 +20,7 @@ dependencies:
|
|
20
20
|
requirements:
|
21
21
|
- - ~>
|
22
22
|
- !ruby/object:Gem::Version
|
23
|
-
version: 0.9.
|
23
|
+
version: 0.9.10
|
24
24
|
version:
|
25
25
|
description: DataMapper plugin constraining relationships
|
26
26
|
email:
|
@@ -43,6 +43,7 @@ files:
|
|
43
43
|
- TODO
|
44
44
|
- lib/dm-constraints.rb
|
45
45
|
- lib/dm-constraints/data_objects_adapter.rb
|
46
|
+
- lib/dm-constraints/delete_constraint.rb
|
46
47
|
- lib/dm-constraints/mysql_adapter.rb
|
47
48
|
- lib/dm-constraints/postgres_adapter.rb
|
48
49
|
- lib/dm-constraints/version.rb
|