composite_primary_keys 7.0.16 → 8.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. checksums.yaml +4 -4
  2. data/History.rdoc +600 -623
  3. data/lib/composite_primary_keys.rb +113 -115
  4. data/lib/composite_primary_keys/associations/association.rb +23 -23
  5. data/lib/composite_primary_keys/associations/association_scope.rb +73 -77
  6. data/lib/composite_primary_keys/associations/collection_association.rb +15 -0
  7. data/lib/composite_primary_keys/associations/has_many_association.rb +69 -56
  8. data/lib/composite_primary_keys/associations/has_many_through_association.rb +30 -28
  9. data/lib/composite_primary_keys/associations/join_dependency.rb +87 -89
  10. data/lib/composite_primary_keys/associations/join_dependency/join_association.rb +22 -22
  11. data/lib/composite_primary_keys/associations/preloader/association.rb +90 -78
  12. data/lib/composite_primary_keys/associations/preloader/belongs_to.rb +19 -19
  13. data/lib/composite_primary_keys/associations/singular_association.rb +15 -0
  14. data/lib/composite_primary_keys/attribute_methods.rb +9 -0
  15. data/lib/composite_primary_keys/attribute_methods/dirty.rb +29 -26
  16. data/lib/composite_primary_keys/attribute_methods/read.rb +19 -34
  17. data/lib/composite_primary_keys/attribute_methods/write.rb +30 -36
  18. data/lib/composite_primary_keys/attribute_set/builder.rb +20 -0
  19. data/lib/composite_primary_keys/base.rb +135 -129
  20. data/lib/composite_primary_keys/composite_arrays.rb +30 -30
  21. data/lib/composite_primary_keys/composite_predicates.rb +50 -50
  22. data/lib/composite_primary_keys/composite_relation.rb +48 -48
  23. data/lib/composite_primary_keys/connection_adapters/abstract/connection_specification_changes.rb +2 -4
  24. data/lib/composite_primary_keys/connection_adapters/postgresql_adapter.rb +46 -60
  25. data/lib/composite_primary_keys/core.rb +69 -47
  26. data/lib/composite_primary_keys/fixtures.rb +22 -22
  27. data/lib/composite_primary_keys/persistence.rb +56 -60
  28. data/lib/composite_primary_keys/relation.rb +68 -56
  29. data/lib/composite_primary_keys/relation/calculations.rb +79 -75
  30. data/lib/composite_primary_keys/relation/finder_methods.rb +175 -196
  31. data/lib/composite_primary_keys/relation/query_methods.rb +40 -40
  32. data/lib/composite_primary_keys/sanitization.rb +52 -52
  33. data/lib/composite_primary_keys/validations/uniqueness.rb +36 -37
  34. data/lib/composite_primary_keys/version.rb +8 -8
  35. data/tasks/databases/oracle.rake +25 -25
  36. data/tasks/databases/sqlserver.rake +27 -40
  37. data/test/abstract_unit.rb +113 -113
  38. data/test/connections/databases.ci.yml +15 -15
  39. data/test/connections/databases.example.yml +18 -18
  40. data/test/connections/native_oracle/connection.rb +11 -11
  41. data/test/connections/native_oracle_enhanced/connection.rb +16 -16
  42. data/test/connections/native_sqlserver/connection.rb +11 -14
  43. data/test/fixtures/comment.rb +7 -7
  44. data/test/fixtures/db_definitions/db2-create-tables.sql +125 -126
  45. data/test/fixtures/db_definitions/db2-drop-tables.sql +18 -18
  46. data/test/fixtures/db_definitions/mysql.sql +207 -208
  47. data/test/fixtures/db_definitions/oracle.drop.sql +45 -45
  48. data/test/fixtures/db_definitions/oracle.sql +222 -223
  49. data/test/fixtures/db_definitions/postgresql.sql +209 -210
  50. data/test/fixtures/db_definitions/sqlite.sql +196 -197
  51. data/test/fixtures/db_definitions/sqlserver.drop.sql +91 -94
  52. data/test/fixtures/db_definitions/sqlserver.sql +225 -232
  53. data/test/fixtures/dorm.rb +2 -2
  54. data/test/fixtures/employee.rb +5 -5
  55. data/test/fixtures/membership.rb +6 -6
  56. data/test/fixtures/membership_statuses.yml +16 -16
  57. data/test/fixtures/memberships.yml +10 -10
  58. data/test/fixtures/product_tariffs.yml +14 -14
  59. data/test/fixtures/reference_code.rb +7 -7
  60. data/test/fixtures/restaurants_suburb.rb +2 -2
  61. data/test/fixtures/suburb.rb +5 -5
  62. data/test/fixtures/topic.rb +5 -5
  63. data/test/fixtures/topic_source.rb +6 -6
  64. data/test/fixtures/topic_sources.yml +3 -3
  65. data/test/fixtures/topics.yml +8 -8
  66. data/test/fixtures/users.yml +10 -10
  67. data/test/test_associations.rb +295 -275
  68. data/test/test_attribute_methods.rb +63 -63
  69. data/test/test_attributes.rb +60 -60
  70. data/test/test_calculations.rb +37 -42
  71. data/test/test_callbacks.rb +99 -99
  72. data/test/test_create.rb +112 -112
  73. data/test/test_delete.rb +148 -152
  74. data/test/test_delete_all.rb +28 -26
  75. data/test/test_dumpable.rb +15 -15
  76. data/test/test_enum.rb +21 -20
  77. data/test/test_equal.rb +26 -26
  78. data/test/test_find.rb +118 -118
  79. data/test/test_habtm.rb +113 -113
  80. data/test/test_nested_attributes.rb +124 -124
  81. data/test/test_polymorphic.rb +26 -26
  82. data/test/test_predicates.rb +40 -40
  83. data/test/test_santiago.rb +23 -23
  84. data/test/test_suite.rb +33 -34
  85. data/test/test_touch.rb +23 -23
  86. data/test/test_tutorial_example.rb +21 -21
  87. data/test/test_update.rb +71 -71
  88. metadata +9 -13
  89. data/lib/composite_primary_keys/arel/visitors/to_sql.rb +0 -20
  90. data/lib/composite_primary_keys/associations/has_and_belongs_to_many_association.rb +0 -59
  91. data/lib/composite_primary_keys/associations/join_dependency/join_part.rb +0 -39
  92. data/lib/composite_primary_keys/associations/preloader/has_and_belongs_to_many.rb +0 -46
  93. data/lib/composite_primary_keys/connection_adapters/sqlserver_adapter.rb +0 -17
  94. data/lib/composite_primary_keys/locking/optimistic.rb +0 -55
  95. data/test/test_optimistic.rb +0 -18
