composite_primary_keys 12.0.10 → 13.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8a4e9a7e1c59a4bdff405ea57888fa3666c72624318fb5d3be9f77fc27bff77c
4
- data.tar.gz: 16fe6c9e3a9453017f757640f87e9ab7d28df86726465cec44cb1d780bf181f1
3
+ metadata.gz: fa8acb9d77d6a2de529283cbaa0cbea67daec08813942c8bfa83f39b0bdf299f
4
+ data.tar.gz: deb46f06053d8a07b19903a46b3d30e3ae82699d265f339c5463e810900416b6
5
5
  SHA512:
6
- metadata.gz: bccebc92dc4835aef2c755f5cfda7331c659d7f5ee77274630bd502deddc359e015fffac4fba06a91a9f2a628b900ee81d6046d23ab504e8ff64463802211d4a
7
- data.tar.gz: f575f24eab4d87ec4c4c7ba00cca1b96f34ab4af94fae89243e332f1b662b857ee8c271d88b20e5690aebf6c6cb17fd3496f8fb72e7fa7860bed49d86b35cae0
6
+ metadata.gz: d31a495089eafc1cced865e0cd55e6e7a09698b124f30c7d18a754aa396c611f85abcfcf32af55e5c23534432d0f74c7eee5f1d674574c0a0b0c937d61fb16fd
7
+ data.tar.gz: ab7776729d53de9d12c08bb0035c256ef1d4cc00dab2b7be029a4c610c6cd36b6e1397160ebde56d7acaecc29817b1456e83b482a6a7f33c0a5ac15c6ba715e2
data/History.rdoc CHANGED
@@ -1,3 +1,6 @@
1
+ == 13.0.0
2
+ * Update to ActiveRecord 6.1 (Javier Julio, Charlie Savage, Sammy Larbi)
3
+
1
4
  == 12.0.10 (2021-05-09)
2
5
  * Support using the #id method on records with primary keys that also have an :id attribute.
3
6
 
