composite_primary_keys 7.0.16 → 8.0.0

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 (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