@@ -1,19 +1,19 @@
1
- module ActiveRecord
2
- module Associations
3
- class Preloader
4
- class BelongsTo
5
- def query_scope(ids)
6
- # CPK
7
- # scope.where(association_key.in(ids))
8
-
9
- if association_key_name.is_a?(Array)
10
- predicate = cpk_in_predicate(table, association_key_name, ids)
11
- scope.where(predicate)
12
- else
13
- scope.where(association_key.in(ids))
14
- end
15
- end
16
- end
17
- end
18
- end
19
- end
1
+ module ActiveRecord
2
+ module Associations
3
+ class Preloader
4
+ class BelongsTo
5
+ def query_scope(ids)
6
+ # CPK
7
+ # scope.where(association_key.in(ids))
8
+
9
+ if association_key_name.is_a?(Array)
10
+ predicate = cpk_in_predicate(table, association_key_name, ids)
11
+ scope.where(predicate)
12
+ else
13
+ scope.where(association_key.in(ids))
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,15 @@
1
+ module CompositePrimaryKeys
2
+ module SingularAssociation
3
+ extend ActiveSupport::Concern
4
+ included do
5
+ def get_records_with_cpk_support
6
+ cpk_applies = (target && target.composite?) || (owner && owner.composite?)
7
+ return scope.limit(1).to_a if cpk_applies
8
+ get_records_without_cpk_support
9
+ end
10
+ alias_method_chain :get_records, :cpk_support
11
+ end
12
+ end
13
+ end
14
+
15
+ ActiveRecord::Associations::SingularAssociation.send(:include, CompositePrimaryKeys::SingularAssociation)
@@ -0,0 +1,9 @@
1
+ module ActiveRecord
2
+ module AttributeMethods
3
+ def has_attribute?(attr_name)
4
+ # CPK
5
+ # @attributes.key?(attr_name.to_s)
6
+ Array(attr_name).all?{|single_attr| @attributes.key?(single_attr.to_s) }
7
+ end
8
+ end
9
+ end
@@ -1,26 +1,29 @@
1
- module ActiveRecord
2
- module AttributeMethods
3
- module Dirty
4
- def write_attribute(attr, value)
5
- # CPK
6
- if attr.kind_of?(Array)
7
- # A *composite* attribute can't be marked as changed! So do nothing now.
8
- # We will come back in here with an *individual* attribute when Write#write_attribute looks through the individual attributes comprising this composite key:
9
- # [attr_name, value].transpose.map {|name,val| write_attribute(name, val)}
10
- else
11
- attr = attr.to_s
12
-
13
- save_changed_attribute(attr, value)
14
- end
15
-
16
- # Carry on.
17
- super(attr, value)
18
- end
19
- end
20
- end
21
- end
22
-
23
- ActiveRecord::Base.class_eval do
24
- alias :[]= :write_attribute
25
- public :[]=
26
- end
1
+ module ActiveRecord
2
+ module AttributeMethods
3
+ module Dirty
4
+ def write_attribute(attr, value)
5
+ # CPK
6
+ if attr.kind_of?(Array)
7
+ # A *composite* attribute can't be marked as changed! So do nothing now.
8
+ # We will come back in here with an *individual* attribute when Write#write_attribute looks through the individual attributes comprising this composite key:
9
+ value = [nil] * attr.length if value.nil?
10
+ [attr, value].transpose.map {|name,val| write_attribute(name, val)}
11
+ else
12
+ attr = attr.to_s
13
+
14
+ old_value = old_attribute_value(attr)
15
+
16
+ result = super
17
+ store_original_raw_attribute(attr)
18
+ save_changed_attribute(attr, old_value)
19
+ result
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ ActiveRecord::Base.class_eval do
27
+ alias :[]= :write_attribute
28
+ public :[]=
29
+ end
@@ -1,35 +1,20 @@
1
- module ActiveRecord
2
- module AttributeMethods
3
- module Read
4
- def read_attribute(attr_name)
5
- if attr_name.kind_of?(Array)
6
- attr_name.map {|name| read_attribute(name)}.to_composite_keys
7
- else
8
- # If it's cached, just return it
9
- # We use #[] first as a perf optimization for non-nil values. See https://gist.github.com/jonleighton/3552829.
10
- name = attr_name.to_s
11
- @attributes_cache[name] || @attributes_cache.fetch(name) {
12
- column = @column_types_override[name] if @column_types_override
13
- column ||= @column_types[name]
14
-
15
- return @attributes.fetch(name) {
16
- if name == 'id' && self.class.primary_key != name
17
- read_attribute(self.class.primary_key)
18
- end
19
- } unless column
20
-
21
- value = @attributes.fetch(name) {
22
- return block_given? ? yield(name) : nil
23
- }
24
-
25
- if self.class.cache_attribute?(name)
26
- @attributes_cache[name] = column.type_cast(value)
27
- else
28
- column.type_cast value
29
- end
30
- }
31
- end
32
- end
33
- end
34
- end
1
+ module ActiveRecord
2
+ module AttributeMethods
3
+ module Read
4
+ def read_attribute(attr_name, &block)
5
+ # CPK
6
+ # name = attr_name.to_s
7
+ # name = self.class.primary_key if name == 'id'
8
+ # @attributes.fetch_value(name, &block)
9
+
10
+ if attr_name.kind_of?(Array)
11
+ attr_name.map {|name| read_attribute(name)}.to_composite_keys
12
+ else
13
+ name = attr_name.to_s
14
+ name = self.class.primary_key if name == 'id' && !composite?
15
+ @attributes.fetch_value(name, &block)
16
+ end
17
+ end
18
+ end
19
+ end
35
20
  end
