dm-constraints 1.1.0 → 1.2.0.rc1
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/Gemfile +8 -8
- data/README.rdoc +23 -21
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/dm-constraints.gemspec +36 -39
- data/lib/data_mapper/constraints/adapters/abstract_adapter.rb +36 -0
- data/lib/{dm-constraints/adapters/dm-do-adapter.rb → data_mapper/constraints/adapters/do_adapter.rb} +48 -39
- data/lib/data_mapper/constraints/adapters/extension.rb +49 -0
- data/lib/{dm-constraints/adapters/dm-mysql-adapter.rb → data_mapper/constraints/adapters/mysql_adapter.rb} +2 -8
- data/lib/{dm-constraints/adapters/dm-oracle-adapter.rb → data_mapper/constraints/adapters/oracle_adapter.rb} +9 -2
- data/lib/{dm-constraints/adapters/dm-postgres-adapter.rb → data_mapper/constraints/adapters/postgres_adapter.rb} +1 -1
- data/lib/{dm-constraints/adapters/dm-sqlite-adapter.rb → data_mapper/constraints/adapters/sqlite_adapter.rb} +4 -0
- data/lib/{dm-constraints/adapters/dm-sqlserver-adapter.rb → data_mapper/constraints/adapters/sqlserver_adapter.rb} +1 -1
- data/lib/data_mapper/constraints/migrations/model.rb +36 -0
- data/lib/data_mapper/constraints/migrations/relationship.rb +42 -0
- data/lib/data_mapper/constraints/migrations/singleton_methods.rb +48 -0
- data/lib/data_mapper/constraints/relationship/many_to_many.rb +50 -0
- data/lib/data_mapper/constraints/relationship/one_to_many.rb +83 -0
- data/lib/data_mapper/constraints/resource.rb +34 -0
- data/lib/dm-constraints.rb +12 -88
- data/spec/integration/constraints_spec.rb +4 -2
- data/spec/isolated/require_spec.rb +7 -4
- metadata +79 -44
- data/lib/dm-constraints/adapters/dm-abstract-adapter.rb +0 -22
- data/lib/dm-constraints/delete_constraint.rb +0 -115
- data/lib/dm-constraints/migrations.rb +0 -60
- data/lib/dm-constraints/relationships.rb +0 -41
@@ -1,4 +1,4 @@
|
|
1
|
-
require '
|
1
|
+
require 'data_mapper/constraints/adapters/do_adapter'
|
2
2
|
|
3
3
|
module DataMapper
|
4
4
|
module Constraints
|
@@ -8,8 +8,10 @@ module DataMapper
|
|
8
8
|
include DataObjectsAdapter
|
9
9
|
|
10
10
|
# oracle does not provide the information_schema table
|
11
|
-
# To question
|
11
|
+
# To question internal state like postgres or mysql
|
12
|
+
#
|
12
13
|
# @see DataMapper::Constraints::Adapters::DataObjectsAdapter
|
14
|
+
#
|
13
15
|
# @api private
|
14
16
|
def constraint_exists?(storage_name, constraint_name)
|
15
17
|
statement = DataMapper::Ext::String.compress_lines(<<-SQL)
|
@@ -24,6 +26,10 @@ module DataMapper
|
|
24
26
|
|
25
27
|
|
26
28
|
# @see DataMapper::Constraints::Adapters::DataObjectsAdapter#create_constraints_statement
|
29
|
+
#
|
30
|
+
# @api private
|
31
|
+
#
|
32
|
+
# TODO: is it desirable to always set `INITIALLY DEFERRED DEFERRABLE`?
|
27
33
|
def create_constraints_statement(storage_name, constraint_name, constraint_type, foreign_keys, reference_storage_name, reference_keys)
|
28
34
|
DataMapper::Ext::String.compress_lines(<<-SQL)
|
29
35
|
ALTER TABLE #{quote_name(storage_name)}
|
@@ -34,6 +40,7 @@ module DataMapper
|
|
34
40
|
SQL
|
35
41
|
end
|
36
42
|
|
43
|
+
# @api private
|
37
44
|
def destroy_constraints_statement(storage_name, constraint_name)
|
38
45
|
DataMapper::Ext::String.compress_lines(<<-SQL)
|
39
46
|
ALTER TABLE #{quote_name(storage_name)}
|
@@ -4,17 +4,21 @@ module DataMapper
|
|
4
4
|
|
5
5
|
module SqliteAdapter
|
6
6
|
|
7
|
+
# @api private
|
7
8
|
def constraint_exists?(*)
|
8
9
|
false
|
9
10
|
end
|
10
11
|
|
12
|
+
# @api private
|
11
13
|
def create_relationship_constraint(*)
|
12
14
|
false
|
13
15
|
end
|
14
16
|
|
17
|
+
# @api private
|
15
18
|
def destroy_relationship_constraint(*)
|
16
19
|
false
|
17
20
|
end
|
21
|
+
|
18
22
|
end
|
19
23
|
|
20
24
|
end
|
@@ -0,0 +1,36 @@
|
|
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
|
+
|
10
|
+
# @api private
|
11
|
+
def auto_migrate_constraints_up(repository_name = self.repository_name)
|
12
|
+
# TODO: this check should not be here
|
13
|
+
return if self.respond_to?(:is_remixable?) && self.is_remixable?
|
14
|
+
|
15
|
+
relationships(repository_name).each do |relationship|
|
16
|
+
relationship.auto_migrate_constraints_up(repository_name)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# @api private
|
21
|
+
def auto_migrate_constraints_down(repository_name = self.repository_name)
|
22
|
+
return unless storage_exists?(repository_name)
|
23
|
+
# TODO: this check should not be here
|
24
|
+
return if self.respond_to?(:is_remixable?) && self.is_remixable?
|
25
|
+
|
26
|
+
relationships(repository_name).each do |relationship|
|
27
|
+
relationship.auto_migrate_constraints_down(repository_name)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end # module Model
|
32
|
+
end # module Migrations
|
33
|
+
end # module Constraints
|
34
|
+
|
35
|
+
Model.append_extensions Constraints::Migrations::Model
|
36
|
+
end # module DataMapper
|
@@ -0,0 +1,42 @@
|
|
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
|
+
|
31
|
+
end # module Relationship
|
32
|
+
end # module Migrations
|
33
|
+
end # module Constraints
|
34
|
+
|
35
|
+
Associations::Relationship.class_eval do
|
36
|
+
include Constraints::Migrations::Relationship
|
37
|
+
end
|
38
|
+
|
39
|
+
Associations::ManyToOne::Relationship.class_eval do
|
40
|
+
include Constraints::Migrations::Relationship::ManyToOne
|
41
|
+
end
|
42
|
+
end # module DataMapper
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Constraints
|
3
|
+
module Migrations
|
4
|
+
module SingletonMethods
|
5
|
+
|
6
|
+
def auto_migrate!(repository_name = nil)
|
7
|
+
auto_migrate_constraints_down(repository_name)
|
8
|
+
# TODO: Model#auto_migrate! drops and adds constraints, as well.
|
9
|
+
# is that an avoidable duplication?
|
10
|
+
super
|
11
|
+
auto_migrate_constraints_up(repository_name)
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def auto_migrate_down!(repository_name = nil)
|
18
|
+
auto_migrate_constraints_down(repository_name)
|
19
|
+
super
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
def auto_migrate_up!(repository_name = nil)
|
24
|
+
super
|
25
|
+
auto_migrate_constraints_up(repository_name)
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
# @api private
|
30
|
+
def auto_migrate_constraints_up(repository_name = nil)
|
31
|
+
DataMapper::Model.descendants.each do |model|
|
32
|
+
model.auto_migrate_constraints_up(repository_name || model.default_repository_name)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# @api private
|
37
|
+
def auto_migrate_constraints_down(repository_name = nil)
|
38
|
+
DataMapper::Model.descendants.each do |model|
|
39
|
+
model.auto_migrate_constraints_down(repository_name || model.default_repository_name)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end # module SingletonMethods
|
44
|
+
end # module Migrations
|
45
|
+
end # module Constraints
|
46
|
+
|
47
|
+
extend Constraints::Migrations::SingletonMethods
|
48
|
+
end # module DataMapper
|
@@ -0,0 +1,50 @@
|
|
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
|
9
|
+
|
10
|
+
def one_to_many_options
|
11
|
+
super.merge(:constraint => @constraint)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Checks that the constraint type is appropriate to the relationship
|
15
|
+
#
|
16
|
+
# @param [Fixnum] cardinality
|
17
|
+
# cardinality of relationship
|
18
|
+
# @param [Symbol] name
|
19
|
+
# name of relationship to evaluate constraint of
|
20
|
+
# @param [Hash] options
|
21
|
+
# options hash
|
22
|
+
#
|
23
|
+
# @option *args :constraint[Symbol]
|
24
|
+
# one of VALID_CONSTRAINT_VALUES
|
25
|
+
#
|
26
|
+
# @raise ArgumentError
|
27
|
+
# if @option :constraint is not one of VALID_CONSTRAINT_TYPES
|
28
|
+
#
|
29
|
+
# @return [Undefined]
|
30
|
+
#
|
31
|
+
# @api private
|
32
|
+
def assert_valid_constraint
|
33
|
+
super
|
34
|
+
|
35
|
+
# TODO: is any constraint valid for a m:m relationship?
|
36
|
+
if @constraint == :set_nil
|
37
|
+
raise ArgumentError, "#{@constraint} is not a valid constraint type for #{self.class}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end # module ManyToMany
|
42
|
+
end # module Relationship
|
43
|
+
end # module Constraints
|
44
|
+
|
45
|
+
Associations::ManyToMany::Relationship::OPTIONS << :constraint
|
46
|
+
|
47
|
+
Associations::ManyToMany::Relationship.class_eval do
|
48
|
+
include Constraints::Relationship::ManyToMany
|
49
|
+
end
|
50
|
+
end # module DataMapper
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Constraints
|
3
|
+
module Relationship
|
4
|
+
module OneToMany
|
5
|
+
|
6
|
+
attr_reader :constraint
|
7
|
+
|
8
|
+
# @api private
|
9
|
+
def enforce_destroy_constraint(resource)
|
10
|
+
return true unless association = get(resource)
|
11
|
+
|
12
|
+
constraint = self.constraint
|
13
|
+
|
14
|
+
case constraint
|
15
|
+
when :protect
|
16
|
+
Array(association).empty?
|
17
|
+
when :destroy, :destroy!
|
18
|
+
association.__send__(constraint)
|
19
|
+
when :set_nil
|
20
|
+
Array(association).all? do |resource|
|
21
|
+
resource.update(inverse => nil)
|
22
|
+
end
|
23
|
+
when :skip
|
24
|
+
true # do nothing
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
##
|
31
|
+
# Adds the delete constraint options to a relationship
|
32
|
+
#
|
33
|
+
# @param params [*ARGS] Arguments passed to Relationship#initialize
|
34
|
+
#
|
35
|
+
# @return [nil]
|
36
|
+
#
|
37
|
+
# @api private
|
38
|
+
def initialize(*args)
|
39
|
+
super
|
40
|
+
set_constraint
|
41
|
+
assert_valid_constraint
|
42
|
+
end
|
43
|
+
|
44
|
+
def set_constraint
|
45
|
+
@constraint = @options.fetch(:constraint, :protect) || :skip
|
46
|
+
end
|
47
|
+
|
48
|
+
# Checks that the constraint type is appropriate to the relationship
|
49
|
+
#
|
50
|
+
# @param [Fixnum] cardinality
|
51
|
+
# cardinality of relationship
|
52
|
+
# @param [Symbol] name
|
53
|
+
# name of relationship to evaluate constraint of
|
54
|
+
# @param [Hash] options
|
55
|
+
# options hash
|
56
|
+
#
|
57
|
+
# @option *args :constraint[Symbol]
|
58
|
+
# one of VALID_CONSTRAINT_VALUES
|
59
|
+
#
|
60
|
+
# @raise ArgumentError
|
61
|
+
# if @option :constraint is not one of VALID_CONSTRAINT_VALUES
|
62
|
+
#
|
63
|
+
# @return [Undefined]
|
64
|
+
#
|
65
|
+
# @api semipublic
|
66
|
+
def assert_valid_constraint
|
67
|
+
return unless @constraint
|
68
|
+
|
69
|
+
unless VALID_CONSTRAINT_VALUES.include?(@constraint)
|
70
|
+
raise ArgumentError, ":constraint option must be one of #{VALID_CONSTRAINT_VALUES.to_a.join(', ')}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
end # module OneToMany
|
75
|
+
end # module Relationship
|
76
|
+
end # module Constraints
|
77
|
+
|
78
|
+
Associations::OneToMany::Relationship::OPTIONS << :constraint
|
79
|
+
|
80
|
+
Associations::OneToMany::Relationship.class_eval do
|
81
|
+
include Constraints::Relationship::OneToMany
|
82
|
+
end
|
83
|
+
end # module DataMapper
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Constraints
|
3
|
+
module Resource
|
4
|
+
def before_destroy_hook
|
5
|
+
enforce_destroy_constraints
|
6
|
+
super
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
# Check delete constraints prior to destroying a dm resource or collection
|
12
|
+
#
|
13
|
+
# @note
|
14
|
+
# - It only considers a relationship's constraints if this is the parent model (ie a child shouldn't delete a parent)
|
15
|
+
# - Many to Many Relationships are skipped, as they are evaluated by their underlying 1:M relationships
|
16
|
+
#
|
17
|
+
# @return [nil]
|
18
|
+
#
|
19
|
+
# @api semi-public
|
20
|
+
def enforce_destroy_constraints
|
21
|
+
relationships.each do |relationship|
|
22
|
+
next unless relationship.respond_to?(:enforce_destroy_constraint)
|
23
|
+
|
24
|
+
constraint_satisfied = relationship.enforce_destroy_constraint(self)
|
25
|
+
|
26
|
+
throw(:halt, false) unless constraint_satisfied
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end # module Resource
|
31
|
+
end # module Constraints
|
32
|
+
|
33
|
+
Model.append_inclusions Constraints::Resource
|
34
|
+
end # module DataMapper
|
data/lib/dm-constraints.rb
CHANGED
@@ -1,95 +1,19 @@
|
|
1
1
|
require 'dm-core'
|
2
2
|
|
3
|
-
require '
|
4
|
-
require 'dm-constraints/delete_constraint'
|
5
|
-
require 'dm-constraints/relationships'
|
6
|
-
require 'dm-constraints/adapters/dm-abstract-adapter'
|
3
|
+
require 'data_mapper/constraints/resource'
|
7
4
|
|
8
|
-
|
5
|
+
require 'data_mapper/constraints/migrations/model'
|
6
|
+
require 'data_mapper/constraints/migrations/relationship'
|
7
|
+
require 'data_mapper/constraints/migrations/singleton_methods'
|
9
8
|
|
10
|
-
|
9
|
+
require 'data_mapper/constraints/relationship/one_to_many'
|
10
|
+
require 'data_mapper/constraints/relationship/many_to_many'
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
append_extensions DataMapper::Constraints::Migrations::Model
|
15
|
-
end
|
12
|
+
require 'data_mapper/constraints/adapters/extension'
|
13
|
+
require 'data_mapper/constraints/adapters/abstract_adapter'
|
16
14
|
|
15
|
+
module DataMapper
|
17
16
|
module Constraints
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
module ClassMethods
|
22
|
-
include DeleteConstraint::ClassMethods
|
23
|
-
end
|
24
|
-
|
25
|
-
##
|
26
|
-
# Add before hooks to #has to check for proper constraint definitions
|
27
|
-
# Add before hooks to #destroy to properly constrain children
|
28
|
-
#
|
29
|
-
def self.included(model)
|
30
|
-
model.extend(ClassMethods)
|
31
|
-
model.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
32
|
-
before_class_method :has, :check_delete_constraint_type
|
33
|
-
|
34
|
-
if instance_methods.any? { |m| m.to_sym == :destroy }
|
35
|
-
before :destroy, :check_delete_constraints
|
36
|
-
end
|
37
|
-
RUBY
|
38
|
-
end
|
39
|
-
|
40
|
-
def self.include_constraint_api
|
41
|
-
DataMapper::Adapters::AbstractAdapter.descendants.each do |adapter_class|
|
42
|
-
DataMapper::Adapters.include_constraint_api(DataMapper::Inflector.demodulize(adapter_class.name))
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
Model.append_inclusions self
|
47
|
-
|
48
|
-
end # module Constraints
|
49
|
-
|
50
|
-
module Adapters
|
51
|
-
|
52
|
-
class AbstractAdapter
|
53
|
-
include DataMapper::Constraints::Adapters::AbstractAdapter
|
54
|
-
end
|
55
|
-
|
56
|
-
def self.include_constraint_api(const_name)
|
57
|
-
require constraint_extensions(const_name)
|
58
|
-
if Constraints::Adapters.const_defined?(const_name)
|
59
|
-
adapter = const_get(const_name)
|
60
|
-
adapter.send(:include, constraint_module(const_name))
|
61
|
-
end
|
62
|
-
rescue LoadError
|
63
|
-
# Silently ignore the fact that no adapter extensions could be required
|
64
|
-
# This means that the adapter in use doesn't support constraints
|
65
|
-
end
|
66
|
-
|
67
|
-
def self.constraint_module(const_name)
|
68
|
-
Constraints::Adapters.const_get(const_name)
|
69
|
-
end
|
70
|
-
|
71
|
-
class << self
|
72
|
-
private
|
73
|
-
|
74
|
-
# @api private
|
75
|
-
def constraint_extensions(const_name)
|
76
|
-
name = adapter_name(const_name)
|
77
|
-
name = 'do' if name == 'dataobjects'
|
78
|
-
"dm-constraints/adapters/dm-#{name}-adapter"
|
79
|
-
end
|
80
|
-
|
81
|
-
end
|
82
|
-
|
83
|
-
extendable do
|
84
|
-
# @api private
|
85
|
-
def const_added(const_name)
|
86
|
-
include_constraint_api(const_name)
|
87
|
-
super
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
end # module Adapters
|
92
|
-
|
93
|
-
Constraints.include_constraint_api
|
94
|
-
|
95
|
-
end # module DataMapper
|
17
|
+
VALID_CONSTRAINT_VALUES = [ :protect, :destroy, :destroy!, :set_nil, :skip ].to_set.freeze
|
18
|
+
end
|
19
|
+
end
|