composite_primary_keys 12.0.9 → 13.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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/History.rdoc +883 -877
  3. data/README.rdoc +181 -180
  4. data/lib/composite_primary_keys.rb +119 -117
  5. data/lib/composite_primary_keys/active_model/attribute_assignment.rb +19 -19
  6. data/lib/composite_primary_keys/associations/association_scope.rb +66 -68
  7. data/lib/composite_primary_keys/associations/join_dependency.rb +118 -103
  8. data/lib/composite_primary_keys/attribute_methods.rb +21 -9
  9. data/lib/composite_primary_keys/attribute_methods/primary_key.rb +0 -2
  10. data/lib/composite_primary_keys/attribute_methods/read.rb +30 -30
  11. data/lib/composite_primary_keys/attribute_methods/write.rb +35 -35
  12. data/lib/composite_primary_keys/base.rb +141 -141
  13. data/lib/composite_primary_keys/connection_adapters/abstract/database_statements.rb +37 -37
  14. data/lib/composite_primary_keys/connection_adapters/sqlserver/database_statements.rb +44 -44
  15. data/lib/composite_primary_keys/core.rb +48 -48
  16. data/lib/composite_primary_keys/nested_attributes.rb +1 -1
  17. data/lib/composite_primary_keys/persistence.rb +82 -81
  18. data/lib/composite_primary_keys/reflection.rb +91 -29
  19. data/lib/composite_primary_keys/relation.rb +197 -193
  20. data/lib/composite_primary_keys/relation/batches.rb +15 -7
  21. data/lib/composite_primary_keys/relation/calculations.rb +104 -81
  22. data/lib/composite_primary_keys/relation/finder_methods.rb +235 -235
  23. data/lib/composite_primary_keys/relation/predicate_builder/association_query_value.rb +39 -20
  24. data/lib/composite_primary_keys/relation/query_methods.rb +42 -42
  25. data/lib/composite_primary_keys/relation/where_clause.rb +18 -23
  26. data/lib/composite_primary_keys/table_metadata.rb +11 -0
  27. data/lib/composite_primary_keys/version.rb +8 -8
  28. data/test/abstract_unit.rb +114 -114
  29. data/test/connections/databases.ci.yml +22 -22
  30. data/test/fixtures/db_definitions/db2-create-tables.sql +112 -112
  31. data/test/fixtures/db_definitions/db2-drop-tables.sql +16 -16
  32. data/test/fixtures/db_definitions/mysql.sql +180 -180
  33. data/test/fixtures/db_definitions/oracle.drop.sql +41 -41
  34. data/test/fixtures/db_definitions/oracle.sql +199 -199
  35. data/test/fixtures/db_definitions/postgresql.sql +182 -182
  36. data/test/fixtures/db_definitions/sqlite.sql +169 -169
  37. data/test/fixtures/db_definitions/sqlserver.sql +176 -176
  38. data/test/fixtures/department.rb +16 -16
  39. data/test/fixtures/departments.yml +19 -15
  40. data/test/fixtures/employees.yml +33 -28
  41. data/test/fixtures/restaurants_suburbs.yml +10 -10
  42. data/test/fixtures/streets.yml +16 -16
  43. data/test/fixtures/suburbs.yml +14 -14
  44. data/test/fixtures/user.rb +11 -11
  45. data/test/test_associations.rb +364 -358
  46. data/test/test_attributes.rb +75 -60
  47. data/test/test_calculations.rb +49 -42
  48. data/test/test_create.rb +218 -206
  49. data/test/test_delete.rb +182 -179
  50. data/test/test_exists.rb +39 -39
  51. data/test/test_find.rb +170 -164
  52. data/test/test_ids.rb +112 -112
  53. data/test/test_nested_attributes.rb +67 -67
  54. data/test/test_update.rb +96 -96
  55. metadata +12 -11