@@ -1,36 +1,30 @@
1
- module ActiveRecord
2
- module AttributeMethods
3
- module Write
4
- def write_attribute_with_type_cast(attr_name, value, type_cast_method)
5
- # CPK
6
- if attr_name.kind_of?(Array)
7
- value = [nil]*attr_name.length if value.nil?
8
- unless value.length == attr_name.length
9
- raise "Number of attr_names #{attr_name.inspect} and values #{value.inspect} do not match"
10
- end
11
- [attr_name, value].transpose.map {|name,val| write_attribute(name, val)}
12
- value
13
- else
14
- attr_name = attr_name.to_s
15
- # CPK
16
- # attr_name = self.class.primary_key if attr_name == 'id' && self.class.primary_key
17
- attr_name = self.class.primary_key if attr_name == 'id' && self.class.primary_key && !self.composite?
18
- @attributes_cache.delete(attr_name)
19
- column = column_for_attribute(attr_name)
20
-
21
- # If we're dealing with a binary column, write the data to the cache
22
- # so we don't attempt to typecast multiple times.
23
- if column && column.binary?
24
- @attributes_cache[attr_name] = value
25
- end
26
-
27
- if column || @attributes.has_key?(attr_name)
28
- @attributes[attr_name] = send(type_cast_method, column, value)
29
- else
30
- raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{attr_name}'"
31
- end
32
- end
33
- end
34
- end
35
- end
36
- end
1
+ module ActiveRecord
2
+ module AttributeMethods
3
+ module Write
4
+ def write_attribute_with_type_cast(attr_name, value, should_type_cast)
5
+ # CPK
6
+ if attr_name.kind_of?(Array)
7
+ value = [nil]*attr_name.length if value.nil?
8
+ unless value.length == attr_name.length
9
+ raise "Number of attr_names #{attr_name.inspect} and values #{value.inspect} do not match"
10
+ end
11
+ [attr_name, value].transpose.map {|name,val| write_attribute(name, val)}
12
+ value
13
+ else
14
+ attr_name = attr_name.to_s
15
+ # CPK
16
+ # attr_name = self.class.primary_key if attr_name == 'id' && self.class.primary_key
17
+ attr_name = self.class.primary_key if attr_name == 'id' && self.class.primary_key && !self.composite?
18
+
19
+ if should_type_cast
20
+ @attributes.write_from_user(attr_name, value)
21
+ else
22
+ @attributes.write_from_database(attr_name, value)
23
+ end
24
+
25
+ value
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,20 @@
1
+ module ActiveRecord
2
+ class AttributeSet # :nodoc:
3
+ class Builder # :nodoc:
4
+ def build_from_database(values = {}, additional_types = {})
5
+ # CPK
6
+ # if always_initialized && !values.key?(always_initialized)
7
+ # values[always_initialized] = nil
8
+ # end
9
+ Array(always_initialized).each do |always_initialized_attribute|
10
+ if always_initialized_attribute && !values.key?(always_initialized_attribute)
11
+ values[always_initialized_attribute] = nil
12
+ end
13
+ end
14
+
15
+ attributes = LazyAttributeHash.new(types, values, additional_types)
16
+ AttributeSet.new(attributes)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -1,129 +1,135 @@
1
- module ActiveRecord
2
- class CompositeKeyError < StandardError #:nodoc:
3
- end
4
-
5
- class Base
6
- INVALID_FOR_COMPOSITE_KEYS = 'Not appropriate for composite primary keys'
7
- NOT_IMPLEMENTED_YET = 'Not implemented for composite primary keys yet'
8
-
9
- class << self
10
- def primary_keys
11
- unless defined?(@primary_keys)
12
- reset_primary_keys
13
- end
14
- @primary_keys
15
- end
16
-
17
- # Don't like this method name, but its modeled after how AR does it
18
- def reset_primary_keys
19
- if self != base_class
20
- self.primary_keys = base_class.primary_keys
21
- end
22
- end
23
-
24
- def primary_key_with_composite_key_support=(keys)
25
- unless keys.kind_of?(Array)
26
- self.primary_key_without_composite_key_support = keys
27
- return
28
- end
29
-
30
- @primary_keys = keys.map { |k| k.to_s }.to_composite_keys
31
-
32
- class_eval <<-EOV
33
- extend CompositeClassMethods
34
- include CompositeInstanceMethods
35
- EOV
36
- end
37
- alias_method_chain :primary_key=, :composite_key_support
38
- alias_method :primary_keys=, :primary_key=
39
-
40
- def set_primary_keys(*keys)
41
- ActiveSupport::Deprecation.warn(
42
- "Calling set_primary_keys is deprecated. Please use `self.primary_keys = keys` instead."
43
- )
44
-
45
- keys = keys.first if keys.first.is_a?(Array)
46
- if keys.length == 1
47
- self.primary_key = keys.first
48
- else
49
- self.primary_keys = keys
50
- end
51
- end
52
-
53
- def composite?
54
- false
55
- end
56
- end
57
-
58
- def composite?
59
- self.class.composite?
60
- end
61
-
62
- module CompositeClassMethods
63
- def primary_key
64
- primary_keys
65
- end
66
-
67
- def primary_key=(keys)
68
- primary_keys = keys
69
- end
70
-
71
- def composite?
72
- true
73
- end
74
-
75
- #ids_to_s([[1,2],[7,3]]) -> "(1,2),(7,3)"
76
- #ids_to_s([[1,2],[7,3]], ',', ';') -> "1,2;7,3"
77
- def ids_to_s(many_ids, id_sep = CompositePrimaryKeys::ID_SEP, list_sep = ',', left_bracket = '(', right_bracket = ')')
78
- many_ids.map {|ids| "#{left_bracket}#{CompositePrimaryKeys::CompositeKeys.new(ids)}#{right_bracket}"}.join(list_sep)
79
- end
80
- end
81
-
82
- module CompositeInstanceMethods
83
- # A model instance's primary keys is always available as model.ids
84
- # whether you name it the default 'id' or set it to something else.
85
- def id
86
- attr_names = self.class.primary_keys
87
- ::CompositePrimaryKeys::CompositeKeys.new(attr_names.map { |attr_name| read_attribute(attr_name) })
88
- end
89
- alias_method :ids, :id
90
-
91
- def ids_hash
92
- self.class.primary_key.zip(ids).inject(Hash.new) do |hash, (key, value)|
93
- hash[key] = value
94
- hash
95
- end
96
- end
97
-
98
- def id_before_type_cast
99
- self.class.primary_keys.map do |key|
100
- self.send("#{key.to_s}_before_type_cast")
101
- end
102
- end
103
-
104
- # Sets the primary ID.
105
- def id=(ids)
106
- ids = CompositePrimaryKeys::CompositeKeys.parse(ids)
107
- unless ids.length == self.class.primary_keys.length
108
- raise "#{self.class}.id= requires #{self.class.primary_keys.length} ids"
109
- end
110
- [self.class.primary_keys, ids].transpose.each {|key, an_id| write_attribute(key , an_id)}
111
- id
112
- end
113
-
114
- def can_change_primary_key_values?
115
- false
116
- end
117
-
118
- # Returns this record's primary keys values in an Array
119
- # if any value is available
120
- def to_key
121
- ids.to_a if !ids.compact.empty? # XXX Maybe use primary_keys with send instead of ids
122
- end
123
-
124
- def to_param
125
- persisted? ? to_key.join(CompositePrimaryKeys::ID_SEP) : nil
126
- end
127
- end
128
- end
129
- end
1
+ module ActiveRecord
2
+ class CompositeKeyError < StandardError #:nodoc:
3
+ end
4
+
5
+ class Base
6
+ INVALID_FOR_COMPOSITE_KEYS = 'Not appropriate for composite primary keys'
7
+ NOT_IMPLEMENTED_YET = 'Not implemented for composite primary keys yet'
8
+
9
+ class << self
10
+ def primary_keys
11
+ unless defined?(@primary_keys)
12
+ reset_primary_keys
13
+ end
14
+ @primary_keys
15
+ end
16
+
17
+ # Don't like this method name, but its modeled after how AR does it
18
+ def reset_primary_keys
19
+ if self != base_class
20
+ self.primary_keys = base_class.primary_keys
21
+ end
22
+ end
23
+
24
+ def primary_key_with_composite_key_support=(keys)
25
+ unless keys.kind_of?(Array)
26
+ self.primary_key_without_composite_key_support = keys
27
+ return
28
+ end
29
+
30
+ @primary_keys = keys.map { |k| k.to_s }.to_composite_keys
31
+
32
+ class_eval <<-EOV
33
+ extend CompositeClassMethods
34
+ include CompositeInstanceMethods
35
+ EOV
36
+ end
37
+ alias_method_chain :primary_key=, :composite_key_support
38
+ alias_method :primary_keys=, :primary_key=
39
+
40
+ def set_primary_keys(*keys)
41
+ ActiveSupport::Deprecation.warn(
42
+ "Calling set_primary_keys is deprecated. Please use `self.primary_keys = keys` instead."
43
+ )
44
+
45
+ keys = keys.first if keys.first.is_a?(Array)
46
+ if keys.length == 1
47
+ self.primary_key = keys.first
48
+ else
49
+ self.primary_keys = keys
50
+ end
51
+ end
52
+
53
+ def composite?
54
+ false
55
+ end
56
+ end
57
+
58
+ def composite?
59
+ self.class.composite?
60
+ end
61
+
62
+ module CompositeClassMethods
63
+ def primary_key
64
+ primary_keys
65
+ end
66
+
67
+ def primary_key=(keys)
68
+ primary_keys = keys
69
+ end
70
+
71
+ def composite?
72
+ true
73
+ end
74
+
75
+ #ids_to_s([[1,2],[7,3]]) -> "(1,2),(7,3)"
76
+ #ids_to_s([[1,2],[7,3]], ',', ';') -> "1,2;7,3"
77
+ def ids_to_s(many_ids, id_sep = CompositePrimaryKeys::ID_SEP, list_sep = ',', left_bracket = '(', right_bracket = ')')
78
+ many_ids.map {|ids| "#{left_bracket}#{CompositePrimaryKeys::CompositeKeys.new(ids)}#{right_bracket}"}.join(list_sep)
79
+ end
80
+ end
81
+
82
+ module CompositeInstanceMethods
83
+ # A model instance's primary keys is always available as model.ids
84
+ # whether you name it the default 'id' or set it to something else.
85
+ def id
86
+ attr_names = self.class.primary_keys
87
+ ::CompositePrimaryKeys::CompositeKeys.new(attr_names.map { |attr_name| read_attribute(attr_name) })
88
+ end
89
+ alias_method :ids, :id
90
+
91
+ def ids_hash
92
+ self.class.primary_key.zip(ids).inject(Hash.new) do |hash, (key, value)|
93
+ hash[key] = value
94
+ hash
95
+ end
96
+ end
97
+
98
+ def id_before_type_cast
99
+ self.class.primary_keys.map do |key|
100
+ self.send("#{key.to_s}_before_type_cast")
101
+ end
102
+ end
103
+
104
+ # Sets the primary ID.
105
+ def id=(ids)
106
+ ids = CompositePrimaryKeys::CompositeKeys.parse(ids)
107
+ unless ids.length == self.class.primary_keys.length
108
+ raise "#{self.class}.id= requires #{self.class.primary_keys.length} ids"
109
+ end
110
+ [self.class.primary_keys, ids].transpose.each {|key, an_id| write_attribute(key , an_id)}
111
+ id
112
+ end
113
+
114
+ def ==(comparison_object)
115
+ return false if !persisted? && comparison_object.object_id != object_id
116
+ return true if equal? comparison_object
117
+ ids.is_a?(Array) ? super(comparison_object) && ids.all? {|id| !id.nil?} : super(comparison_object)
118
+ end
119
+
120
+ def can_change_primary_key_values?
121
+ false
122
+ end
123
+
124
+ # Returns this record's primary keys values in an Array
125
+ # if any value is available
126
+ def to_key
127
+ ids.to_a if !ids.compact.empty? # XXX Maybe use primary_keys with send instead of ids
128
+ end
129
+
130
+ def to_param
131
+ persisted? ? to_key.join(CompositePrimaryKeys::ID_SEP) : nil
132
+ end
133
+ end
134
+ end
135
+ end