sbf-dm-constraints 1.3.0.beta

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +39 -0
  3. data/.rspec +5 -0
  4. data/.rubocop.yml +468 -0
  5. data/Gemfile +70 -0
  6. data/LICENSE +20 -0
  7. data/README.rdoc +58 -0
  8. data/Rakefile +4 -0
  9. data/dm-constraints.gemspec +21 -0
  10. data/lib/data_mapper/constraints/adapters/abstract_adapter.rb +33 -0
  11. data/lib/data_mapper/constraints/adapters/do_adapter.rb +192 -0
  12. data/lib/data_mapper/constraints/adapters/extension.rb +45 -0
  13. data/lib/data_mapper/constraints/adapters/mysql_adapter.rb +26 -0
  14. data/lib/data_mapper/constraints/adapters/oracle_adapter.rb +53 -0
  15. data/lib/data_mapper/constraints/adapters/postgres_adapter.rb +13 -0
  16. data/lib/data_mapper/constraints/adapters/sqlite_adapter.rb +22 -0
  17. data/lib/data_mapper/constraints/adapters/sqlserver_adapter.rb +11 -0
  18. data/lib/data_mapper/constraints/migrations/model.rb +34 -0
  19. data/lib/data_mapper/constraints/migrations/relationship.rb +41 -0
  20. data/lib/data_mapper/constraints/migrations/singleton_methods.rb +44 -0
  21. data/lib/data_mapper/constraints/relationship/many_to_many.rb +48 -0
  22. data/lib/data_mapper/constraints/relationship/one_to_many.rb +79 -0
  23. data/lib/data_mapper/constraints/resource.rb +30 -0
  24. data/lib/data_mapper/constraints/version.rb +5 -0
  25. data/lib/dm-constraints.rb +19 -0
  26. data/spec/integration/constraints_spec.rb +630 -0
  27. data/spec/isolated/require_after_setup_spec.rb +36 -0
  28. data/spec/isolated/require_before_setup_spec.rb +35 -0
  29. data/spec/isolated/require_spec.rb +15 -0
  30. data/spec/rcov.opts +6 -0
  31. data/spec/spec_helper.rb +26 -0
  32. data/tasks/spec.rake +21 -0
  33. data/tasks/yard.rake +9 -0
  34. data/tasks/yardstick.rake +19 -0
  35. metadata +94 -0
