dm-constraints 0.9.10 → 0.9.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History.txt +10 -0
- data/README.txt +48 -0
- data/Rakefile +1 -1
- data/lib/dm-constraints/data_objects_adapter.rb +96 -2
- data/lib/dm-constraints/delete_constraint.rb +147 -20
- data/lib/dm-constraints/mysql_adapter.rb +22 -2
- data/lib/dm-constraints/postgres_adapter.rb +12 -2
- data/lib/dm-constraints/version.rb +1 -1
- data/lib/dm-constraints.rb +24 -1
- data/spec/integration/constraints_spec.rb +400 -67
- data/spec/spec_helper.rb +2 -2
- data/tasks/spec.rb +1 -1
- metadata +4 -4
data/History.txt
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
=== 0.9.11 / 2009-03-29
|
2
|
+
|
3
|
+
* 5 major enhancements:
|
4
|
+
|
5
|
+
* Added :destroy! constraints
|
6
|
+
* Added support for 1:1 constraints
|
7
|
+
* Added support for M:M constraints
|
8
|
+
* Add'l rspecs
|
9
|
+
* Updated readme.txt
|
10
|
+
|
1
11
|
=== 0.9.10 / 2009-01-19
|
2
12
|
|
3
13
|
* 1 major enhancement:
|
data/README.txt
CHANGED
@@ -2,3 +2,51 @@
|
|
2
2
|
|
3
3
|
Plugin that adds foreign key constraints to associations.
|
4
4
|
Currently supports only PostgreSQL and MySQL
|
5
|
+
|
6
|
+
All constraints are added to the underlying database, but constraining is implemented in
|
7
|
+
pure ruby.
|
8
|
+
|
9
|
+
|
10
|
+
=== Constraints
|
11
|
+
|
12
|
+
- :protect returns false on destroy if there are child records
|
13
|
+
- :destroy deletes children if present
|
14
|
+
- :destroy! deletes children directly without instantiating the resource, bypassing any hooks
|
15
|
+
Does not support 1:1 Relationships as #destroy! is not supported on Resource in dm-master
|
16
|
+
- :set_nil sets parent id to nil in child associations
|
17
|
+
Not valid for M:M relationships as duplicate records could be created (see explanation in specs)
|
18
|
+
- :skip Does nothing with children, results in orphaned records
|
19
|
+
|
20
|
+
By default a relationship will PROTECT its children.
|
21
|
+
|
22
|
+
|
23
|
+
=== Cardinality Notes
|
24
|
+
* 1:1
|
25
|
+
* Applicable constraints: [:set_nil, :skip, :protect, :destroy]
|
26
|
+
|
27
|
+
* 1:M
|
28
|
+
* Applicable constraints: [:set_nil, :skip, :protect, :destroy, :destroy!]
|
29
|
+
|
30
|
+
* M:M
|
31
|
+
* Applicable constraints: [:skip, :protect, :destroy, :destroy!]
|
32
|
+
|
33
|
+
|
34
|
+
=== Examples
|
35
|
+
|
36
|
+
# 1:M Example
|
37
|
+
class Farmer
|
38
|
+
has n, :pigs #equivalent to: has n, :pigs, :constraint => :protect
|
39
|
+
end
|
40
|
+
|
41
|
+
# M:M Example
|
42
|
+
class Articles
|
43
|
+
has n, :tags, :through => Resource, :constraint => :destroy
|
44
|
+
end
|
45
|
+
class Tags
|
46
|
+
has n, :articles, :through => Resource, :constraint => :destroy
|
47
|
+
end
|
48
|
+
|
49
|
+
# 1:1 Example
|
50
|
+
class Farmer
|
51
|
+
has 1, :beloved_sheep, :constraint => :protect
|
52
|
+
end
|
data/Rakefile
CHANGED
@@ -12,7 +12,7 @@ AUTHOR = 'Dirkjan Bussink'
|
|
12
12
|
EMAIL = 'd.bussink [a] gmail [d] com'
|
13
13
|
GEM_NAME = 'dm-constraints'
|
14
14
|
GEM_VERSION = DataMapper::Constraints::VERSION
|
15
|
-
GEM_DEPENDENCIES = [['dm-core',
|
15
|
+
GEM_DEPENDENCIES = [['dm-core', GEM_VERSION]]
|
16
16
|
GEM_CLEAN = %w[ log pkg coverage ]
|
17
17
|
GEM_EXTRAS = { :has_rdoc => true, :extra_rdoc_files => %w[ README.txt LICENSE TODO History.txt ] }
|
18
18
|
|
@@ -2,8 +2,26 @@ module DataMapper
|
|
2
2
|
module Constraints
|
3
3
|
module DataObjectsAdapter
|
4
4
|
module SQL
|
5
|
+
|
6
|
+
##
|
7
|
+
# generates all foreign key create constraint statements for valid relationships
|
8
|
+
# given repository and a model
|
9
|
+
#
|
10
|
+
# This wraps calls to create_constraints_statement
|
11
|
+
#
|
12
|
+
# @see #create_constraints_statement
|
13
|
+
#
|
14
|
+
# @param repository_name [Symbol] Name of the repository to constrain
|
15
|
+
#
|
16
|
+
# @param model [DataMapper::Model] Model to constrain
|
17
|
+
#
|
18
|
+
# @return [Array[String]] List of statements to create constraints
|
19
|
+
#
|
20
|
+
#
|
21
|
+
# @api public
|
5
22
|
def create_constraints_statements(repository_name, model)
|
6
23
|
model.many_to_one_relationships.map do |relationship|
|
24
|
+
|
7
25
|
table_name = model.storage_name(repository_name)
|
8
26
|
constraint_name = constraint_name(table_name, relationship.name)
|
9
27
|
next if constraint_exists?(table_name, constraint_name)
|
@@ -13,8 +31,16 @@ module DataMapper
|
|
13
31
|
foreign_table = parent.storage_name(repository_name)
|
14
32
|
foreign_keys = parent.key.map { |key| property_to_column_name(parent.repository(repository_name), key, false) }
|
15
33
|
|
16
|
-
|
17
|
-
|
34
|
+
#Anonymous relationshps for :through => Resource
|
35
|
+
one_to_many_relationship = parent.relationships.values.select { |rel|
|
36
|
+
rel.options[:near_relationship_name] == Extlib::Inflection.tableize(model.name).to_sym
|
37
|
+
}.first
|
38
|
+
|
39
|
+
one_to_many_relationship ||= parent.relationships.values.select { |rel|
|
40
|
+
rel.child_model == model
|
41
|
+
}.first
|
42
|
+
|
43
|
+
delete_constraint_type = case one_to_many_relationship.nil? ? :protect : one_to_many_relationship.delete_constraint
|
18
44
|
when :protect, nil
|
19
45
|
"NO ACTION"
|
20
46
|
when :destroy, :destroy!
|
@@ -24,10 +50,27 @@ module DataMapper
|
|
24
50
|
when :skip
|
25
51
|
nil
|
26
52
|
end
|
53
|
+
|
27
54
|
create_constraints_statement(table_name, constraint_name, keys, foreign_table, foreign_keys, delete_constraint_type) if delete_constraint_type
|
28
55
|
end.compact
|
29
56
|
end
|
30
57
|
|
58
|
+
##
|
59
|
+
# generates all foreign key destroy constraint statements for valid relationships
|
60
|
+
# given repository and a model
|
61
|
+
#
|
62
|
+
# This wraps calls to destroy_constraints_statement
|
63
|
+
#
|
64
|
+
# @see #destroy_constraints_statement
|
65
|
+
#
|
66
|
+
# @param repository_name [Symbol] Name of the repository to constrain
|
67
|
+
#
|
68
|
+
# @param model [DataMapper::Model] Model to constrain
|
69
|
+
#
|
70
|
+
# @return [Array[String]] List of statements to destroy constraints
|
71
|
+
#
|
72
|
+
#
|
73
|
+
# @api public
|
31
74
|
def destroy_constraints_statements(repository_name, model)
|
32
75
|
model.many_to_one_relationships.map do |relationship|
|
33
76
|
table_name = model.storage_name(repository_name)
|
@@ -35,11 +78,30 @@ module DataMapper
|
|
35
78
|
next unless constraint_exists?(table_name, constraint_name)
|
36
79
|
|
37
80
|
destroy_constraints_statement(table_name, constraint_name)
|
81
|
+
|
38
82
|
end.compact
|
39
83
|
end
|
40
84
|
|
41
85
|
private
|
42
86
|
|
87
|
+
##
|
88
|
+
# Generates the SQL statement to create a constraint
|
89
|
+
#
|
90
|
+
# @param table_name [String] name of table to constrain
|
91
|
+
#
|
92
|
+
# @param constraint_name [String] name of foreign key constraint
|
93
|
+
#
|
94
|
+
# @param keys [Array[String]] keys that refer to another table
|
95
|
+
#
|
96
|
+
# @param foreign_table [String] table fk refers to
|
97
|
+
#
|
98
|
+
# @param foreign_keys [Array[String]] keys on foreign table that constraint refers to
|
99
|
+
#
|
100
|
+
# @param delete_constraint_type [String] the constraint to add to the table
|
101
|
+
#
|
102
|
+
# @return [String] SQL DDL Statement to create a constraint
|
103
|
+
#
|
104
|
+
# @api private
|
43
105
|
def create_constraints_statement(table_name, constraint_name, keys, foreign_table, foreign_keys, delete_constraint_type)
|
44
106
|
<<-EOS.compress_lines
|
45
107
|
ALTER TABLE #{quote_table_name(table_name)}
|
@@ -51,6 +113,16 @@ module DataMapper
|
|
51
113
|
EOS
|
52
114
|
end
|
53
115
|
|
116
|
+
##
|
117
|
+
# Generates the SQL statement to destroy a constraint
|
118
|
+
#
|
119
|
+
# @param table_name [String] name of table to constrain
|
120
|
+
#
|
121
|
+
# @param constraint_name [String] name of foreign key constraint
|
122
|
+
#
|
123
|
+
# @return [String] SQL DDL Statement to destroy a constraint
|
124
|
+
#
|
125
|
+
# @api private
|
54
126
|
def destroy_constraints_statement(table_name, constraint_name)
|
55
127
|
<<-EOS.compress_lines
|
56
128
|
ALTER TABLE #{quote_table_name(table_name)}
|
@@ -58,10 +130,30 @@ module DataMapper
|
|
58
130
|
EOS
|
59
131
|
end
|
60
132
|
|
133
|
+
##
|
134
|
+
# generates a unique constraint name given a table and a relationships
|
135
|
+
#
|
136
|
+
# @param table_name [String] name of table to constrain
|
137
|
+
#
|
138
|
+
# @param relationships_name [String] name of the relationship to constrain
|
139
|
+
#
|
140
|
+
# @return [String] name of the constraint
|
141
|
+
#
|
142
|
+
# @api private
|
61
143
|
def constraint_name(table_name, relationship_name)
|
62
144
|
"#{table_name}_#{relationship_name}_fk"
|
63
145
|
end
|
64
146
|
|
147
|
+
##
|
148
|
+
# SQL quotes a foreign key constraint name
|
149
|
+
#
|
150
|
+
# @see #quote_table_name
|
151
|
+
#
|
152
|
+
# @param foreign_key [String] SQL quotes a foreign key name
|
153
|
+
#
|
154
|
+
# @return [String] quoted constraint name
|
155
|
+
#
|
156
|
+
# @api private
|
65
157
|
def quote_constraint_name(foreign_key)
|
66
158
|
quote_table_name(foreign_key)
|
67
159
|
end
|
@@ -78,6 +170,7 @@ module DataMapper
|
|
78
170
|
def auto_migrate_constraints_down(repository_name, *descendants)
|
79
171
|
descendants = DataMapper::Resource.descendants.to_a if descendants.empty?
|
80
172
|
descendants.each do |model|
|
173
|
+
repository_name ||= model.repository(repository_name).name
|
81
174
|
if model.storage_exists?(repository_name)
|
82
175
|
adapter = model.repository(repository_name).adapter
|
83
176
|
next unless adapter.respond_to?(:destroy_constraints_statements)
|
@@ -90,6 +183,7 @@ module DataMapper
|
|
90
183
|
def auto_migrate_constraints_up(retval, repository_name, *descendants)
|
91
184
|
descendants = DataMapper::Resource.descendants.to_a if descendants.empty?
|
92
185
|
descendants.each do |model|
|
186
|
+
repository_name ||= model.repository(repository_name).name
|
93
187
|
adapter = model.repository(repository_name).adapter
|
94
188
|
next unless adapter.respond_to?(:create_constraints_statements)
|
95
189
|
statements = adapter.create_constraints_statements(repository_name, model)
|
@@ -8,16 +8,57 @@ module DataMapper
|
|
8
8
|
|
9
9
|
module ClassMethods
|
10
10
|
DELETE_CONSTRAINT_OPTIONS = [:protect, :destroy, :destroy!, :set_nil, :skip]
|
11
|
+
|
12
|
+
##
|
13
|
+
# Checks that the constraint type is appropriate to the relationship
|
14
|
+
#
|
15
|
+
# @param cardinality [Fixnum] cardinality of relationship
|
16
|
+
#
|
17
|
+
# @param name [Symbol] name of relationship to evaluate constraint of
|
18
|
+
#
|
19
|
+
# @param options [Hash] options hash
|
20
|
+
#
|
21
|
+
# @raises ArgumentError
|
22
|
+
#
|
23
|
+
# @return [nil]
|
24
|
+
#
|
25
|
+
# @api semi-public
|
11
26
|
def check_delete_constraint_type(cardinality, name, options = {})
|
27
|
+
#Make sure options contains :constraint key, whether nil or not
|
28
|
+
options[:constraint] ||= nil
|
12
29
|
constraint_type = options[:constraint]
|
13
30
|
return if constraint_type.nil?
|
31
|
+
|
14
32
|
delete_constraint_options = DELETE_CONSTRAINT_OPTIONS.map { |o| ":#{o}" }
|
15
33
|
if !DELETE_CONSTRAINT_OPTIONS.include?(constraint_type)
|
16
34
|
raise ArgumentError, ":constraint option must be one of #{delete_constraint_options * ', '}"
|
17
35
|
end
|
36
|
+
|
37
|
+
if constraint_type == :set_nil && self.relationships[name].is_a?(DataMapper::Associations::RelationshipChain)
|
38
|
+
raise ArgumentError, "Constraint type :set_nil is not valid for M:M relationships"
|
39
|
+
end
|
40
|
+
|
41
|
+
if cardinality == 1 && constraint_type == :destroy!
|
42
|
+
raise ArgumentError, "Constraint type :destroy! is not valid for 1:1 relationships"
|
43
|
+
end
|
18
44
|
end
|
19
45
|
|
20
|
-
|
46
|
+
##
|
47
|
+
# Temporarily changes the visibility of a method so a block can be evaluated against it
|
48
|
+
#
|
49
|
+
# @param method [Symobl] method to change visibility of
|
50
|
+
#
|
51
|
+
# @param from_visibility [Symbol] original visibility
|
52
|
+
#
|
53
|
+
# @param to_visibility [Symbol] temporary visibility
|
54
|
+
#
|
55
|
+
# @param block [Proc] proc to run
|
56
|
+
#
|
57
|
+
# @notes TODO: this should be moved to a 'util-like' module
|
58
|
+
#
|
59
|
+
# @return [nil]
|
60
|
+
#
|
61
|
+
# @api semi-public
|
21
62
|
def with_changed_method_visibility(method, from_visibility, to_visibility, &block)
|
22
63
|
send(to_visibility, method)
|
23
64
|
yield
|
@@ -26,33 +67,119 @@ module DataMapper
|
|
26
67
|
|
27
68
|
end
|
28
69
|
|
29
|
-
|
30
|
-
|
70
|
+
##
|
71
|
+
# Addes the delete constraint options to a relationship
|
72
|
+
#
|
73
|
+
# @param params [*ARGS] Arguments passed to Relationship#initialize or RelationshipChain#initialize
|
74
|
+
#
|
75
|
+
# @notes This takes *params because it runs before the initializer for Relationships and RelationshipChains
|
76
|
+
# which have different method signatures
|
77
|
+
#
|
78
|
+
# @return [nil]
|
79
|
+
#
|
80
|
+
# @api semi-public
|
81
|
+
def add_delete_constraint_option(*params)
|
82
|
+
opts = params.last
|
83
|
+
|
84
|
+
if opts.is_a?(Hash)
|
85
|
+
#if it is a chain, set the constraint on the 1:M near relationship(anonymous)
|
86
|
+
if self.is_a?(DataMapper::Associations::RelationshipChain)
|
87
|
+
opts = params.last
|
88
|
+
near_rel = opts[:parent_model].relationships[opts[:near_relationship_name]]
|
89
|
+
near_rel.options[:constraint] = opts[:constraint]
|
90
|
+
near_rel.instance_variable_set "@delete_constraint", opts[:constraint]
|
91
|
+
end
|
92
|
+
|
93
|
+
@delete_constraint = params.last[:constraint]
|
94
|
+
end
|
31
95
|
end
|
32
96
|
|
97
|
+
##
|
98
|
+
# Checks delete constraints prior to destroying a dm resource or collection
|
99
|
+
#
|
100
|
+
# @throws :halt
|
101
|
+
#
|
102
|
+
# @notes
|
103
|
+
# - It only considers a relationship's constraints if this is the parent model (ie a child shouldn't delete a parent)
|
104
|
+
# - RelationshipChains are skipped, as they are evaluated by their underlying 1:M relationships
|
105
|
+
#
|
106
|
+
# @returns [nil]
|
107
|
+
#
|
108
|
+
# @api semi-public
|
33
109
|
def check_delete_constraints
|
34
110
|
model.relationships.each do |rel_name, rel|
|
111
|
+
#Only look at relationships where this model is the parent
|
112
|
+
next if rel.parent_model != model
|
113
|
+
|
114
|
+
#Don't delete across M:M relationships, instead use their anonymous 1:M Relationships
|
115
|
+
next if rel.is_a?(DataMapper::Associations::RelationshipChain)
|
116
|
+
|
35
117
|
children = self.send(rel_name)
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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
|
118
|
+
if children.kind_of?(DataMapper::Collection)
|
119
|
+
check_collection_delete_constraints(rel,children)
|
120
|
+
elsif children
|
121
|
+
check_resource_delete_constraints(rel,children)
|
122
|
+
end
|
53
123
|
end # relationships
|
54
124
|
end # check_delete_constraints
|
55
125
|
|
126
|
+
##
|
127
|
+
# Performs the meat of the check_delete_constraints method for a collection of resources
|
128
|
+
#
|
129
|
+
# @param rel [DataMapper::Associations::Relationship] relationship being evaluated
|
130
|
+
#
|
131
|
+
# @param children [~DataMapper::Collection] child records to constrain
|
132
|
+
#
|
133
|
+
# @see #check_delete_constraints
|
134
|
+
#
|
135
|
+
# @api semi-public
|
136
|
+
def check_collection_delete_constraints(rel, children)
|
137
|
+
case rel.delete_constraint
|
138
|
+
when nil, :protect
|
139
|
+
unless children.empty?
|
140
|
+
DataMapper.logger.error("Could not delete #{self.class} a child #{children.first.class} exists")
|
141
|
+
throw(:halt,false)
|
142
|
+
end
|
143
|
+
when :destroy
|
144
|
+
children.each{|child| child.destroy}
|
145
|
+
when :destroy!
|
146
|
+
children.destroy!
|
147
|
+
when :set_nil
|
148
|
+
children.each do |child|
|
149
|
+
child.class.many_to_one_relationships.each do |mto_rel|
|
150
|
+
child.send("#{mto_rel.name}=", nil) if child.send(mto_rel.name).eql?(self)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
##
|
157
|
+
# Performs the meat of check_delete_constraints method for a single resource
|
158
|
+
#
|
159
|
+
# @param rel [DataMapper::Associations::Relationship] the relationship to evaluate
|
160
|
+
#
|
161
|
+
# @param child [~DataMapper::Model] the model to constrain
|
162
|
+
#
|
163
|
+
# @see #check_delete_constraints
|
164
|
+
#
|
165
|
+
# @api semi-public
|
166
|
+
def check_resource_delete_constraints(rel, child)
|
167
|
+
case rel.delete_constraint
|
168
|
+
when nil, :protect
|
169
|
+
unless child.nil?
|
170
|
+
DataMapper.logger.error("Could not delete #{self.class} a child #{child.class} exists")
|
171
|
+
throw(:halt,false)
|
172
|
+
end
|
173
|
+
when :destroy
|
174
|
+
child.destroy
|
175
|
+
when :destroy!
|
176
|
+
#not supported in dm-master, an exception should have been raised on class load
|
177
|
+
when :set_nil
|
178
|
+
child.class.many_to_one_relationships.each do |mto_rel|
|
179
|
+
child.send("#{mto_rel.name}=", nil) if child.send(mto_rel.name).eql?(self)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
56
183
|
|
57
184
|
end # DeleteConstraint
|
58
185
|
end # Constraints
|
@@ -6,6 +6,16 @@ module DataMapper
|
|
6
6
|
|
7
7
|
private
|
8
8
|
|
9
|
+
##
|
10
|
+
# MySQL specific query to determine to drop a foreign key
|
11
|
+
#
|
12
|
+
# @param table_name [Symbol] name of table to check constraint on
|
13
|
+
#
|
14
|
+
# @param constraint_name [~String] name of constraint to check for
|
15
|
+
#
|
16
|
+
# @return [String] SQL DDL to destroy a constraint
|
17
|
+
#
|
18
|
+
# @api private
|
9
19
|
def destroy_constraints_statement(table_name, constraint_name)
|
10
20
|
<<-EOS.compress_lines
|
11
21
|
ALTER TABLE #{quote_table_name(table_name)}
|
@@ -13,7 +23,17 @@ module DataMapper
|
|
13
23
|
EOS
|
14
24
|
end
|
15
25
|
|
16
|
-
|
26
|
+
##
|
27
|
+
# MySQL specific query to determine if a constraint exists
|
28
|
+
#
|
29
|
+
# @param table_name [Symbol] name of table to check constraint on
|
30
|
+
#
|
31
|
+
# @param constraint_name [~String] name of constraint to check for
|
32
|
+
#
|
33
|
+
# @return [Boolean]
|
34
|
+
#
|
35
|
+
# @api private
|
36
|
+
def constraint_exists?(table_name, constraint_name)
|
17
37
|
statement = <<-EOS.compress_lines
|
18
38
|
SELECT COUNT(*)
|
19
39
|
FROM `information_schema`.`table_constraints`
|
@@ -22,7 +42,7 @@ module DataMapper
|
|
22
42
|
AND `table_name` = ?
|
23
43
|
AND `constraint_name` = ?
|
24
44
|
EOS
|
25
|
-
query(statement, db_name,
|
45
|
+
query(statement, db_name, table_name, constraint_name).first > 0
|
26
46
|
end
|
27
47
|
end
|
28
48
|
end
|
@@ -6,7 +6,17 @@ module DataMapper
|
|
6
6
|
|
7
7
|
private
|
8
8
|
|
9
|
-
|
9
|
+
##
|
10
|
+
# Postgres specific query to determine if a constraint exists
|
11
|
+
#
|
12
|
+
# @param table_name [Symbol] name of table to check constraint on
|
13
|
+
#
|
14
|
+
# @param constraint_name [~String] name of constraint to check for
|
15
|
+
#
|
16
|
+
# @return [Boolean]
|
17
|
+
#
|
18
|
+
# @api private
|
19
|
+
def constraint_exists?(table_name, constraint_name)
|
10
20
|
statement = <<-EOS.compress_lines
|
11
21
|
SELECT COUNT(*)
|
12
22
|
FROM "information_schema"."table_constraints"
|
@@ -14,7 +24,7 @@ module DataMapper
|
|
14
24
|
AND "table_name" = ?
|
15
25
|
AND "constraint_name" = ?
|
16
26
|
EOS
|
17
|
-
query(statement,
|
27
|
+
query(statement, table_name, constraint_name).first > 0
|
18
28
|
end
|
19
29
|
end
|
20
30
|
end
|
data/lib/dm-constraints.rb
CHANGED
@@ -3,7 +3,7 @@ require 'rubygems'
|
|
3
3
|
require 'pathname'
|
4
4
|
|
5
5
|
# Add all external dependencies for the plugin here
|
6
|
-
gem 'dm-core', '
|
6
|
+
gem 'dm-core', '0.9.11'
|
7
7
|
require 'dm-core'
|
8
8
|
|
9
9
|
# Require plugin-files
|
@@ -12,6 +12,25 @@ require Pathname(__FILE__).dirname.expand_path / 'dm-constraints' / 'postgres_ad
|
|
12
12
|
require Pathname(__FILE__).dirname.expand_path / 'dm-constraints' / 'mysql_adapter'
|
13
13
|
require Pathname(__FILE__).dirname.expand_path / 'dm-constraints' / 'delete_constraint'
|
14
14
|
|
15
|
+
module DataMapper
|
16
|
+
module Associations
|
17
|
+
class RelationshipChain
|
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
|
+
|
15
34
|
module DataMapper
|
16
35
|
module Associations
|
17
36
|
class Relationship
|
@@ -40,6 +59,10 @@ module DataMapper
|
|
40
59
|
include DeleteConstraint::ClassMethods
|
41
60
|
end
|
42
61
|
|
62
|
+
##
|
63
|
+
# Add before hooks to #has to check for proper constraint definitions
|
64
|
+
# Add before hooks to #destroy to properly constrain children
|
65
|
+
#
|
43
66
|
def self.included(model)
|
44
67
|
model.extend(ClassMethods)
|
45
68
|
model.class_eval do
|
@@ -2,11 +2,7 @@ require 'pathname'
|
|
2
2
|
require Pathname(__FILE__).dirname.expand_path.parent + 'spec_helper'
|
3
3
|
|
4
4
|
ADAPTERS.each do |adapter|
|
5
|
-
|
6
5
|
describe 'DataMapper::Constraints' do
|
7
|
-
|
8
|
-
# load_models_for_metaphor :stable, :farmer, :cow
|
9
|
-
|
10
6
|
before do
|
11
7
|
DataMapper::Repository.adapters[:default] = DataMapper::Repository.adapters[adapter]
|
12
8
|
|
@@ -43,10 +39,43 @@ ADAPTERS.each do |adapter|
|
|
43
39
|
belongs_to :farmer
|
44
40
|
end
|
45
41
|
|
42
|
+
#Used to test a belongs_to association with no has() association
|
43
|
+
#on the other end
|
44
|
+
class ::Pig
|
45
|
+
include DataMapper::Resource
|
46
|
+
include DataMapper::Constraints
|
47
|
+
|
48
|
+
property :id, Serial
|
49
|
+
property :name, String
|
50
|
+
|
51
|
+
belongs_to :farmer
|
52
|
+
end
|
53
|
+
|
54
|
+
#Used to test M:M :through => Resource relationships
|
55
|
+
class ::Chicken
|
56
|
+
include DataMapper::Resource
|
57
|
+
include DataMapper::Constraints
|
58
|
+
|
59
|
+
property :id, Serial
|
60
|
+
property :name, String
|
61
|
+
|
62
|
+
has n, :tags, :through => Resource
|
63
|
+
end
|
64
|
+
|
65
|
+
class ::Tag
|
66
|
+
include DataMapper::Resource
|
67
|
+
include DataMapper::Constraints
|
68
|
+
|
69
|
+
property :id, Serial
|
70
|
+
property :phrase, String
|
71
|
+
|
72
|
+
has n, :chickens, :through => Resource
|
73
|
+
end
|
74
|
+
|
46
75
|
DataMapper.auto_migrate!
|
47
|
-
end
|
76
|
+
end # before
|
48
77
|
|
49
|
-
it "is included when DataMapper::
|
78
|
+
it "is included when DataMapper::Constraints is loaded" do
|
50
79
|
Cow.new.should be_kind_of(DataMapper::Constraints)
|
51
80
|
end
|
52
81
|
|
@@ -65,16 +94,24 @@ ADAPTERS.each do |adapter|
|
|
65
94
|
lambda { @c1 = Cow.create(:name => "Bea", :stable_id => s.id + 1) }.should raise_error
|
66
95
|
end
|
67
96
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
97
|
+
describe "belongs_to without matching has association" do
|
98
|
+
before do
|
99
|
+
@f1 = Farmer.create(:first_name => "John", :last_name => "Doe")
|
100
|
+
@f2 = Farmer.create(:first_name => "Some", :last_name => "Body")
|
101
|
+
@p = Pig.create(:name => "Bea", :farmer => @f2)
|
102
|
+
end
|
103
|
+
it "should destroy the parent if there are no children in the association" do
|
104
|
+
@f1.destroy.should == true
|
105
|
+
end
|
106
|
+
|
107
|
+
it "the child should be destroyable" do
|
108
|
+
@p.destroy.should == true
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
76
112
|
|
77
113
|
describe "constraint options" do
|
114
|
+
|
78
115
|
describe "when no constraint options are given" do
|
79
116
|
|
80
117
|
it "should destroy the parent if there are no children in the association" do
|
@@ -96,128 +133,424 @@ ADAPTERS.each do |adapter|
|
|
96
133
|
before do
|
97
134
|
class ::Farmer
|
98
135
|
has n, :cows, :constraint => :protect
|
136
|
+
has 1, :pig, :constraint => :protect
|
137
|
+
end
|
138
|
+
class ::Pig
|
139
|
+
belongs_to :farmer
|
99
140
|
end
|
100
141
|
class ::Cow
|
101
142
|
belongs_to :farmer
|
102
143
|
end
|
144
|
+
class ::Chicken
|
145
|
+
has n, :tags, :through => Resource, :constraint => :protect
|
146
|
+
end
|
147
|
+
class ::Tag
|
148
|
+
has n, :chickens, :through => Resource, :constraint => :protect
|
149
|
+
end
|
103
150
|
end
|
104
151
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
152
|
+
describe "one-to-one associations" do
|
153
|
+
before do
|
154
|
+
@f1 = Farmer.create(:first_name => "Mary", :last_name => "Smith")
|
155
|
+
@p1 = Pig.create(:name => "Morton",:farmer => @f1)
|
156
|
+
end
|
157
|
+
|
158
|
+
it "should not destroy the parent if there are children in the association" do
|
159
|
+
@f1.destroy.should == false
|
160
|
+
end
|
161
|
+
|
162
|
+
it "the child should be destroyable" do
|
163
|
+
@p1.destroy.should == true
|
164
|
+
end
|
110
165
|
end
|
111
166
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
167
|
+
describe "one-to-many associations" do
|
168
|
+
before do
|
169
|
+
@f1 = Farmer.create(:first_name => "John", :last_name => "Doe")
|
170
|
+
@f2 = Farmer.create(:first_name => "Some", :last_name => "Body")
|
171
|
+
@c1 = Cow.create(:name => "Bea", :farmer => @f2)
|
172
|
+
end
|
173
|
+
|
174
|
+
it "should destroy the parent if there are no children in the association" do
|
175
|
+
@f1.destroy.should == true
|
176
|
+
end
|
177
|
+
|
178
|
+
it "should not destroy the parent if there are children in the association" do
|
179
|
+
@f2.destroy.should == false
|
180
|
+
end
|
181
|
+
|
182
|
+
it "the child should be destroyable" do
|
183
|
+
@c1.destroy.should == true
|
184
|
+
end
|
116
185
|
end
|
117
186
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
187
|
+
describe "many-to-many associations" do
|
188
|
+
before do
|
189
|
+
@t1 = Tag.create(:phrase => "silly chicken")
|
190
|
+
@t2 = Tag.create(:phrase => "serious chicken")
|
191
|
+
@chk1 = Chicken.create(:name =>"Frank the Chicken", :tags => [@t2])
|
192
|
+
end
|
193
|
+
|
194
|
+
it "should destroy the parent if there are no children in the association" do
|
195
|
+
@t1.destroy.should == true
|
196
|
+
end
|
197
|
+
|
198
|
+
it "should not destroy the parent if there are children in the association" do
|
199
|
+
@t2.destroy.should == false
|
200
|
+
end
|
201
|
+
|
202
|
+
it "the child should be destroyable" do
|
203
|
+
@chk1.tags.clear
|
204
|
+
@chk1.save.should == true
|
205
|
+
@chk1.tags.should be_empty
|
206
|
+
end
|
122
207
|
end
|
123
208
|
|
124
|
-
end
|
209
|
+
end # when constraint protect is given
|
210
|
+
|
211
|
+
describe "when :constraint => :destroy! is given" do
|
212
|
+
before do
|
213
|
+
class ::Farmer
|
214
|
+
has n, :cows, :constraint => :destroy!
|
215
|
+
end
|
216
|
+
class ::Cow
|
217
|
+
belongs_to :farmer
|
218
|
+
end
|
219
|
+
class ::Chicken
|
220
|
+
has n, :tags, :through => Resource, :constraint => :destroy!
|
221
|
+
end
|
222
|
+
class ::Tag
|
223
|
+
has n, :chickens, :through => Resource, :constraint => :destroy!
|
224
|
+
end
|
225
|
+
|
226
|
+
DataMapper.auto_migrate!
|
227
|
+
end
|
228
|
+
|
229
|
+
describe "one-to-many associations" do
|
230
|
+
before(:each) do
|
231
|
+
@f = Farmer.create(:first_name => "John", :last_name => "Doe")
|
232
|
+
@c1 = Cow.create(:name => "Bea", :farmer => @f)
|
233
|
+
@c2 = Cow.create(:name => "Riksa", :farmer => @f)
|
234
|
+
end
|
235
|
+
|
236
|
+
it "should let the parent to be destroyed" do
|
237
|
+
@f.destroy.should == true
|
238
|
+
@f.should be_new_record
|
239
|
+
end
|
240
|
+
|
241
|
+
it "should destroy the children" do
|
242
|
+
@f.destroy
|
243
|
+
@f.cows.all? { |c| c.should be_new_record }
|
244
|
+
end
|
245
|
+
|
246
|
+
it "the child should be destroyable" do
|
247
|
+
@c1.destroy.should == true
|
248
|
+
end
|
249
|
+
|
250
|
+
end
|
251
|
+
|
252
|
+
describe "many-to-many associations" do
|
253
|
+
before do
|
254
|
+
@t1 = Tag.create(:phrase => "floozy")
|
255
|
+
@t2 = Tag.create(:phrase => "dirty")
|
256
|
+
@chk1 = Chicken.create(:name => "Nancy Chicken", :tags => [@t1, @t2])
|
257
|
+
end
|
258
|
+
|
259
|
+
it "should destroy! the parent and the children, too" do
|
260
|
+
@chk1.destroy.should == true
|
261
|
+
@chk1.should be_new_record
|
262
|
+
|
263
|
+
# @t1 & @t2 should still exist, the chicken_tags should have been deleted
|
264
|
+
ChickenTag.all.should be_empty
|
265
|
+
@t1.should_not be_new_record
|
266
|
+
@t2.should_not be_new_record
|
267
|
+
end
|
268
|
+
|
269
|
+
it "the child should be destroyable" do
|
270
|
+
@chk1.destroy.should == true
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
end # when :constraint => :destroy! is given
|
125
275
|
|
126
276
|
describe "when :constraint => :destroy is given" do
|
127
277
|
before do
|
128
278
|
class ::Farmer
|
129
279
|
has n, :cows, :constraint => :destroy
|
280
|
+
has 1, :pig, :constraint => :destroy
|
130
281
|
end
|
131
282
|
class ::Cow
|
132
283
|
belongs_to :farmer
|
133
284
|
end
|
285
|
+
class ::Chicken
|
286
|
+
has n, :tags, :through => Resource, :constraint => :destroy
|
287
|
+
end
|
288
|
+
class ::Tag
|
289
|
+
has n, :chickens, :through => Resource, :constraint => :destroy
|
290
|
+
end
|
291
|
+
|
134
292
|
DataMapper.auto_migrate!
|
135
293
|
end
|
136
294
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
295
|
+
describe "one-to-one associations" do
|
296
|
+
before do
|
297
|
+
@f = Farmer.create(:first_name => "Ted", :last_name => "Cornhusker")
|
298
|
+
@p = Pig.create(:name => "BaconBits", :farmer => @f)
|
299
|
+
end
|
300
|
+
|
301
|
+
it "should let the parent to be destroyed" do
|
302
|
+
@f.destroy.should == true
|
303
|
+
@f.should be_new_record
|
304
|
+
end
|
305
|
+
|
306
|
+
it "should destroy the children" do
|
307
|
+
pig = @f.pig
|
308
|
+
@f.destroy
|
309
|
+
pig.should be_new_record
|
310
|
+
end
|
311
|
+
|
312
|
+
it "the child should be destroyable" do
|
313
|
+
@p.destroy.should == true
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
describe "one-to-many associations" do
|
318
|
+
before(:each) do
|
142
319
|
@f = Farmer.create(:first_name => "John", :last_name => "Doe")
|
143
320
|
@c1 = Cow.create(:name => "Bea", :farmer => @f)
|
144
321
|
@c2 = Cow.create(:name => "Riksa", :farmer => @f)
|
322
|
+
end
|
323
|
+
|
324
|
+
it "should let the parent to be destroyed" do
|
145
325
|
@f.destroy.should == true
|
146
326
|
@f.should be_new_record
|
147
|
-
@c1.should be_new_record
|
148
|
-
@c2.should be_new_record
|
149
327
|
end
|
328
|
+
|
329
|
+
it "should destroy the children" do
|
330
|
+
@f.destroy
|
331
|
+
@f.cows.all? { |c| c.should be_new_record }
|
332
|
+
@f.should be_new_record
|
333
|
+
end
|
334
|
+
|
335
|
+
it "the child should be destroyable" do
|
336
|
+
@c1.destroy.should == true
|
337
|
+
end
|
338
|
+
|
150
339
|
end
|
151
340
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
341
|
+
describe "many-to-many associations" do
|
342
|
+
before do
|
343
|
+
@t1 = Tag.create :phrase => "floozy"
|
344
|
+
@t2 = Tag.create :phrase => "dirty"
|
345
|
+
@chk1 = Chicken.create :name => "Nancy Chicken", :tags => [@t1,@t2]
|
346
|
+
end
|
347
|
+
|
348
|
+
it "should destroy the parent and the children, too" do
|
349
|
+
@chk1.destroy.should == true
|
350
|
+
@chk1.should be_new_record
|
351
|
+
|
352
|
+
#@t1 & @t2 should still exist, the chicken_tags should have been deleted
|
353
|
+
ChickenTag.all.should be_empty
|
354
|
+
@t1.should_not be_new_record
|
355
|
+
@t2.should_not be_new_record
|
356
|
+
end
|
357
|
+
|
358
|
+
it "the child should be destroyable" do
|
359
|
+
@chk1.destroy.should == true
|
360
|
+
end
|
156
361
|
end
|
157
362
|
|
158
|
-
end
|
363
|
+
end # when :constraint => :destroy is given
|
159
364
|
|
160
365
|
describe "when :constraint => :set_nil is given" do
|
161
366
|
before do
|
162
367
|
class ::Farmer
|
163
368
|
has n, :cows, :constraint => :set_nil
|
369
|
+
has 1, :pig, :constraint => :set_nil
|
164
370
|
end
|
165
371
|
class ::Cow
|
166
372
|
belongs_to :farmer
|
167
373
|
end
|
374
|
+
# NOTE: M:M Relationships are not supported,
|
375
|
+
# see "when checking constraint types" tests at bottom
|
168
376
|
DataMapper.auto_migrate!
|
169
377
|
end
|
170
378
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
379
|
+
describe "one-to-one associations" do
|
380
|
+
before do
|
381
|
+
@f = Farmer.create(:first_name => "Mr", :last_name => "Hands")
|
382
|
+
@p = Pig.create(:name => "Greasy", :farmer => @f)
|
383
|
+
end
|
384
|
+
|
385
|
+
it "should let the parent to be destroyed" do
|
386
|
+
@f.destroy.should == true
|
387
|
+
end
|
388
|
+
|
389
|
+
it "should set the child's foreign_key id to nil" do
|
390
|
+
pig = @f.pig
|
391
|
+
@f.destroy.should == true
|
392
|
+
pig.farmer.should be_nil
|
393
|
+
end
|
394
|
+
|
395
|
+
it "the child should be destroyable" do
|
396
|
+
@p.destroy.should == true
|
397
|
+
end
|
398
|
+
|
178
399
|
end
|
179
400
|
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
401
|
+
describe "one-to-many associations" do
|
402
|
+
before(:each) do
|
403
|
+
@f = Farmer.create(:first_name => "John", :last_name => "Doe")
|
404
|
+
@c1 = Cow.create(:name => "Bea", :farmer => @f)
|
405
|
+
@c2 = Cow.create(:name => "Riksa", :farmer => @f)
|
406
|
+
end
|
407
|
+
|
408
|
+
it "should let the parent to be destroyed" do
|
409
|
+
@f.destroy.should == true
|
410
|
+
@f.should be_new_record
|
411
|
+
end
|
412
|
+
|
413
|
+
it "should set the foreign_key ids of children to nil" do
|
414
|
+
@f.destroy
|
415
|
+
@f.cows.all? { |c| c.farmer.should be_nil }
|
416
|
+
end
|
417
|
+
|
418
|
+
it "the children should be destroyable" do
|
419
|
+
@c1.destroy.should == true
|
420
|
+
@c2.destroy.should == true
|
421
|
+
end
|
422
|
+
|
184
423
|
end
|
185
424
|
|
186
|
-
end # describe
|
425
|
+
end # describe "when :constraint => :set_nil is given" do
|
187
426
|
|
188
427
|
describe "when :constraint => :skip is given" do
|
189
428
|
before do
|
190
429
|
class ::Farmer
|
191
430
|
has n, :cows, :constraint => :skip
|
431
|
+
has 1, :pig, :constraint => :skip
|
192
432
|
end
|
193
433
|
class ::Cow
|
194
434
|
belongs_to :farmer
|
195
435
|
end
|
436
|
+
class ::Chicken
|
437
|
+
has n, :tags, :through => Resource, :constraint => :skip
|
438
|
+
end
|
439
|
+
class ::Tag
|
440
|
+
has n, :chickens, :through => Resource, :constraint => :skip
|
441
|
+
end
|
196
442
|
DataMapper.auto_migrate!
|
197
443
|
end
|
198
444
|
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
445
|
+
describe "one-to-one associations" do
|
446
|
+
before do
|
447
|
+
@f = Farmer.create(:first_name => "William", :last_name => "Shepard")
|
448
|
+
@p = Pig.create(:name => "Jiggles The Pig", :farmer => @f)
|
449
|
+
end
|
450
|
+
|
451
|
+
it "should let the parent be destroyed" do
|
452
|
+
@f.destroy.should == true
|
453
|
+
@f.should be_new_record
|
454
|
+
# @p.farmer.should be_new_record
|
455
|
+
end
|
456
|
+
|
457
|
+
it "should let the children become orphan records" do
|
458
|
+
@f.destroy
|
459
|
+
@p.farmer.should be_new_record
|
460
|
+
end
|
461
|
+
|
462
|
+
it "the child should be destroyable" do
|
463
|
+
@p.destroy.should == true
|
464
|
+
end
|
465
|
+
|
206
466
|
end
|
207
467
|
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
468
|
+
describe "one-to-many associations" do
|
469
|
+
before do
|
470
|
+
@f = Farmer.create(:first_name => "John", :last_name => "Doe")
|
471
|
+
@c1 = Cow.create(:name => "Bea", :farmer => @f)
|
472
|
+
@c2 = Cow.create(:name => "Riksa", :farmer => @f)
|
473
|
+
end
|
474
|
+
|
475
|
+
it "should let the parent to be destroyed" do
|
476
|
+
@f.destroy.should == true
|
477
|
+
@f.should be_new_record
|
478
|
+
end
|
479
|
+
|
480
|
+
it "should let the children become orphan records" do
|
481
|
+
@f.destroy
|
482
|
+
@c1.farmer.should be_new_record
|
483
|
+
@c2.farmer.should be_new_record
|
484
|
+
end
|
485
|
+
|
486
|
+
it "the children should be destroyable" do
|
487
|
+
@c1.destroy.should == true
|
488
|
+
@c2.destroy.should == true
|
489
|
+
end
|
490
|
+
|
491
|
+
end
|
492
|
+
|
493
|
+
describe "many-to-many associations" do
|
494
|
+
before do
|
495
|
+
@t = Tag.create(:phrase => "Richard Pryor's Chicken")
|
496
|
+
@chk = Chicken.create(:name => "Delicious", :tags => [@t])
|
497
|
+
end
|
498
|
+
|
499
|
+
it "the children should be destroyable" do
|
500
|
+
@chk.destroy.should == true
|
501
|
+
end
|
212
502
|
end
|
213
503
|
|
214
|
-
end # describe
|
504
|
+
end # describe "when :constraint => :skip is given"
|
505
|
+
|
506
|
+
describe "when checking constraint types" do
|
507
|
+
|
508
|
+
#M:M relationships results in a join table composed of a two part primary key
|
509
|
+
# setting a portion of the primary key is not possible for two reasons:
|
510
|
+
# 1. the columns are defined as :nullable => false
|
511
|
+
# 2. there could be duplicate rows if more than one of either of the types
|
512
|
+
# was deleted while being associated to the same type on the other side of the relationshp
|
513
|
+
# Given
|
514
|
+
# Turkey(Name: Ted, ID: 1) =>
|
515
|
+
# Tags[Tag(Phrase: awesome, ID: 1), Tag(Phrase: fat, ID: 2)]
|
516
|
+
# Turkey(Name: Steve, ID: 2) =>
|
517
|
+
# Tags[Tag(Phrase: awesome, ID: 1), Tag(Phrase: flamboyant, ID: 3)]
|
518
|
+
#
|
519
|
+
# Table turkeys_tags would look like (turkey_id, tag_id)
|
520
|
+
# (1, 1)
|
521
|
+
# (1, 2)
|
522
|
+
# (2, 1)
|
523
|
+
# (2, 3)
|
524
|
+
#
|
525
|
+
# If both turkeys were deleted and pk was set null
|
526
|
+
# (null, 1)
|
527
|
+
# (null, 2)
|
528
|
+
# (null, 1) #at this time there would be a duplicate row error
|
529
|
+
# (null, 3)
|
530
|
+
#
|
531
|
+
# I would suggest setting :constraint to :skip in this scenario which will leave
|
532
|
+
# you with orphaned rows.
|
533
|
+
it "should raise an error if :set_nil is given for a M:M relationship" do
|
534
|
+
lambda{
|
535
|
+
class ::Chicken
|
536
|
+
has n, :tags, :through => Resource, :constraint => :set_nil
|
537
|
+
end
|
538
|
+
class ::Tag
|
539
|
+
has n, :chickens, :through => Resource, :constraint => :set_nil
|
540
|
+
end
|
541
|
+
}.should raise_error(ArgumentError)
|
542
|
+
end
|
215
543
|
|
216
|
-
|
217
|
-
|
544
|
+
# Resource#destroy! is not suppored in dm-core
|
545
|
+
it "should raise an error if :destroy! is given for a 1:1 relationship" do
|
546
|
+
lambda do
|
547
|
+
class ::Farmer
|
548
|
+
has 1, :pig, :constraint => :destroy!
|
549
|
+
end
|
550
|
+
end.should raise_error(ArgumentError)
|
218
551
|
end
|
219
552
|
|
220
|
-
it "should raise an error" do
|
553
|
+
it "should raise an error if an unknown type is given" do
|
221
554
|
lambda do
|
222
555
|
class ::Farmer
|
223
556
|
has n, :cows, :constraint => :chocolate
|
data/spec/spec_helper.rb
CHANGED
data/tasks/spec.rb
CHANGED
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.11
|
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-
|
12
|
+
date: 2009-03-29 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -18,9 +18,9 @@ dependencies:
|
|
18
18
|
version_requirement:
|
19
19
|
version_requirements: !ruby/object:Gem::Requirement
|
20
20
|
requirements:
|
21
|
-
- -
|
21
|
+
- - "="
|
22
22
|
- !ruby/object:Gem::Version
|
23
|
-
version: 0.9.
|
23
|
+
version: 0.9.11
|
24
24
|
version:
|
25
25
|
description: DataMapper plugin constraining relationships
|
26
26
|
email:
|