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.
- checksums.yaml +4 -4
- data/History.rdoc +883 -877
- data/README.rdoc +181 -180
- data/lib/composite_primary_keys.rb +119 -117
- data/lib/composite_primary_keys/active_model/attribute_assignment.rb +19 -19
- data/lib/composite_primary_keys/associations/association_scope.rb +66 -68
- data/lib/composite_primary_keys/associations/join_dependency.rb +118 -103
- data/lib/composite_primary_keys/attribute_methods.rb +21 -9
- data/lib/composite_primary_keys/attribute_methods/primary_key.rb +0 -2
- data/lib/composite_primary_keys/attribute_methods/read.rb +30 -30
- data/lib/composite_primary_keys/attribute_methods/write.rb +35 -35
- data/lib/composite_primary_keys/base.rb +141 -141
- data/lib/composite_primary_keys/connection_adapters/abstract/database_statements.rb +37 -37
- data/lib/composite_primary_keys/connection_adapters/sqlserver/database_statements.rb +44 -44
- data/lib/composite_primary_keys/core.rb +48 -48
- data/lib/composite_primary_keys/nested_attributes.rb +1 -1
- data/lib/composite_primary_keys/persistence.rb +82 -81
- data/lib/composite_primary_keys/reflection.rb +91 -29
- data/lib/composite_primary_keys/relation.rb +197 -193
- data/lib/composite_primary_keys/relation/batches.rb +15 -7
- data/lib/composite_primary_keys/relation/calculations.rb +104 -81
- data/lib/composite_primary_keys/relation/finder_methods.rb +235 -235
- data/lib/composite_primary_keys/relation/predicate_builder/association_query_value.rb +39 -20
- data/lib/composite_primary_keys/relation/query_methods.rb +42 -42
- data/lib/composite_primary_keys/relation/where_clause.rb +18 -23
- data/lib/composite_primary_keys/table_metadata.rb +11 -0
- data/lib/composite_primary_keys/version.rb +8 -8
- data/test/abstract_unit.rb +114 -114
- data/test/connections/databases.ci.yml +22 -22
- data/test/fixtures/db_definitions/db2-create-tables.sql +112 -112
- data/test/fixtures/db_definitions/db2-drop-tables.sql +16 -16
- data/test/fixtures/db_definitions/mysql.sql +180 -180
- data/test/fixtures/db_definitions/oracle.drop.sql +41 -41
- data/test/fixtures/db_definitions/oracle.sql +199 -199
- data/test/fixtures/db_definitions/postgresql.sql +182 -182
- data/test/fixtures/db_definitions/sqlite.sql +169 -169
- data/test/fixtures/db_definitions/sqlserver.sql +176 -176
- data/test/fixtures/department.rb +16 -16
- data/test/fixtures/departments.yml +19 -15
- data/test/fixtures/employees.yml +33 -28
- data/test/fixtures/restaurants_suburbs.yml +10 -10
- data/test/fixtures/streets.yml +16 -16
- data/test/fixtures/suburbs.yml +14 -14
- data/test/fixtures/user.rb +11 -11
- data/test/test_associations.rb +364 -358
- data/test/test_attributes.rb +75 -60
- data/test/test_calculations.rb +49 -42
- data/test/test_create.rb +218 -206
- data/test/test_delete.rb +182 -179
- data/test/test_exists.rb +39 -39
- data/test/test_find.rb +170 -164
- data/test/test_ids.rb +112 -112
- data/test/test_nested_attributes.rb +67 -67
- data/test/test_update.rb +96 -96
- 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
|
-
#
|
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(
|
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
|
-
|
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
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
select_value = operation_over_aggregate_column(column, operation, distinct)
|
36
|
-
if operation == "sum" && distinct
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
relation.select_values = [ Arel.sql(::ActiveRecord::FinderMethods::ONE_AS_ONE) ] unless distinct
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
end
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
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(
|
30
|
-
# relation.order_values
|
31
|
-
# )
|
32
|
-
|
33
|
-
columns = @klass.primary_keys.map do |key|
|
34
|
-
connection.visitor.compile(
|
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!(
|
114
|
-
# if order_values.empty? && primary_key
|
115
|
-
# if composite?
|
116
|
-
# result.order!(primary_keys.map { |pk|
|
117
|
-
# elsif
|
118
|
-
# result.order!(
|
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(
|
228
|
-
order(Array(implicit_order_column || primary_key).map {|key|
|
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
|