@@ -0,0 +1,33 @@
1
+ require 'data_mapper/constraints/adapters/extension'
2
+
3
+ module DataMapper
4
+ module Constraints
5
+ module Adapters
6
+ module AbstractAdapter
7
+ # @api private
8
+ def constraint_exists?(*)
9
+ false
10
+ end
11
+
12
+ # @api private
13
+ def create_relationship_constraint(*)
14
+ false
15
+ end
16
+
17
+ # @api private
18
+ def destroy_relationship_constraint(*)
19
+ false
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ Adapters::AbstractAdapter.class_eval do
26
+ include Constraints::Adapters::AbstractAdapter
27
+ end
28
+
29
+ Adapters::AbstractAdapter.descendants.each do |adapter_class|
30
+ const_name = DataMapper::Inflector.demodulize(adapter_class.name)
31
+ Adapters.include_constraint_api(const_name)
32
+ end
33
+ end
@@ -0,0 +1,192 @@
1
+ module DataMapper
2
+ module Constraints
3
+ module Adapters
4
+ module DataObjectsAdapter
5
+ ##
6
+ # Determine if a constraint exists for a table
7
+ #
8
+ # @param storage_name [Symbol]
9
+ # name of table to check constraint on
10
+ # @param constraint_name [~String]
11
+ # name of constraint to check for
12
+ #
13
+ # @return [Boolean]
14
+ #
15
+ # @api private
16
+ def constraint_exists?(storage_name, constraint_name)
17
+ statement = DataMapper::Ext::String.compress_lines(<<-SQL)
18
+ SELECT COUNT(*)
19
+ FROM #{quote_name('information_schema')}.#{quote_name('table_constraints')}
20
+ WHERE #{quote_name('constraint_type')} = 'FOREIGN KEY'
21
+ AND #{quote_name('table_schema')} = ?
22
+ AND #{quote_name('table_name')} = ?
23
+ AND #{quote_name('constraint_name')} = ?
24
+ SQL
25
+
26
+ select(statement, schema_name, storage_name, constraint_name).first > 0
27
+ end
28
+
29
+ ##
30
+ # Create the constraint for a relationship
31
+ #
32
+ # @param relationship [Relationship]
33
+ # the relationship to create the constraint for
34
+ #
35
+ # @return [true, false]
36
+ # true if creating the constraints was successful
37
+ #
38
+ # @api semipublic
39
+ def create_relationship_constraint(relationship)
40
+ return false unless valid_relationship_for_constraint?(relationship)
41
+
42
+ source_storage_name = relationship.source_model.storage_name(name)
43
+ target_storage_name = relationship.target_model.storage_name(name)
44
+ constraint_name = constraint_name(source_storage_name, relationship.name)
45
+
46
+ return false if constraint_exists?(source_storage_name, constraint_name)
47
+
48
+ constraint_type =
49
+ case relationship.inverse.constraint
50
+ when :protect then 'NO ACTION'
51
+ # TODO: support :cascade as an option:
52
+ # (destroy doesn't communicate the UPDATE constraint)
53
+ when :destroy, :destroy! then 'CASCADE'
54
+ when :set_nil then 'SET NULL'
55
+ end
56
+
57
+ return false if constraint_type.nil?
58
+
59
+ source_keys = relationship.source_key.map { |p| property_to_column_name(p, false) }
60
+ target_keys = relationship.target_key.map { |p| property_to_column_name(p, false) }
61
+
62
+ create_constraints_statement = create_constraints_statement(
63
+ constraint_name,
64
+ constraint_type,
65
+ source_storage_name,
66
+ source_keys,
67
+ target_storage_name,
68
+ target_keys
69
+ )
70
+
71
+ execute(create_constraints_statement)
72
+ end
73
+
74
+ ##
75
+ # Remove the constraint for a relationship
76
+ #
77
+ # @param relationship [Relationship]
78
+ # the relationship to remove the constraint for
79
+ #
80
+ # @return [true, false]
81
+ # true if destroying the constraint was successful
82
+ #
83
+ # @api semipublic
84
+ def destroy_relationship_constraint(relationship)
85
+ return false unless valid_relationship_for_constraint?(relationship)
86
+
87
+ storage_name = relationship.source_model.storage_name(name)
88
+ constraint_name = constraint_name(storage_name, relationship.name)
89
+
90
+ return false unless constraint_exists?(storage_name, constraint_name)
91
+
92
+ destroy_constraints_statement =
93
+ destroy_constraints_statement(storage_name, constraint_name)
94
+
95
+ execute(destroy_constraints_statement)
96
+ end
97
+
98
+ ##
99
+ # Check to see if the relationship's constraints can be used
100
+ #
101
+ # Only one-to-one, one-to-many, and many-to-many relationships
102
+ # can be used for constraints. They must also be in the same
103
+ # repository as the adapter is connected to.
104
+ #
105
+ # @param relationship [Relationship]
106
+ # the relationship to check
107
+ #
108
+ # @return [true, false]
109
+ # true if a constraint can be established for relationship
110
+ #
111
+ # @api private
112
+ private def valid_relationship_for_constraint?(relationship)
113
+ return false unless relationship.source_repository_name == name || relationship.source_repository_name.nil?
114
+ return false unless relationship.target_repository_name == name || relationship.target_repository_name.nil?
115
+ return false unless relationship.is_a?(Associations::ManyToOne::Relationship)
116
+
117
+ true
118
+ end
119
+
120
+ module SQL
121
+ # Generates the SQL statement to create a constraint
122
+ #
123
+ # @param [String] constraint_name
124
+ # name of the foreign key constraint
125
+ # @param [String] constraint_type
126
+ # type of constraint to ALTER source_storage_name with
127
+ # @param [String] source_storage_name
128
+ # name of table to ALTER with constraint
129
+ # @param [Array(String)] source_keys
130
+ # columns in source_storage_name that refer to foreign table
131
+ # @param [String] target_storage_name
132
+ # target table of the constraint
133
+ # @param [Array(String)] target_keys
134
+ # columns the target table that are referred to
135
+ #
136
+ # @return [String]
137
+ # SQL DDL Statement to create a constraint
138
+ #
139
+ # @api private
140
+ private def create_constraints_statement(constraint_name, constraint_type, source_storage_name, source_keys, target_storage_name, target_keys)
141
+ DataMapper::Ext::String.compress_lines(<<-SQL)
142
+ ALTER TABLE #{quote_name(source_storage_name)}
143
+ ADD CONSTRAINT #{quote_name(constraint_name)}
144
+ FOREIGN KEY (#{source_keys.join(', ')})
145
+ REFERENCES #{quote_name(target_storage_name)} (#{target_keys.join(', ')})
146
+ ON DELETE #{constraint_type}
147
+ ON UPDATE #{constraint_type}
148
+ SQL
149
+ end
150
+
151
+ ##
152
+ # Generates the SQL statement to destroy a constraint
153
+ #
154
+ # @param [String] storage_name
155
+ # name of table to constrain
156
+ # @param [String] constraint_name
157
+ # name of foreign key constraint
158
+ #
159
+ # @return [String]
160
+ # SQL DDL Statement to destroy a constraint
161
+ #
162
+ # @api private
163
+ private def destroy_constraints_statement(storage_name, constraint_name)
164
+ DataMapper::Ext::String.compress_lines(<<-SQL)
165
+ ALTER TABLE #{quote_name(storage_name)}
166
+ DROP CONSTRAINT #{quote_name(constraint_name)}
167
+ SQL
168
+ end
169
+
170
+ ##
171
+ # generates a unique constraint name given a table and a relationships
172
+ #
173
+ # @param [String] storage_name
174
+ # name of table to constrain
175
+ # @param [String] relationship_name
176
+ # name of the relationship to constrain
177
+ #
178
+ # @return [String]
179
+ # name of the constraint
180
+ #
181
+ # @api private
182
+ private def constraint_name(storage_name, relationship_name)
183
+ identifier = "#{storage_name}_#{relationship_name}"[0, self.class::IDENTIFIER_MAX_LENGTH - 3]
184
+ "#{identifier}_fk"
185
+ end
186
+ end
187
+
188
+ include SQL
189
+ end
190
+ end
191
+ end
192
+ end
@@ -0,0 +1,45 @@
1
+ module DataMapper
2
+ module Constraints
3
+ module Adapters
4
+ module Extension
5
+ # Include the corresponding Constraints module into a adapter class
6
+ #
7
+ # @param [Symbol] const_name
8
+ # demodulized name of the adapter class to include corresponding
9
+ # constraints module into
10
+ #
11
+ # TODO: come up with a better way to include modules
12
+ # into all currently loaded and subsequently loaded Adapters
13
+ #
14
+ # @api private
15
+ def include_constraint_api(const_name)
16
+ require constraint_extensions(const_name)
17
+
18
+ if Constraints::Adapters.const_defined?(const_name)
19
+ adapter = const_get(const_name)
20
+ constraint_module = Constraints::Adapters.const_get(const_name)
21
+ adapter.class_eval { include constraint_module }
22
+ end
23
+ rescue LoadError
24
+ # Silently ignore the fact that no adapter extensions could be required
25
+ # This means that the adapter in use doesn't support constraints
26
+ end
27
+
28
+ # @api private
29
+ private def constraint_extensions(const_name)
30
+ name = adapter_name(const_name)
31
+ name = 'do' if name == 'dataobjects'
32
+ "data_mapper/constraints/adapters/#{name}_adapter"
33
+ end
34
+
35
+ # @api private
36
+ private def const_added(const_name)
37
+ include_constraint_api(const_name)
38
+ super
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ Adapters.extend Constraints::Adapters::Extension
45
+ end
@@ -0,0 +1,26 @@
1
+ require 'data_mapper/constraints/adapters/do_adapter'
2
+
3
+ module DataMapper
4
+ module Constraints
5
+ module Adapters
6
+ module MysqlAdapter
7
+ include SQL, DataObjectsAdapter
8
+
9
+ module SQL
10
+ ##
11
+ # MySQL specific query to drop a foreign key
12
+ #
13
+ # @see DataMapper::Constraints::Adapters::DataObjectsAdapter#destroy_constraints_statement
14
+ #
15
+ # @api private
16
+ private def destroy_constraints_statement(storage_name, constraint_name)
17
+ DataMapper::Ext::String.compress_lines(<<-SQL)
18
+ ALTER TABLE #{quote_name(storage_name)}
19
+ DROP FOREIGN KEY #{quote_name(constraint_name)}
20
+ SQL
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,53 @@
1
+ require 'data_mapper/constraints/adapters/do_adapter'
2
+
3
+ module DataMapper
4
+ module Constraints
5
+ module Adapters
6
+ module OracleAdapter
7
+ include DataObjectsAdapter
8
+
9
+ # oracle does not provide the information_schema table
10
+ # To question internal state like postgres or mysql
11
+ #
12
+ # @see DataMapper::Constraints::Adapters::DataObjectsAdapter
13
+ #
14
+ # @api private
15
+ def constraint_exists?(storage_name, constraint_name)
16
+ statement = DataMapper::Ext::String.compress_lines(<<-SQL)
17
+ SELECT COUNT(*)
18
+ FROM USER_CONSTRAINTS
19
+ WHERE table_name = ?
20
+ AND constraint_name = ?
21
+ SQL
22
+
23
+ select(statement, oracle_upcase(storage_name)[0, self.class::IDENTIFIER_MAX_LENGTH].gsub('"', '_'), oracle_upcase(constraint_name)[0, self.class::IDENTIFIER_MAX_LENGTH].gsub('"', '_')).first > 0
24
+ end
25
+
26
+ # @see DataMapper::Constraints::Adapters::DataObjectsAdapter#create_constraints_statement
27
+ #
28
+ # @api private
29
+ #
30
+ # TODO: is it desirable to always set `INITIALLY DEFERRED DEFERRABLE`?
31
+ def create_constraints_statement(constraint_name, constraint_type, source_storage_name, source_keys, target_storage_name, target_keys)
32
+ DataMapper::Ext::String.compress_lines(<<-SQL)
33
+ ALTER TABLE #{quote_name(source_storage_name)}
34
+ ADD CONSTRAINT #{quote_name(constraint_name)}
35
+ FOREIGN KEY (#{source_keys.join(', ')})
36
+ REFERENCES #{quote_name(target_storage_name)} (#{target_keys.join(', ')})
37
+ #{"ON DELETE #{constraint_type}" if constraint_type && constraint_type != 'NO ACTION'}
38
+ INITIALLY DEFERRED DEFERRABLE
39
+ SQL
40
+ end
41
+
42
+ # @api private
43
+ def destroy_constraints_statement(storage_name, constraint_name)
44
+ DataMapper::Ext::String.compress_lines(<<-SQL)
45
+ ALTER TABLE #{quote_name(storage_name)}
46
+ DROP CONSTRAINT #{quote_name(constraint_name)}
47
+ CASCADE
48
+ SQL
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,13 @@
1
+ require 'data_mapper/constraints/adapters/do_adapter'
2
+
3
+ module DataMapper
4
+ module Constraints
5
+ module Adapters
6
+
7
+ module PostgresAdapter
8
+ include DataObjectsAdapter
9
+ end
10
+
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,22 @@
1
+ module DataMapper
2
+ module Constraints
3
+ module Adapters
4
+ module SqliteAdapter
5
+ # @api private
6
+ def constraint_exists?(*)
7
+ false
8
+ end
9
+
10
+ # @api private
11
+ def create_relationship_constraint(*)
12
+ false
13
+ end
14
+
15
+ # @api private
16
+ def destroy_relationship_constraint(*)
17
+ false
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,11 @@
1
+ require 'data_mapper/constraints/adapters/do_adapter'
2
+
3
+ module DataMapper
4
+ module Constraints
5
+ module Adapters
6
+ module SqlserverAdapter
7
+ include DataObjectsAdapter
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,34 @@
1
+ # TODO: figure out some other (less tightly coupled) way to ensure that
2
+ # dm-migrations' method implementations are loaded before this file
3
+ require 'dm-migrations/auto_migration'
4
+
5
+ module DataMapper
6
+ module Constraints
7
+ module Migrations
8
+ module Model
9
+ # @api private
10
+ def auto_migrate_constraints_up(repository_name = self.repository_name)
11
+ # TODO: this check should not be here
12
+ return if respond_to?(:is_remixable?) && is_remixable?
13
+
14
+ relationships(repository_name).each do |relationship|
15
+ relationship.auto_migrate_constraints_up(repository_name)
16
+ end
17
+ end
18
+
19
+ # @api private
20
+ def auto_migrate_constraints_down(repository_name = self.repository_name)
21
+ return unless storage_exists?(repository_name)
22
+ # TODO: this check should not be here
23
+ return if respond_to?(:is_remixable?) && is_remixable?
24
+
25
+ relationships(repository_name).each do |relationship|
26
+ relationship.auto_migrate_constraints_down(repository_name)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ Model.append_extensions Constraints::Migrations::Model
34
+ end
@@ -0,0 +1,41 @@
1
+ module DataMapper
2
+ module Constraints
3
+ module Migrations
4
+ module Relationship
5
+ # @api private
6
+ def auto_migrate_constraints_up(_repository_name)
7
+ # no-op
8
+ end
9
+
10
+ # @api private
11
+ def auto_migrate_constraints_down(_repository_name)
12
+ # no-op
13
+ end
14
+
15
+ module ManyToOne
16
+ # @api private
17
+ def auto_migrate_constraints_up(repository_name)
18
+ adapter = DataMapper.repository(repository_name)&.adapter
19
+ adapter&.create_relationship_constraint(self)
20
+ self
21
+ end
22
+
23
+ # @api private
24
+ def auto_migrate_constraints_down(repository_name)
25
+ adapter = DataMapper.repository(repository_name)&.adapter
26
+ adapter&.destroy_relationship_constraint(self)
27
+ self
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ Associations::Relationship.class_eval do
35
+ include Constraints::Migrations::Relationship
36
+ end
37
+
38
+ Associations::ManyToOne::Relationship.class_eval do
39
+ include Constraints::Migrations::Relationship::ManyToOne
40
+ end
41
+ end
@@ -0,0 +1,44 @@
1
+ module DataMapper
2
+ module Constraints
3
+ module Migrations
4
+ module SingletonMethods
5
+ def auto_migrate!(repository_name = nil)
6
+ auto_migrate_constraints_down(repository_name)
7
+ # TODO: Model#auto_migrate! drops and adds constraints, as well.
8
+ # is that an avoidable duplication?
9
+ super
10
+ auto_migrate_constraints_up(repository_name)
11
+ self
12
+ end
13
+
14
+ private def auto_migrate_down!(repository_name = nil)
15
+ auto_migrate_constraints_down(repository_name)
16
+ super
17
+ self
18
+ end
19
+
20
+ private def auto_migrate_up!(repository_name = nil)
21
+ super
22
+ auto_migrate_constraints_up(repository_name)
23
+ self
24
+ end
25
+
26
+ # @api private
27
+ private def auto_migrate_constraints_up(repository_name = nil)
28
+ DataMapper::Model.descendants.each do |model|
29
+ model.auto_migrate_constraints_up(repository_name || model.default_repository_name)
30
+ end
31
+ end
32
+
33
+ # @api private
34
+ private def auto_migrate_constraints_down(repository_name = nil)
35
+ DataMapper::Model.descendants.each do |model|
36
+ model.auto_migrate_constraints_down(repository_name || model.default_repository_name)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ extend Constraints::Migrations::SingletonMethods
44
+ end
@@ -0,0 +1,48 @@
1
+ require 'data_mapper/constraints/relationship/one_to_many'
2
+
3
+ module DataMapper
4
+ module Constraints
5
+ module Relationship
6
+ module ManyToMany
7
+
8
+ private def one_to_many_options
9
+ super.merge(constraint: @constraint)
10
+ end
11
+
12
+ # Checks that the constraint type is appropriate to the relationship
13
+ #
14
+ # @param [Fixnum] cardinality
15
+ # cardinality of relationship
16
+ # @param [Symbol] name
17
+ # name of relationship to evaluate constraint of
18
+ # @param [Hash] options
19
+ # options hash
20
+ #
21
+ # @option *args :constraint[Symbol]
22
+ # one of VALID_CONSTRAINT_VALUES
23
+ #
24
+ # @raise ArgumentError
25
+ # if @option :constraint is not one of VALID_CONSTRAINT_TYPES
26
+ #
27
+ # @return [Undefined]
28
+ #
29
+ # @api private
30
+ private def assert_valid_constraint
31
+ super
32
+
33
+ # TODO: is any constraint valid for a m:m relationship?
34
+ return unless @constraint == :set_nil
35
+
36
+ raise ArgumentError, "#{@constraint} is not a valid constraint type for #{self.class}"
37
+ end
38
+
39
+ end
40
+ end
41
+ end
42
+
43
+ Associations::ManyToMany::Relationship::OPTIONS << :constraint
44
+
45
+ Associations::ManyToMany::Relationship.class_eval do
46
+ include Constraints::Relationship::ManyToMany
47
+ end
48
+ end
@@ -0,0 +1,79 @@
1
+ module DataMapper
2
+ module Constraints
3
+ module Relationship
4
+ module OneToMany
5
+ attr_reader :constraint
6
+
7
+ # @api private
8
+ def enforce_destroy_constraint(resource)
9
+ return true unless (association = get(resource))
10
+
11
+ constraint = self.constraint
12
+
13
+ case constraint
14
+ when :protect
15
+ Array(association).empty?
16
+ when :destroy, :destroy!
17
+ association.__send__(constraint)
18
+ when :set_nil
19
+ Array(association).all? do |r|
20
+ r.update(inverse => nil)
21
+ end
22
+ when :skip
23
+ true # do nothing
24
+ end
25
+ end
26
+
27
+ ##
28
+ # Adds the delete constraint options to a relationship
29
+ #
30
+ # @param args [*args] Arguments passed to Relationship#initialize
31
+ #
32
+ # @return [nil]
33
+ #
34
+ # @api private
35
+ private def initialize(*args)
36
+ super
37
+ set_constraint
38
+ assert_valid_constraint
39
+ end
40
+
41
+ private def set_constraint
42
+ @constraint = @options.fetch(:constraint, :protect) || :skip
43
+ end
44
+
45
+ # Checks that the constraint type is appropriate to the relationship
46
+ #
47
+ # @param [Fixnum] cardinality
48
+ # cardinality of relationship
49
+ # @param [Symbol] name
50
+ # name of relationship to evaluate constraint of
51
+ # @param [Hash] options
52
+ # options hash
53
+ #
54
+ # @option *args :constraint[Symbol]
55
+ # one of VALID_CONSTRAINT_VALUES
56
+ #
57
+ # @raise ArgumentError
58
+ # if @option :constraint is not one of VALID_CONSTRAINT_VALUES
59
+ #
60
+ # @return [Undefined]
61
+ #
62
+ # @api semipublic
63
+ private def assert_valid_constraint
64
+ return unless @constraint
65
+
66
+ return if VALID_CONSTRAINT_VALUES.include?(@constraint)
67
+
68
+ raise ArgumentError, ":constraint option must be one of #{VALID_CONSTRAINT_VALUES.to_a.join(', ')}"
69
+ end
70
+ end
71
+ end
72
+ end
73
+
74
+ Associations::OneToMany::Relationship::OPTIONS << :constraint
75
+
76
+ Associations::OneToMany::Relationship.class_eval do
77
+ include Constraints::Relationship::OneToMany
78
+ end
79
+ end
@@ -0,0 +1,30 @@
1
+ module DataMapper
2
+ module Constraints
3
+ module Resource
4
+ def before_destroy_hook
5
+ enforce_destroy_constraints
6
+ super
7
+ end
8
+
9
+ # Check delete constraints prior to destroying a dm resource or collection
10
+ #
11
+ # @note
12
+ # - It only considers a relationship's constraints if this is the parent model (ie a child shouldn't delete a parent)
13
+ # - Many to Many Relationships are skipped, as they are evaluated by their underlying 1:M relationships
14
+ #
15
+ # @return [nil]
16
+ #
17
+ # @api semi-public
18
+ private def enforce_destroy_constraints
19
+ relationships.each do |relationship|
20
+ next unless relationship.respond_to?(:enforce_destroy_constraint)
21
+
22
+ constraint_satisfied = relationship.enforce_destroy_constraint(self)
23
+
24
+ throw(:halt, false) unless constraint_satisfied
25
+ end
26
+ end
27
+ end
28
+ end
29
+ Model.append_inclusions Constraints::Resource
30
+ end
@@ -0,0 +1,5 @@
1
+ module DataMapper
2
+ module Constraints
3
+ VERSION = '1.3.0.beta'.freeze
4
+ end
5
+ end