@@ -514,7 +517,7 @@ by replacing quoted identifiers with all-caps equivalents on Oracle (Rhett Sutph
514
517
  * Update Oracle tests (Rhett Sutphin)
515
518
 
516
519
  == 4.1.1 2011-08-31
517
- * Support for AR 3.1.1
520
+ * Support for AR 3.1.1
518
521
  * Make polymorphic belongs_to work in rails 3.1.1 (Tom Hughes)
519
522
  * Eliminate relative paths from the test suite (Rhett Sutphin)
520
523
  * Minor improvements to the CPK test runner w/o relative path changes (Rhett Sutphin)
data/README.rdoc CHANGED
@@ -20,6 +20,7 @@ Every major version of ActiveRecord has included numerous internal changes. As
20
20
  CPK has to be rewritten for each version of ActiveRecord. To help keep
21
21
  things straight, here is the mapping:
22
22
 
23
+ Version 13.x is designed to work with ActiveRecord 6.1.x
23
24
  Version 12.x is designed to work with ActiveRecord 6.0.x
24
25
  Version 11.x is designed to work with ActiveRecord 5.2.x
25
26
  Version 10.x is designed to work with ActiveRecord 5.1.x
@@ -23,7 +23,7 @@
23
23
 
24
24
  unless defined?(ActiveRecord)
25
25
  require 'rubygems'
26
- gem 'activerecord', '~>6.0.0'
26
+ gem 'activerecord', '~>6.1.0'
27
27
  require 'active_record'
28
28
  end
29
29
 
@@ -64,6 +64,7 @@ require 'active_record/connection_adapters/abstract_adapter'
64
64
  require 'active_record/connection_adapters/postgresql/database_statements'
65
65
 
66
66
  require 'active_record/relation/where_clause'
67
+ require 'active_record/table_metadata'
67
68
 
68
69
  # CPK overrides
69
70
  require_relative 'composite_primary_keys/active_model/attribute_assignment'
@@ -115,3 +116,4 @@ require_relative 'composite_primary_keys/composite_relation'
115
116
 
116
117
  require_relative 'composite_primary_keys/arel/to_sql'
117
118
  require_relative 'composite_primary_keys/arel/sqlserver'
119
+ require_relative 'composite_primary_keys/table_metadata'
@@ -23,9 +23,8 @@ module ActiveRecord
23
23
  end
24
24
 
25
25
  def last_chain_scope(scope, reflection, owner)
26
- join_keys = reflection.join_keys
27
- key = join_keys.key
28
- foreign_key = join_keys.foreign_key
26
+ key = reflection.join_primary_key
27
+ foreign_key = reflection.join_foreign_key
29
28
 
30
29
  table = reflection.aliased_table
31
30
 
@@ -46,9 +45,8 @@ module ActiveRecord
46
45
  end
47
46
 
48
47
  def next_chain_scope(scope, reflection, next_reflection)
49
- join_keys = reflection.join_keys
50
- key = join_keys.key
51
- foreign_key = join_keys.foreign_key
48
+ key = reflection.join_primary_key
49
+ foreign_key = reflection.join_foreign_key
52
50
 
53
51
  table = reflection.aliased_table
54
52
  foreign_table = next_reflection.aliased_table
@@ -15,46 +15,62 @@ module ActiveRecord
15
15
  end
16
16
  end
17
17
 
18
- def instantiate(result_set, &block)
18
+ def instantiate(result_set, strict_loading_value, &block)
19
19
  primary_key = aliases.column_alias(join_root, join_root.primary_key)
20
20
 
21
- seen = Hash.new { |i, object_id|
22
- i[object_id] = Hash.new { |j, child_class|
21
+ seen = Hash.new { |i, parent|
22
+ i[parent] = Hash.new { |j, child_class|
23
23
  j[child_class] = {}
24
24
  }
25
- }
25
+ }.compare_by_identity
26
26
 
27
27
  model_cache = Hash.new { |h, klass| h[klass] = {} }
28
28
  parents = model_cache[join_root]
29
- column_aliases = aliases.column_aliases join_root
29
+
30
+ column_aliases = aliases.column_aliases(join_root)
31
+ column_names = []
32
+
33
+ result_set.columns.each do |name|
34
+ column_names << name unless /\At\d+_r\d+\z/.match?(name)
35
+ end
36
+
37
+ if column_names.empty?
38
+ column_types = {}
39
+ else
40
+ column_types = result_set.column_types
41
+ unless column_types.empty?
42
+ attribute_types = join_root.attribute_types
43
+ column_types = column_types.slice(*column_names).delete_if { |k, _| attribute_types.key?(k) }
44
+ end
45
+ column_aliases += column_names.map! { |name| Aliases::Column.new(name, name) }
46
+ end
30
47
 
31
48
  message_bus = ActiveSupport::Notifications.instrumenter
32
49
 
33
50
  payload = {
34
- record_count: result_set.length,
35
- class_name: join_root.base_klass.name
51
+ record_count: result_set.length,
52
+ class_name: join_root.base_klass.name
36
53
  }
37
54
 
38
55
  message_bus.instrument("instantiation.active_record", payload) do
39
56
  result_set.each { |row_hash|
40
57
  # CPK
41
58
  # parent_key = primary_key ? row_hash[primary_key] : row_hash
42
- # CPK
43
59
  parent_key = if primary_key.kind_of?(Array)
44
60
  primary_key.map {|key| row_hash[key]}
45
61
  else
46
62
  primary_key ? row_hash[primary_key] : row_hash
47
63
  end
48
64
 
49
- parent = parents[parent_key] ||= join_root.instantiate(row_hash, column_aliases, &block)
50
- construct(parent, join_root, row_hash, seen, model_cache)
65
+ parent = parents[parent_key] ||= join_root.instantiate(row_hash, column_aliases, column_types, &block)
66
+ construct(parent, join_root, row_hash, seen, model_cache, strict_loading_value)
51
67
  }
52
68
  end
53
69
 
54
70
  parents.values
55
71
  end
56
72
 
57
- def construct(ar_parent, parent, row, seen, model_cache)
73
+ def construct(ar_parent, parent, row, seen, model_cache, strict_loading_value)
58
74
  return if ar_parent.nil?
59
75
 
60
76
  parent.children.each do |node|
@@ -63,12 +79,11 @@ module ActiveRecord
63
79
  other.loaded!
64
80
  elsif ar_parent.association_cached?(node.reflection.name)
65
81
  model = ar_parent.association(node.reflection.name).target
66
- construct(model, node, row, seen, model_cache)
82
+ construct(model, node, row, seen, model_cache, strict_loading_value)
67
83
  next
68
84
  end
69
85
 
70
86
  key = aliases.column_alias(node, node.primary_key)
71
-
72
87
  # CPK
73
88
  if key.is_a?(Array)
74
89
  id = Array(key).map do |column_alias|
@@ -80,21 +95,21 @@ module ActiveRecord
80
95
  id = row[key]
81
96
  end
82
97
 
83
- if id.nil? # duplicating this so it is clear what remained unchanged from the original
98
+ if id.nil?
84
99
  nil_association = ar_parent.association(node.reflection.name)
85
100
  nil_association.loaded!
86
101
  next
87
102
  end
88
103
 
89
- model = seen[ar_parent.object_id][node][id]
104
+ model = seen[ar_parent][node][id]
90
105
 
91
106
  if model
92
- construct(model, node, row, seen, model_cache)
107
+ construct(model, node, row, seen, model_cache, strict_loading_value)
93
108
  else
94
- model = construct_model(ar_parent, node, row, model_cache, id)
109
+ model = construct_model(ar_parent, node, row, model_cache, id, strict_loading_value)
95
110
 
96
- seen[ar_parent.object_id][node][id] = model
97
- construct(model, node, row, seen, model_cache)
111
+ seen[ar_parent][node][id] = model
112
+ construct(model, node, row, seen, model_cache, strict_loading_value)
98
113
  end
99
114
  end
100
115
  end
@@ -2,8 +2,20 @@ module ActiveRecord
2
2
  module AttributeMethods
3
3
  def has_attribute?(attr_name)
4
4
  # CPK
5
- # @attributes.key?(attr_name.to_s)
6
- Array(attr_name).all? {|single_attr| @attributes.key?(single_attr.to_s) }
5
+ # attr_name = attr_name.to_s
6
+ # attr_name = self.class.attribute_aliases[attr_name] || attr_name
7
+ # @attributes.key?(attr_name)
8
+ Array(attr_name).all? do |attr|
9
+ attr = attr.to_s
10
+ attr = self.class.attribute_aliases[attr] || attr
11
+ @attributes.key?(attr)
12
+ end
13
+ end
14
+
15
+ def _has_attribute?(attr_name)
16
+ # CPK
17
+ # @attributes.key?(attr_name)
18
+ Array(attr_name).all? { |attr| @attributes.key?(attr) }
7
19
  end
8
20
  end
9
21
  end
@@ -16,7 +16,6 @@ module ActiveRecord
16
16
 
17
17
  # Returns the primary key previous value.
18
18
  def id_was
19
- sync_with_transaction_state
20
19
  # CPK
21
20
  # attribute_was(self.class.primary_key)
22
21
  if self.composite?
@@ -29,7 +28,6 @@ module ActiveRecord
29
28
  end
30
29
 
31
30
  def id_in_database
32
- sync_with_transaction_state
33
31
  # CPK
34
32
  # attribute_in_database(self.class.primary_key)
35
33
  if self.composite?
@@ -13,7 +13,7 @@ module ActiveRecord
13
13
  # CPK
14
14
  # name = primary_key if name == "id" && primary_key
15
15
  name = primary_key if name == "id" && primary_key && !composite?
16
- sync_with_transaction_state if name == primary_key
16
+
17
17
  _read_attribute(name, &block)
18
18
  end
19
19
 
@@ -13,7 +13,7 @@ module ActiveRecord
13
13
  # CPK
14
14
  # name = primary_key if name == "id" && primary_key
15
15
  name = primary_key if name == "id" && primary_key && !composite?
16
- sync_with_transaction_state if name == primary_key
16
+
17
17
  _write_attribute(name, value)
18
18
  end
19
19
 
@@ -70,7 +70,7 @@ module ActiveRecord
70
70
  if target_record
71
71
  existing_record = target_record
72
72
  else
73
- association.add_to_target(existing_record, :skip_callbacks)
73
+ association.add_to_target(existing_record, skip_callbacks: true)
74
74
  end
75
75
 
76
76
  assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
@@ -6,8 +6,8 @@ module ActiveRecord
6
6
  scope_chain_items = join_scopes(table, predicate_builder)
7
7
  klass_scope = klass_join_scope(table, predicate_builder)
8
8
 
9
- key = join_keys.key
10
- foreign_key = join_keys.foreign_key
9
+ key = join_primary_key
10
+ foreign_key = join_foreign_key
11
11
 
12
12
  # CPK
13
13
  #klass_scope.where!(table[key].eq(foreign_table[foreign_key]))
@@ -25,5 +25,67 @@ module ActiveRecord
25
25
  scope_chain_items.inject(klass_scope, &:merge!)
26
26
  end
27
27
  end
28
+
29
+ class AssociationReflection < MacroReflection
30
+ def foreign_key
31
+ # CPK
32
+ # @foreign_key ||= -(options[:foreign_key]&.to_s || derive_foreign_key)
33
+ @foreign_key ||= extract_keys(options[:foreign_key]) || derive_foreign_key
34
+ end
35
+
36
+ def association_foreign_key
37
+ # CPK
38
+ # @association_foreign_key ||= -(options[:association_foreign_key]&.to_s || class_name.foreign_key)
39
+ @association_foreign_key ||= extract_keys(options[:association_foreign_key]) || class_name.foreign_key
40
+ end
41
+
42
+ def active_record_primary_key
43
+ # CPK (Rails freezes the string returned in the expression that calculates PK here. But Rails uses the `-` method which is not available on Array for CPK, so we calculate it in one line and freeze it on the next)
44
+ # @active_record_primary_key ||= -(options[:primary_key]&.to_s || primary_key(active_record))
45
+ @active_record_primary_key ||= begin
46
+ pk = options[:primary_key] || primary_key(active_record)
47
+ pk.freeze
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ def extract_keys(keys)
54
+ case keys
55
+ when Array
56
+ keys.map { |k| k.to_s }
57
+ when NilClass
58
+ nil
59
+ else
60
+ keys.to_s
61
+ end
62
+ end
63
+ end
64
+
65
+ class BelongsToReflection < AssociationReflection
66
+ def association_primary_key(klass = nil)
67
+ if primary_key = options[:primary_key]
68
+ # CPK
69
+ # @association_primary_key ||= -primary_key.to_s
70
+ @association_primary_key ||= primary_key.freeze
71
+ else
72
+ primary_key(klass || self.klass)
73
+ end
74
+ end
75
+ end
76
+
77
+ class ThroughReflection < AbstractReflection #:nodoc:
78
+ def association_primary_key(klass = nil)
79
+ # Get the "actual" source reflection if the immediate source reflection has a
80
+ # source reflection itself
81
+ if primary_key = actual_source_reflection.options[:primary_key]
82
+ # CPK
83
+ # @association_primary_key ||= -primary_key.to_s
84
+ @association_primary_key ||= primary_key.freeze
85
+ else
86
+ primary_key(klass || self.klass)
87
+ end
88
+ end
89
+ end
28
90
  end
29
91
  end
@@ -25,13 +25,15 @@ module ActiveRecord
25
25
  end
26
26
 
27
27
  stmt = Arel::UpdateManager.new
28
+ stmt.table(arel.join_sources.empty? ? table : arel.source)
29
+ stmt.key = table[primary_key]
30
+
28
31
  # CPK
29
- if @klass.composite?
32
+ if @klass.composite? && stmt.to_sql =~ /['"]#{primary_key.to_s}['"]/
33
+ stmt = Arel::UpdateManager.new
30
34
  stmt.table(arel_table)
31
35
  cpk_subquery(stmt)
32
36
  else
33
- stmt.table(arel.join_sources.empty? ? table : arel.source)
34
- stmt.key = arel_attribute(primary_key)
35
37
  stmt.wheres = arel.constraints
36
38
  end
37
39
  stmt.take(arel.limit)
@@ -42,7 +44,7 @@ module ActiveRecord
42
44
  if klass.locking_enabled? &&
43
45
  !updates.key?(klass.locking_column) &&
44
46
  !updates.key?(klass.locking_column.to_sym)
45
- attr = arel_attribute(klass.locking_column)
47
+ attr = table[klass.locking_column]
46
48
  updates[attr.name] = _increment_attribute(attr)
47
49
  end
48
50
  stmt.set _substitute_values(updates)
@@ -68,13 +70,15 @@ module ActiveRecord
68
70
  end
69
71
 
70
72
  stmt = Arel::DeleteManager.new
73
+ stmt.from(arel.join_sources.empty? ? table : arel.source)
74
+ stmt.key = table[primary_key]
71
75
 
72
- if @klass.composite?
76
+ # CPK
77
+ if @klass.composite? && stmt.to_sql =~ /['"]#{primary_key.to_s}['"]/
78
+ stmt = Arel::DeleteManager.new
73
79
  stmt.from(arel_table)
74
80
  cpk_subquery(stmt)
75
81
  else
76
- stmt.from(arel.join_sources.empty? ? table : arel.source)
77
- stmt.key = arel_attribute(primary_key)
78
82
  stmt.wheres = arel.constraints
79
83
  end
80
84
 
@@ -138,7 +142,7 @@ module ActiveRecord
138
142
  # reference_codes.reference_code = cpk_child.reference_code)
139
143
  def cpk_exists_subquery(stmt)
140
144
  arel_attributes = primary_keys.map do |key|
141
- arel_attribute(key)
145
+ table[key]
142
146
  end.to_composite_keys
143
147
 
144
148
  # Clone the query
@@ -173,7 +177,7 @@ module ActiveRecord
173
177
  # FROM `reference_codes`) __active_record_temp)
174
178
  def cpk_mysql_subquery(stmt)
175
179
  arel_attributes = primary_keys.map do |key|
176
- arel_attribute(key)
180
+ table[key]
177
181
  end.to_composite_keys
178
182
 
179
183
  subselect = arel.clone
@@ -1,12 +1,16 @@
1
1
  module CompositePrimaryKeys
2
2
  module ActiveRecord
3
3
  module Batches
4
- def in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil)
4
+ def in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil, order: :asc)
5
5
  relation = self
6
6
  unless block_given?
7
7
  return ::ActiveRecord::Batches::BatchEnumerator.new(of: of, start: start, finish: finish, relation: self)
8
8
  end
9
9
 
10
+ unless [:asc, :desc].include?(order)
11
+ raise ArgumentError, ":order must be :asc or :desc, got #{order.inspect}"
12
+ end
13
+
10
14
  if arel.orders.present?
11
15
  act_on_ignored_order(error_on_ignore)
12
16
  end
@@ -17,8 +21,8 @@ module CompositePrimaryKeys
17
21
  batch_limit = remaining if remaining < batch_limit
18
22
  end
19
23
 
20
- relation = relation.reorder(batch_order).limit(batch_limit)
21
- relation = apply_limits(relation, start, finish)
24
+ relation = relation.reorder(batch_order(order)).limit(batch_limit)
25
+ relation = apply_limits(relation, start, finish, order)
22
26
  relation.skip_query_cache! # Retaining the results in the query cache would undermine the point of batching
23
27
  batch_relation = relation
24
28
 
@@ -61,7 +65,9 @@ module CompositePrimaryKeys
61
65
  end
62
66
 
63
67
  # CPK
64
- # batch_relation = relation.where(arel_attribute(primary_key).gt(primary_key_offset))
68
+ #batch_relation = relation.where(
69
+ # predicate_builder[primary_key, primary_key_offset, order == :desc ? :lt : :gt]
70
+ #)
65
71
  batch_relation = if composite?
66
72
  # CPK
67
73
  # Lexicographically select records
@@ -81,7 +87,9 @@ module CompositePrimaryKeys
81
87
  end.reduce(:or)
82
88
  relation.where(query)
83
89
  else
84
- relation.where(arel_attribute(primary_key).gt(primary_key_offset))
90
+ batch_relation = relation.where(
91
+ predicate_builder[primary_key, primary_key_offset, order == :desc ? :lt : :gt]
92
+ )
85
93
  end
86
94
  end
87
95
  end
@@ -95,9 +103,9 @@ module CompositePrimaryKeys
95
103
  ary.length.times.reduce([]) { |results, i| results << ary[0..i] }
96
104
  end
97
105
 
98
- def batch_order
106
+ def batch_order(order)
99
107
  self.primary_key.map do |key|
100
- arel_attribute(key).asc
108
+ table[key].public_send(order)
101
109
  end
102
110
  end
103
111
  end
@@ -4,11 +4,12 @@ module CompositePrimaryKeys
4
4
  def aggregate_column(column_name)
5
5
  # CPK
6
6
  if column_name.kind_of?(Array)
7
+ # Note: Test don't seem to run this code?
7
8
  column_name.map do |column|
8
- @klass.arel_attribute(column_name)
9
+ @klass.arel_table[column]
9
10
  end
10
11
  elsif @klass.has_attribute?(column_name) || @klass.attribute_alias?(column_name)
11
- @klass.arel_attribute(column_name)
12
+ @klass.arel_table[column_name]
12
13
  else
13
14
  Arel.sql(column_name == :all ? "*" : column_name.to_s)
14
15
  end
@@ -31,38 +32,33 @@ module CompositePrimaryKeys
31
32
  relation = unscope(:order).distinct!(false)
32
33
 
33
34
  column = aggregate_column(column_name)
34
-
35
35
  select_value = operation_over_aggregate_column(column, operation, distinct)
36
- if operation == "sum" && distinct
37
- select_value.distinct = true
38
- end
36
+ select_value.distinct = true if operation == "sum" && distinct
39
37
 
40
- column_alias = select_value.alias
41
- column_alias ||= @klass.connection.column_name_for_operation(operation, select_value)
42
38
  relation.select_values = [select_value]
43
39
 
44
40
  query_builder = relation.arel
45
41
  end
46
42
 
47
- result = skip_query_cache_if_necessary { @klass.connection.select_all(query_builder, nil) }
48
- row = result.first
49
- value = row && row.values.first
50
- type = result.column_types.fetch(column_alias) do
51
- type_for(column_name)
52
- end
43
+ result = skip_query_cache_if_necessary { @klass.connection.select_all(query_builder) }
53
44
 
54
- type_cast_calculated_value(value, type, operation)
45
+ type_cast_calculated_value(result.cast_values.first, operation) do |value|
46
+ type = column.try(:type_caster) ||
47
+ # CPK
48
+ # lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
49
+ lookup_cast_type_from_join_dependencies(column_name.to_s) || ::ActiveRecord::Type.default_value
50
+ type.deserialize(value)
51
+ end
55
52
  end
56
53
 
57
54
  def build_count_subquery(relation, column_name, distinct)
58
55
  if column_name == :all
56
+ column_alias = Arel.star
57
+ # CPK
58
+ # relation.select_values = [ Arel.sql(FinderMethods::ONE_AS_ONE) ] unless distinct
59
59
  relation.select_values = [ Arel.sql(::ActiveRecord::FinderMethods::ONE_AS_ONE) ] unless distinct
60
- if relation.select_values.first.is_a?(Array)
61
- relation.select_values = relation.select_values.first.map do |column|
62
- Arel::Attribute.new(@klass.unscoped.table, column)
63
- end
64
- end
65
60
  elsif column_name.is_a?(Array)
61
+ column_alias = Arel.star
66
62
  relation.select_values = column_name.map do |column|
67
63
  Arel::Attribute.new(@klass.unscoped.table, column)
68
64
  end
@@ -71,10 +67,37 @@ module CompositePrimaryKeys
71
67
  relation.select_values = [ aggregate_column(column_name).as(column_alias) ]
72
68
  end
73
69
 
74
- subquery = relation.arel.as(Arel.sql("subquery_for_count"))
75
- select_value = operation_over_aggregate_column(column_alias || Arel.star, "count", false)
70
+ subquery_alias = Arel.sql("subquery_for_count")
71
+ select_value = operation_over_aggregate_column(column_alias, "count", false)
72
+
73
+ relation.build_subquery(subquery_alias, select_value)
74
+ end
75
+
76
+ def calculate(operation, column_name)
77
+ if has_include?(column_name)
78
+ relation = apply_join_dependency
76
79
 
77
- Arel::SelectManager.new(subquery).project(select_value)
80
+ if operation.to_s.downcase == "count"
81
+ unless distinct_value || distinct_select?(column_name || select_for_count)
82
+ relation.distinct!
83
+ # CPK
84
+ # relation.select_values = [ klass.primary_key || table[Arel.star] ]
85
+ if klass.primary_key.present? && klass.primary_key.is_a?(Array)
86
+ relation.select_values = klass.primary_key.map do |k|
87
+ "#{connection.quote_table_name(klass.table_name)}.#{connection.quote_column_name(k)}"
88
+ end
89
+ else
90
+ relation.select_values = [ klass.primary_key || table[Arel.star] ]
91
+ end
92
+ end
93
+ # PostgreSQL: ORDER BY expressions must appear in SELECT list when using DISTINCT
94
+ relation.order_values = [] if group_values.empty?
95
+ end
96
+
97
+ relation.calculate(operation, column_name)
98
+ else
99
+ perform_calculation(operation, column_name)
100
+ end
78
101
  end
79
102
  end
80
103
  end
@@ -26,12 +26,12 @@ module CompositePrimaryKeys
26
26
  def limited_ids_for(relation)
27
27
  # CPK
28
28
  # values = @klass.connection.columns_for_distinct(
29
- # connection.column_name_from_arel_node(arel_attribute(primary_key)),
29
+ # connection.column_name_from_arel_node(table[primary_key]),
30
30
  # relation.order_values
31
31
  # )
32
32
 
33
33
  columns = @klass.primary_keys.map do |key|
34
- connection.visitor.compile(arel_attribute(key))
34
+ connection.visitor.compile(table[key])
35
35
  end
36
36
  values = @klass.connection.columns_for_distinct(columns, relation.order_values)
37
37
 
@@ -110,12 +110,12 @@ module CompositePrimaryKeys
110
110
  #
111
111
  # result = limit(limit || 1)
112
112
  # # CPK
113
- # # result.order!(arel_attribute(primary_key)) if order_values.empty? && primary_key
113
+ # # result.order!(table[primary_key]) if order_values.empty? && primary_key
114
114
  # if order_values.empty? && primary_key
115
115
  # if composite?
116
- # result.order!(primary_keys.map { |pk| arel_attribute(pk).asc })
116
+ # result.order!(primary_keys.map { |pk| table[pk].asc })
117
117
  # elsif
118
- # result.order!(arel_attribute(primary_key))
118
+ # result.order!(table[primary_key])
119
119
  # end
120
120
  # end
121
121
  #
@@ -224,8 +224,8 @@ module CompositePrimaryKeys
224
224
  def ordered_relation
225
225
  if order_values.empty? && (implicit_order_column || primary_key)
226
226
  # CPK
227
- # order(arel_attribute(implicit_order_column || primary_key).asc)
228
- order(Array(implicit_order_column || primary_key).map {|key| arel_attribute(key).asc})
227
+ # order(table[implicit_order_column || primary_key].asc)
228
+ order(Array(implicit_order_column || primary_key).map {|key| table[key].asc})
229
229
  else
230
230
  self
231
231
  end
@@ -3,16 +3,35 @@ module ActiveRecord
3
3
  class AssociationQueryValue
4
4
  def queries
5
5
  # CPK
6
- if associated_table.association_join_foreign_key.is_a?(Array)
6
+ if associated_table.join_foreign_key.is_a?(Array)
7
7
  if ids.is_a?(ActiveRecord::Relation)
8
8
  ids.map do |id|
9
- associated_table.association_join_foreign_key.zip(id.id).to_h
9
+ associated_table.join_foreign_key.zip(id.id).to_h
10
10
  end
11
11
  else
12
- [associated_table.association_join_foreign_key.zip(ids).to_h]
12
+ [associated_table.join_foreign_key.zip(ids).to_h]
13
13
  end
14
14
  else
15
- [associated_table.association_join_foreign_key.to_s => ids]
15
+ [associated_table.join_foreign_key => ids]
16
+ end
17
+ end
18
+
19
+ def ids
20
+ case value
21
+ when Relation
22
+ value.select_values.empty? ? value.select(primary_key) : value
23
+ when Array
24
+ value.map { |v| convert_to_id(v) }
25
+ else
26
+ # CPK
27
+ # convert_to_id(value)
28
+ if value.nil?
29
+ nil
30
+ elsif value.respond_to?(:composite?) && value.composite?
31
+ value.class.primary_keys.zip(value.id)
32
+ else
33
+ convert_to_id(value)
34
+ end
16
35
  end
17
36
  end
18
37
  end
@@ -4,12 +4,12 @@ module CompositePrimaryKeys
4
4
  def reverse_sql_order(order_query)
5
5
  if order_query.empty?
6
6
  # CPK
7
- # return [arel_attribute(primary_key).desc] if primary_key
7
+ # return [table[primary_key].desc] if primary_key
8
8
 
9
9
  if primary_key
10
10
  # break apart CPKs
11
11
  return primary_key.map do |key|
12
- arel_attribute(key).desc
12
+ table[key].desc
13
13
  end
14
14
  else
15
15
  raise IrreversibleOrderError,
@@ -1,23 +1,18 @@
1
1
  module ActiveRecord
2
2
  class Relation
3
3
  class WhereClause
4
- def to_h(table_name = nil)
5
- equalities = equalities(predicates)
4
+ def to_h(table_name = nil, equality_only: false)
5
+ equalities = equalities(predicates, equality_only)
6
6
 
7
7
  # CPK Adds this line, because ours are coming in with AND->{EQUALITY, EQUALITY}
8
8
  equalities = predicates.grep(Arel::Nodes::And).map(&:children).flatten.grep(Arel::Nodes::Equality) if equalities.empty?
9
9
 
10
- if table_name
11
- equalities = equalities.select do |node|
12
- node.left.relation.name == table_name
13
- end
14
- end
15
-
16
- equalities.map { |node|
10
+ equalities.each_with_object({}) do |node, hash|
11
+ next if table_name&.!= node.left.relation.name
17
12
  name = node.left.name.to_s
18
13
  value = extract_node_value(node.right)
19
- [name, value]
20
- }.to_h
14
+ hash[name] = value
15
+ end
21
16
  end
22
17
  end
23
18
  end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ class TableMetadata # :nodoc:
5
+ def associated_with?(table_name)
6
+ # CPK
7
+ # klass&._reflect_on_association(table_name) || klass&._reflect_on_association(table_name.singularize)
8
+ klass&._reflect_on_association(table_name) || klass&._reflect_on_association(table_name.to_s.singularize)
9
+ end
10
+ end
11
+ end
@@ -1,8 +1,8 @@
1
1
  module CompositePrimaryKeys
2
2
  module VERSION
3
- MAJOR = 12
3
+ MAJOR = 13
4
4
  MINOR = 0
5
- TINY = 10
5
+ TINY = 0
6
6
  STRING = [MAJOR, MINOR, TINY].join('.')
7
7
  end
8
8
  end
@@ -13,3 +13,7 @@ human_resources:
13
13
  offsite_accounting:
14
14
  id: 1
15
15
  location_id: 2
16
+
17
+ offsite_engineering:
18
+ id: 2
19
+ location_id: 2
@@ -25,4 +25,9 @@ robert:
25
25
  mindy:
26
26
  department_id: 3
27
27
  location_id: 2
28
- name: Mindy
28
+ name: Mindy
29
+
30
+ casey:
31
+ department_id: 2
32
+ location_id: 2
33
+ name: Casey
@@ -355,4 +355,10 @@ class TestAssociations < ActiveSupport::TestCase
355
355
  article.reading_ids = Reading.pluck(:id)
356
356
  assert_equal article.reading_ids, Reading.pluck(:id)
357
357
  end
358
+
359
+ def test_find_by_association
360
+ assert_equal Membership.where(user: '1').count, 1
361
+ assert_equal Membership.where(user_id: '1').count, 1
362
+ assert_equal Membership.where(user: User.find(1)).count, 1
363
+ end
358
364
  end
@@ -2,28 +2,32 @@ require File.expand_path('../abstract_unit', __FILE__)
2
2
 
3
3
  class TestAttributes < ActiveSupport::TestCase
4
4
  fixtures :reference_types, :reference_codes, :products, :tariffs, :product_tariffs
5
-
5
+
6
6
  CLASSES = {
7
7
  :single => {
8
8
  :class => ReferenceType,
9
9
  :primary_keys => :reference_type_id,
10
10
  },
11
- :dual => {
11
+ :dual => {
12
12
  :class => ReferenceCode,
13
13
  :primary_keys => [:reference_type_id, :reference_code],
14
14
  },
15
15
  }
16
-
16
+
17
17
  def setup
18
18
  self.class.classes = CLASSES
19
19
  end
20
-
20
+
21
21
  def test_brackets
22
+ tested_at_least_on_attribute = false
22
23
  testing_with do
23
24
  @first.attributes.each_pair do |attr_name, value|
25
+ next if value.nil?
24
26
  assert_equal value, @first[attr_name]
27
+ tested_at_least_on_attribute = true
25
28
  end
26
29
  end
30
+ assert tested_at_least_on_attribute
27
31
  end
28
32
 
29
33
  def test_brackets_primary_key
@@ -49,6 +53,17 @@ class TestAttributes < ActiveSupport::TestCase
49
53
  compare_indexes(tarrif, tarrif.class.primary_key, product_tariff, [:tariff_id, :tariff_start_date])
50
54
  end
51
55
 
56
+ def test_has_attribute
57
+ tariff = tariffs(:flat)
58
+ assert(tariff.has_attribute?([:tariff_id, :start_date]))
59
+ assert(tariff.has_attribute?(['tariff_id', 'start_date']))
60
+ end
61
+
62
+ def test_has__attribute
63
+ tariff = tariffs(:flat)
64
+ assert(tariff._has_attribute?(['tariff_id', 'start_date']))
65
+ end
66
+
52
67
  private
53
68
 
54
69
  def compare_indexes(obj1, indexes1, obj2, indexes2)
@@ -3,7 +3,7 @@ require File.expand_path('../abstract_unit', __FILE__)
3
3
  class TestCalculations < ActiveSupport::TestCase
4
4
  fixtures :articles, :products, :tariffs, :product_tariffs, :suburbs, :streets, :restaurants,
5
5
  :dorms, :rooms, :room_attributes, :room_attribute_assignments, :students, :room_assignments, :users, :readings,
6
- :departments, :employees, :memberships, :membership_statuses
6
+ :departments, :employees, :memberships, :membership_statuses, :reference_codes, :reference_types
7
7
 
8
8
  def test_count
9
9
  assert_equal(3, Product.includes(:product_tariffs).count)
@@ -19,7 +19,14 @@ class TestCalculations < ActiveSupport::TestCase
19
19
  product = products(:first_product)
20
20
  assert_equal(1, product.product_tariffs.select('tariff_start_date').distinct.count)
21
21
  end
22
-
22
+
23
+ def test_count_on_joined_relations_that_have_column_names_in_common
24
+ count_without_includes = ReferenceCode.count
25
+ count_with_includes = ReferenceCode.includes(:reference_type).references(:reference_type).count
26
+ assert_equal(count_without_includes, count_with_includes)
27
+ assert_equal(5, count_with_includes)
28
+ end
29
+
23
30
  def test_count_not_distinct
24
31
  product = products(:first_product)
25
32
  assert_equal(2, product.product_tariffs.select('tariff_start_date').count)
data/test/test_delete.rb CHANGED
@@ -33,13 +33,13 @@ class TestDelete < ActiveSupport::TestCase
33
33
  def test_delete_all_with_join
34
34
  employee = employees(:mindy)
35
35
 
36
- assert_equal(4, Department.count)
36
+ assert_equal(5, Department.count)
37
37
 
38
38
  deleted = Department.joins(:employees).
39
39
  where('employees.name = ?', employee.name).
40
40
  delete_all
41
41
 
42
- assert_equal(3, Department.count)
42
+ assert_equal(4, Department.count)
43
43
  assert_equal(1, deleted)
44
44
  end
45
45
 
data/test/test_find.rb CHANGED
@@ -129,13 +129,19 @@ class TestFind < ActiveSupport::TestCase
129
129
  def test_find_by_all_associations
130
130
  departments = Department.all
131
131
  employees = Employee.where(:department => departments)
132
- assert_equal(5, employees.to_a.count)
132
+ assert_equal(6, employees.to_a.count)
133
+ end
134
+
135
+ def test_find_by_some_associations
136
+ departments = Department.where(location_id: 1)
137
+ employees = Employee.where(:department => departments)
138
+ assert_equal(4, employees.to_a.count)
133
139
  end
134
140
 
135
141
  def test_expand_all
136
142
  departments = Department.all
137
143
  employees = Employee.where(:department => departments)
138
- assert_equal(5, employees.count)
144
+ assert_equal(6, employees.count)
139
145
  end
140
146
 
141
147
  def test_find_one_with_params_id
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: composite_primary_keys
3
3
  version: !ruby/object:Gem::Version
4
- version: 12.0.10
4
+ version: 13.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Charlie Savage
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 6.0.0
19
+ version: 6.1.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 6.0.0
26
+ version: 6.1.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -87,6 +87,7 @@ files:
87
87
  - lib/composite_primary_keys/relation/query_methods.rb
88
88
  - lib/composite_primary_keys/relation/where_clause.rb
89
89
  - lib/composite_primary_keys/sanitization.rb
90
+ - lib/composite_primary_keys/table_metadata.rb
90
91
  - lib/composite_primary_keys/transactions.rb
91
92
  - lib/composite_primary_keys/validations/uniqueness.rb
92
93
  - lib/composite_primary_keys/version.rb