@@ -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
@@ -1,81 +1,104 @@
1
- module CompositePrimaryKeys
2
- module ActiveRecord
3
- module Calculations
4
- def aggregate_column(column_name)
5
- # CPK
6
- if column_name.kind_of?(Array)
7
- column_name.map do |column|
8
- @klass.arel_attribute(column_name)
9
- end
10
- elsif @klass.has_attribute?(column_name) || @klass.attribute_alias?(column_name)
11
- @klass.arel_attribute(column_name)
12
- else
13
- Arel.sql(column_name == :all ? "*" : column_name.to_s)
14
- end
15
- end
16
-
17
- def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
18
- column_alias = column_name
19
-
20
- # CPK
21
- # if operation == "count" && (column_name == :all && distinct || has_limit_or_offset?)
22
- # # Shortcut when limit is zero.
23
- # return 0 if limit_value == 0
24
- #
25
- # query_builder = build_count_subquery(spawn, column_name, distinct)
26
- if operation == "count"
27
- relation = unscope(:order)
28
- query_builder = build_count_subquery(spawn, column_name, distinct)
29
- else
30
- # PostgreSQL doesn't like ORDER BY when there are no GROUP BY
31
- relation = unscope(:order).distinct!(false)
32
-
33
- column = aggregate_column(column_name)
34
-
35
- select_value = operation_over_aggregate_column(column, operation, distinct)
36
- if operation == "sum" && distinct
37
- select_value.distinct = true
38
- end
39
-
40
- column_alias = select_value.alias
41
- column_alias ||= @klass.connection.column_name_for_operation(operation, select_value)
42
- relation.select_values = [select_value]
43
-
44
- query_builder = relation.arel
45
- end
46
-
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
53
-
54
- type_cast_calculated_value(value, type, operation)
55
- end
56
-
57
- def build_count_subquery(relation, column_name, distinct)
58
- if column_name == :all
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
- elsif column_name.is_a?(Array)
66
- relation.select_values = column_name.map do |column|
67
- Arel::Attribute.new(@klass.unscoped.table, column)
68
- end
69
- else
70
- column_alias = Arel.sql("count_column")
71
- relation.select_values = [ aggregate_column(column_name).as(column_alias) ]
72
- end
73
-
74
- subquery = relation.arel.as(Arel.sql("subquery_for_count"))
75
- select_value = operation_over_aggregate_column(column_alias || Arel.star, "count", false)
76
-
77
- Arel::SelectManager.new(subquery).project(select_value)
78
- end
79
- end
80
- end
81
- end
1
+ module CompositePrimaryKeys
2
+ module ActiveRecord
3
+ module Calculations
4
+ def aggregate_column(column_name)
5
+ # CPK
6
+ if column_name.kind_of?(Array)
7
+ # Note: Test don't seem to run this code?
8
+ column_name.map do |column|
9
+ @klass.arel_table[column]
10
+ end
11
+ elsif @klass.has_attribute?(column_name) || @klass.attribute_alias?(column_name)
12
+ @klass.arel_table[column_name]
13
+ else
14
+ Arel.sql(column_name == :all ? "*" : column_name.to_s)
15
+ end
16
+ end
17
+
18
+ def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
19
+ column_alias = column_name
20
+
21
+ # CPK
22
+ # if operation == "count" && (column_name == :all && distinct || has_limit_or_offset?)
23
+ # # Shortcut when limit is zero.
24
+ # return 0 if limit_value == 0
25
+ #
26
+ # query_builder = build_count_subquery(spawn, column_name, distinct)
27
+ if operation == "count"
28
+ relation = unscope(:order)
29
+ query_builder = build_count_subquery(spawn, column_name, distinct)
30
+ else
31
+ # PostgreSQL doesn't like ORDER BY when there are no GROUP BY
32
+ relation = unscope(:order).distinct!(false)
33
+
34
+ column = aggregate_column(column_name)
35
+ select_value = operation_over_aggregate_column(column, operation, distinct)
36
+ select_value.distinct = true if operation == "sum" && distinct
37
+
38
+ relation.select_values = [select_value]
39
+
40
+ query_builder = relation.arel
41
+ end
42
+
43
+ result = skip_query_cache_if_necessary { @klass.connection.select_all(query_builder) }
44
+
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
52
+ end
53
+
54
+ def build_count_subquery(relation, column_name, distinct)
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
+ relation.select_values = [ Arel.sql(::ActiveRecord::FinderMethods::ONE_AS_ONE) ] unless distinct
60
+ elsif column_name.is_a?(Array)
61
+ column_alias = Arel.star
62
+ relation.select_values = column_name.map do |column|
63
+ Arel::Attribute.new(@klass.unscoped.table, column)
64
+ end
65
+ else
66
+ column_alias = Arel.sql("count_column")
67
+ relation.select_values = [ aggregate_column(column_name).as(column_alias) ]
68
+ end
69
+
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
79
+
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
101
+ end
102
+ end
103
+ end
104
+ end
@@ -1,235 +1,235 @@
1
- module CompositePrimaryKeys
2
- module ActiveRecord
3
- module FinderMethods
4
- def apply_join_dependency(eager_loading: group_values.empty?)
5
- join_dependency = construct_join_dependency(eager_load_values + includes_values, Arel::Nodes::OuterJoin)
6
- relation = except(:includes, :eager_load, :preload).joins!(join_dependency)
7
-
8
- if eager_loading && !using_limitable_reflections?(join_dependency.reflections)
9
- if has_limit_or_offset?
10
- limited_ids = limited_ids_for(relation)
11
-
12
- # CPK
13
- # limited_ids.empty? ? relation.none! : relation.where!(primary_key => limited_ids)
14
- limited_ids.empty? ? relation.none! : relation.where!(cpk_in_predicate(table, self.primary_keys, limited_ids))
15
- end
16
- relation.limit_value = relation.offset_value = nil
17
- end
18
-
19
- if block_given?
20
- yield relation, join_dependency
21
- else
22
- relation
23
- end
24
- end
25
-
26
- def limited_ids_for(relation)
27
- # CPK
28
- # values = @klass.connection.columns_for_distinct(
29
- # connection.column_name_from_arel_node(arel_attribute(primary_key)),
30
- # relation.order_values
31
- # )
32
-
33
- columns = @klass.primary_keys.map do |key|
34
- connection.visitor.compile(arel_attribute(key))
35
- end
36
- values = @klass.connection.columns_for_distinct(columns, relation.order_values)
37
-
38
- relation = relation.except(:select).select(values).distinct!
39
-
40
- id_rows = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, "SQL") }
41
- # CPK
42
- #id_rows.map { |row| row[primary_key] }
43
- id_rows.map do |row|
44
- @klass.primary_keys.map do |key|
45
- row[key]
46
- end
47
- end
48
- end
49
-
50
- def construct_relation_for_exists(conditions)
51
- conditions = sanitize_forbidden_attributes(conditions)
52
-
53
- if distinct_value && offset_value
54
- relation = except(:order).limit!(1)
55
- else
56
- relation = except(:select, :distinct, :order)._select!(::ActiveRecord::FinderMethods::ONE_AS_ONE).limit!(1)
57
- end
58
-
59
- case conditions
60
- # CPK
61
- when CompositePrimaryKeys::CompositeKeys
62
- relation = relation.where(cpk_id_predicate(table, primary_key, conditions))
63
- # CPK
64
- when Array
65
- pk_length = @klass.primary_keys.length
66
-
67
- if conditions.length == pk_length # E.g. conditions = ['France', 'Paris']
68
- return self.construct_relation_for_exists(conditions.to_composite_keys)
69
- else # Assume that conditions contains where relation
70
- relation = relation.where(conditions)
71
- end
72
- when Array, Hash
73
- relation.where!(conditions) unless conditions.empty?
74
- else
75
- relation.where!(primary_key => conditions) unless conditions == :none
76
- end
77
-
78
- relation
79
- end
80
-
81
- def find_with_ids(*ids)
82
- raise UnknownPrimaryKey.new(@klass) if primary_key.nil?
83
-
84
- # CPK
85
- # expects_array = ids.first.kind_of?(Array)
86
- ids = CompositePrimaryKeys.normalize(ids, @klass.primary_keys.length)
87
- expects_array = ids.flatten != ids.flatten(1)
88
- return ids.first if expects_array && ids.first.empty?
89
-
90
- # CPK
91
- # ids = ids.flatten.compact.uniq
92
- ids = expects_array ? ids.first : ids
93
-
94
- model_name = @klass.name
95
-
96
- case ids.size
97
- when 0
98
- error_message = "Couldn't find #{model_name} without an ID"
99
- raise ::ActiveRecord::RecordNotFound.new(error_message, model_name, primary_key)
100
- when 1
101
- result = find_one(ids.first)
102
- expects_array ? [ result ] : result
103
- else
104
- find_some(ids)
105
- end
106
- end
107
-
108
- # def last(limit = nil)
109
- # return find_last(limit) if loaded? || has_limit_or_offset?
110
- #
111
- # result = limit(limit || 1)
112
- # # CPK
113
- # # result.order!(arel_attribute(primary_key)) if order_values.empty? && primary_key
114
- # if order_values.empty? && primary_key
115
- # if composite?
116
- # result.order!(primary_keys.map { |pk| arel_attribute(pk).asc })
117
- # elsif
118
- # result.order!(arel_attribute(primary_key))
119
- # end
120
- # end
121
- #
122
- # result = result.reverse_order!
123
- #
124
- # limit ? result.reverse : result.first
125
- # rescue ::ActiveRecord::IrreversibleOrderError
126
- # ActiveSupport::Deprecation.warn(<<-WARNING.squish)
127
- # Finding a last element by loading the relation when SQL ORDER
128
- # can not be reversed is deprecated.
129
- # Rails 5.1 will raise ActiveRecord::IrreversibleOrderError in this case.
130
- # Please call `to_a.last` if you still want to load the relation.
131
- # WARNING
132
- # find_last(limit)
133
- # end
134
-
135
- def find_one(id)
136
- if ::ActiveRecord::Base === id
137
- raise ArgumentError, <<-MSG.squish
138
- You are passing an instance of ActiveRecord::Base to `find`.
139
- Please pass the id of the object by calling `.id`.
140
- MSG
141
- end
142
-
143
- # CPK
144
- #relation = where(primary_key => id)
145
- relation = where(cpk_id_predicate(table, primary_keys, id))
146
- record = relation.take
147
-
148
- raise_record_not_found_exception!(id, 0, 1) unless record
149
-
150
- record
151
- end
152
-
153
- def find_some(ids)
154
- # CPK
155
- if composite?
156
- ids = if ids.length == 1
157
- CompositePrimaryKeys::CompositeKeys.parse(ids.first)
158
- else
159
- ids.to_composite_keys
160
- end
161
- end
162
-
163
- return find_some_ordered(ids) unless order_values.present?
164
-
165
- # CPK
166
- # result = where(primary_key => ids).to_a
167
- result = if composite?
168
- result = where(cpk_in_predicate(table, primary_keys, ids)).to_a
169
- else
170
- result = where(primary_key => ids).to_a
171
- end
172
-
173
- expected_size =
174
- if limit_value && ids.size > limit_value
175
- limit_value
176
- else
177
- ids.size
178
- end
179
-
180
- # 11 ids with limit 3, offset 9 should give 2 results.
181
- if offset_value && (ids.size - offset_value < expected_size)
182
- expected_size = ids.size - offset_value
183
- end
184
-
185
- if result.size == expected_size
186
- result
187
- else
188
- raise_record_not_found_exception!(ids, result.size, expected_size)
189
- end
190
- end
191
-
192
- def find_some_ordered(ids)
193
- ids = ids.slice(offset_value || 0, limit_value || ids.size) || []
194
-
195
- # CPK
196
- # result = except(:limit, :offset).where(primary_key => ids).records
197
- result = if composite?
198
- except(:limit, :offset).where(cpk_in_predicate(table, primary_keys, ids)).records
199
- else
200
- except(:limit, :offset).where(primary_key => ids).records
201
- end
202
-
203
- if result.size == ids.size
204
- pk_type = @klass.type_for_attribute(primary_key)
205
-
206
- records_by_id = result.index_by(&:id)
207
- # CPK
208
- # ids.map { |id| records_by_id.fetch(pk_type.cast(id)) }
209
- if composite?
210
- ids.map do |id|
211
- typecasted_id = primary_keys.zip(id).map do |col, val|
212
- @klass.type_for_attribute(col).cast(val)
213
- end
214
- records_by_id.fetch(typecasted_id)
215
- end
216
- else
217
- ids.map { |id| records_by_id.fetch(pk_type.cast(id)) }
218
- end
219
- else
220
- raise_record_not_found_exception!(ids, result.size, ids.size)
221
- end
222
- end
223
-
224
- def ordered_relation
225
- if order_values.empty? && (implicit_order_column || primary_key)
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})
229
- else
230
- self
231
- end
232
- end
233
- end
234
- end
235
- end
1
+ module CompositePrimaryKeys
2
+ module ActiveRecord
3
+ module FinderMethods
4
+ def apply_join_dependency(eager_loading: group_values.empty?)
5
+ join_dependency = construct_join_dependency(eager_load_values + includes_values, Arel::Nodes::OuterJoin)
6
+ relation = except(:includes, :eager_load, :preload).joins!(join_dependency)
7
+
8
+ if eager_loading && !using_limitable_reflections?(join_dependency.reflections)
9
+ if has_limit_or_offset?
10
+ limited_ids = limited_ids_for(relation)
11
+
12
+ # CPK
13
+ # limited_ids.empty? ? relation.none! : relation.where!(primary_key => limited_ids)
14
+ limited_ids.empty? ? relation.none! : relation.where!(cpk_in_predicate(table, self.primary_keys, limited_ids))
15
+ end
16
+ relation.limit_value = relation.offset_value = nil
17
+ end
18
+
19
+ if block_given?
20
+ yield relation, join_dependency
21
+ else
22
+ relation
23
+ end
24
+ end
25
+
26
+ def limited_ids_for(relation)
27
+ # CPK
28
+ # values = @klass.connection.columns_for_distinct(
29
+ # connection.column_name_from_arel_node(table[primary_key]),
30
+ # relation.order_values
31
+ # )
32
+
33
+ columns = @klass.primary_keys.map do |key|
34
+ connection.visitor.compile(table[key])
35
+ end
36
+ values = @klass.connection.columns_for_distinct(columns, relation.order_values)
37
+
38
+ relation = relation.except(:select).select(values).distinct!
39
+
40
+ id_rows = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, "SQL") }
41
+ # CPK
42
+ #id_rows.map { |row| row[primary_key] }
43
+ id_rows.map do |row|
44
+ @klass.primary_keys.map do |key|
45
+ row[key]
46
+ end
47
+ end
48
+ end
49
+
50
+ def construct_relation_for_exists(conditions)
51
+ conditions = sanitize_forbidden_attributes(conditions)
52
+
53
+ if distinct_value && offset_value
54
+ relation = except(:order).limit!(1)
55
+ else
56
+ relation = except(:select, :distinct, :order)._select!(::ActiveRecord::FinderMethods::ONE_AS_ONE).limit!(1)
57
+ end
58
+
59
+ case conditions
60
+ # CPK
61
+ when CompositePrimaryKeys::CompositeKeys
62
+ relation = relation.where(cpk_id_predicate(table, primary_key, conditions))
63
+ # CPK
64
+ when Array
65
+ pk_length = @klass.primary_keys.length
66
+
67
+ if conditions.length == pk_length # E.g. conditions = ['France', 'Paris']
68
+ return self.construct_relation_for_exists(conditions.to_composite_keys)
69
+ else # Assume that conditions contains where relation
70
+ relation = relation.where(conditions)
71
+ end
72
+ when Array, Hash
73
+ relation.where!(conditions) unless conditions.empty?
74
+ else
75
+ relation.where!(primary_key => conditions) unless conditions == :none
76
+ end
77
+
78
+ relation
79
+ end
80
+
81
+ def find_with_ids(*ids)
82
+ raise UnknownPrimaryKey.new(@klass) if primary_key.nil?
83
+
84
+ # CPK
85
+ # expects_array = ids.first.kind_of?(Array)
86
+ ids = CompositePrimaryKeys.normalize(ids, @klass.primary_keys.length)
87
+ expects_array = ids.flatten != ids.flatten(1)
88
+ return ids.first if expects_array && ids.first.empty?
89
+
90
+ # CPK
91
+ # ids = ids.flatten.compact.uniq
92
+ ids = expects_array ? ids.first : ids
93
+
94
+ model_name = @klass.name
95
+
96
+ case ids.size
97
+ when 0
98
+ error_message = "Couldn't find #{model_name} without an ID"
99
+ raise ::ActiveRecord::RecordNotFound.new(error_message, model_name, primary_key)
100
+ when 1
101
+ result = find_one(ids.first)
102
+ expects_array ? [ result ] : result
103
+ else
104
+ find_some(ids)
105
+ end
106
+ end
107
+
108
+ # def last(limit = nil)
109
+ # return find_last(limit) if loaded? || has_limit_or_offset?
110
+ #
111
+ # result = limit(limit || 1)
112
+ # # CPK
113
+ # # result.order!(table[primary_key]) if order_values.empty? && primary_key
114
+ # if order_values.empty? && primary_key
115
+ # if composite?
116
+ # result.order!(primary_keys.map { |pk| table[pk].asc })
117
+ # elsif
118
+ # result.order!(table[primary_key])
119
+ # end
120
+ # end
121
+ #
122
+ # result = result.reverse_order!
123
+ #
124
+ # limit ? result.reverse : result.first
125
+ # rescue ::ActiveRecord::IrreversibleOrderError
126
+ # ActiveSupport::Deprecation.warn(<<-WARNING.squish)
127
+ # Finding a last element by loading the relation when SQL ORDER
128
+ # can not be reversed is deprecated.
129
+ # Rails 5.1 will raise ActiveRecord::IrreversibleOrderError in this case.
130
+ # Please call `to_a.last` if you still want to load the relation.
131
+ # WARNING
132
+ # find_last(limit)
133
+ # end
134
+
135
+ def find_one(id)
136
+ if ::ActiveRecord::Base === id
137
+ raise ArgumentError, <<-MSG.squish
138
+ You are passing an instance of ActiveRecord::Base to `find`.
139
+ Please pass the id of the object by calling `.id`.
140
+ MSG
141
+ end
142
+
143
+ # CPK
144
+ #relation = where(primary_key => id)
145
+ relation = where(cpk_id_predicate(table, primary_keys, id))
146
+ record = relation.take
147
+
148
+ raise_record_not_found_exception!(id, 0, 1) unless record
149
+
150
+ record
151
+ end
152
+
153
+ def find_some(ids)
154
+ # CPK
155
+ if composite?
156
+ ids = if ids.length == 1
157
+ CompositePrimaryKeys::CompositeKeys.parse(ids.first)
158
+ else
159
+ ids.to_composite_keys
160
+ end
161
+ end
162
+
163
+ return find_some_ordered(ids) unless order_values.present?
164
+
165
+ # CPK
166
+ # result = where(primary_key => ids).to_a
167
+ result = if composite?
168
+ result = where(cpk_in_predicate(table, primary_keys, ids)).to_a
169
+ else
170
+ result = where(primary_key => ids).to_a
171
+ end
172
+
173
+ expected_size =
174
+ if limit_value && ids.size > limit_value
175
+ limit_value
176
+ else
177
+ ids.size
178
+ end
179
+
180
+ # 11 ids with limit 3, offset 9 should give 2 results.
181
+ if offset_value && (ids.size - offset_value < expected_size)
182
+ expected_size = ids.size - offset_value
183
+ end
184
+
185
+ if result.size == expected_size
186
+ result
187
+ else
188
+ raise_record_not_found_exception!(ids, result.size, expected_size)
189
+ end
190
+ end
191
+
192
+ def find_some_ordered(ids)
193
+ ids = ids.slice(offset_value || 0, limit_value || ids.size) || []
194
+
195
+ # CPK
196
+ # result = except(:limit, :offset).where(primary_key => ids).records
197
+ result = if composite?
198
+ except(:limit, :offset).where(cpk_in_predicate(table, primary_keys, ids)).records
199
+ else
200
+ except(:limit, :offset).where(primary_key => ids).records
201
+ end
202
+
203
+ if result.size == ids.size
204
+ pk_type = @klass.type_for_attribute(primary_key)
205
+
206
+ records_by_id = result.index_by(&:id)
207
+ # CPK
208
+ # ids.map { |id| records_by_id.fetch(pk_type.cast(id)) }
209
+ if composite?
210
+ ids.map do |id|
211
+ typecasted_id = primary_keys.zip(id).map do |col, val|
212
+ @klass.type_for_attribute(col).cast(val)
213
+ end
214
+ records_by_id.fetch(typecasted_id)
215
+ end
216
+ else
217
+ ids.map { |id| records_by_id.fetch(pk_type.cast(id)) }
218
+ end
219
+ else
220
+ raise_record_not_found_exception!(ids, result.size, ids.size)
221
+ end
222
+ end
223
+
224
+ def ordered_relation
225
+ if order_values.empty? && (implicit_order_column || primary_key)
226
+ # CPK
227
+ # order(table[implicit_order_column || primary_key].asc)
228
+ order(Array(implicit_order_column || primary_key).map {|key| table[key].asc})
229
+ else
230
+ self
231
+ end
232
+ end
233
+ end
234
+ end
235
+ end