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.
Files changed (27) hide show
  1. data/Gemfile +8 -8
  2. data/README.rdoc +23 -21
  3. data/Rakefile +1 -1
  4. data/VERSION +1 -1
  5. data/dm-constraints.gemspec +36 -39
  6. data/lib/data_mapper/constraints/adapters/abstract_adapter.rb +36 -0
  7. data/lib/{dm-constraints/adapters/dm-do-adapter.rb → data_mapper/constraints/adapters/do_adapter.rb} +48 -39
  8. data/lib/data_mapper/constraints/adapters/extension.rb +49 -0
  9. data/lib/{dm-constraints/adapters/dm-mysql-adapter.rb → data_mapper/constraints/adapters/mysql_adapter.rb} +2 -8
  10. data/lib/{dm-constraints/adapters/dm-oracle-adapter.rb → data_mapper/constraints/adapters/oracle_adapter.rb} +9 -2
  11. data/lib/{dm-constraints/adapters/dm-postgres-adapter.rb → data_mapper/constraints/adapters/postgres_adapter.rb} +1 -1
  12. data/lib/{dm-constraints/adapters/dm-sqlite-adapter.rb → data_mapper/constraints/adapters/sqlite_adapter.rb} +4 -0
  13. data/lib/{dm-constraints/adapters/dm-sqlserver-adapter.rb → data_mapper/constraints/adapters/sqlserver_adapter.rb} +1 -1
  14. data/lib/data_mapper/constraints/migrations/model.rb +36 -0
  15. data/lib/data_mapper/constraints/migrations/relationship.rb +42 -0
  16. data/lib/data_mapper/constraints/migrations/singleton_methods.rb +48 -0
  17. data/lib/data_mapper/constraints/relationship/many_to_many.rb +50 -0
  18. data/lib/data_mapper/constraints/relationship/one_to_many.rb +83 -0
  19. data/lib/data_mapper/constraints/resource.rb +34 -0
  20. data/lib/dm-constraints.rb +12 -88
  21. data/spec/integration/constraints_spec.rb +4 -2
  22. data/spec/isolated/require_spec.rb +7 -4
  23. metadata +79 -44
  24. data/lib/dm-constraints/adapters/dm-abstract-adapter.rb +0 -22
  25. data/lib/dm-constraints/delete_constraint.rb +0 -115
  26. data/lib/dm-constraints/migrations.rb +0 -60
  27. data/lib/dm-constraints/relationships.rb +0 -41
@@ -1,4 +1,4 @@
1
- require 'dm-constraints/adapters/dm-do-adapter'
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 intenal state like postgres or mysql
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)}
@@ -1,4 +1,4 @@
1
- require 'dm-constraints/adapters/dm-do-adapter'
1
+ require 'data_mapper/constraints/adapters/do_adapter'
2
2
 
3
3
  module DataMapper
4
4
  module Constraints
@@ -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
@@ -1,4 +1,4 @@
1
- require 'dm-constraints/adapters/dm-do-adapter'
1
+ require 'data_mapper/constraints/adapters/do_adapter'
2
2
 
3
3
  module DataMapper
4
4
  module Constraints
@@ -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
@@ -1,95 +1,19 @@
1
1
  require 'dm-core'
2
2
 
3
- require 'dm-constraints/migrations'
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
- module DataMapper
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
- extend Constraints::Migrations::SingletonMethods
9
+ require 'data_mapper/constraints/relationship/one_to_many'
10
+ require 'data_mapper/constraints/relationship/many_to_many'
11
11
 
12
- module Model
13
- extend DataMapper::Constraints::Migrations::Model
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
- include DeleteConstraint
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