activerecord 4.1.8 → 4.2.11.3
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/CHANGELOG.md +1165 -1591
- data/README.rdoc +15 -10
- data/lib/active_record/aggregations.rb +15 -8
- data/lib/active_record/association_relation.rb +13 -0
- data/lib/active_record/associations/alias_tracker.rb +3 -12
- data/lib/active_record/associations/association.rb +16 -4
- data/lib/active_record/associations/association_scope.rb +84 -43
- data/lib/active_record/associations/belongs_to_association.rb +28 -10
- data/lib/active_record/associations/builder/association.rb +16 -5
- data/lib/active_record/associations/builder/belongs_to.rb +7 -29
- data/lib/active_record/associations/builder/collection_association.rb +5 -1
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +9 -14
- data/lib/active_record/associations/builder/has_many.rb +1 -1
- data/lib/active_record/associations/builder/has_one.rb +2 -2
- data/lib/active_record/associations/builder/singular_association.rb +8 -1
- data/lib/active_record/associations/collection_association.rb +87 -30
- data/lib/active_record/associations/collection_proxy.rb +33 -35
- data/lib/active_record/associations/foreign_association.rb +11 -0
- data/lib/active_record/associations/has_many_association.rb +83 -22
- data/lib/active_record/associations/has_many_through_association.rb +49 -26
- data/lib/active_record/associations/has_one_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +25 -15
- data/lib/active_record/associations/join_dependency/join_part.rb +0 -1
- data/lib/active_record/associations/join_dependency.rb +26 -12
- data/lib/active_record/associations/preloader/association.rb +14 -10
- data/lib/active_record/associations/preloader/through_association.rb +4 -3
- data/lib/active_record/associations/preloader.rb +37 -26
- data/lib/active_record/associations/singular_association.rb +17 -2
- data/lib/active_record/associations/through_association.rb +16 -12
- data/lib/active_record/associations.rb +158 -49
- data/lib/active_record/attribute.rb +163 -0
- data/lib/active_record/attribute_assignment.rb +20 -12
- data/lib/active_record/attribute_decorators.rb +66 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +7 -2
- data/lib/active_record/attribute_methods/dirty.rb +107 -43
- data/lib/active_record/attribute_methods/primary_key.rb +7 -8
- data/lib/active_record/attribute_methods/query.rb +1 -1
- data/lib/active_record/attribute_methods/read.rb +22 -59
- data/lib/active_record/attribute_methods/serialization.rb +16 -150
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +38 -28
- data/lib/active_record/attribute_methods/write.rb +9 -24
- data/lib/active_record/attribute_methods.rb +57 -95
- data/lib/active_record/attribute_set/builder.rb +106 -0
- data/lib/active_record/attribute_set.rb +81 -0
- data/lib/active_record/attributes.rb +147 -0
- data/lib/active_record/autosave_association.rb +30 -12
- data/lib/active_record/base.rb +13 -24
- data/lib/active_record/callbacks.rb +6 -6
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +85 -53
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +52 -50
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/quoting.rb +60 -60
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +39 -4
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +139 -57
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -34
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +271 -74
- data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -118
- data/lib/active_record/connection_adapters/abstract_adapter.rb +177 -60
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +295 -141
- data/lib/active_record/connection_adapters/column.rb +29 -240
- data/lib/active_record/connection_adapters/connection_specification.rb +15 -24
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +17 -33
- data/lib/active_record/connection_adapters/mysql_adapter.rb +68 -145
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +15 -27
- data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +40 -25
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +100 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
- data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
- data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +19 -0
- data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +29 -385
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +46 -136
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +4 -4
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +134 -43
- data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +224 -477
- data/lib/active_record/connection_adapters/schema_cache.rb +14 -28
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -75
- data/lib/active_record/connection_handling.rb +1 -1
- data/lib/active_record/core.rb +163 -40
- data/lib/active_record/counter_cache.rb +60 -6
- data/lib/active_record/enum.rb +10 -12
- data/lib/active_record/errors.rb +53 -30
- data/lib/active_record/explain.rb +1 -1
- data/lib/active_record/explain_subscriber.rb +1 -1
- data/lib/active_record/fixtures.rb +62 -74
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +35 -10
- data/lib/active_record/integration.rb +4 -4
- data/lib/active_record/legacy_yaml_adapter.rb +30 -0
- data/lib/active_record/locking/optimistic.rb +46 -26
- data/lib/active_record/migration/command_recorder.rb +19 -2
- data/lib/active_record/migration/join_table.rb +1 -1
- data/lib/active_record/migration.rb +79 -47
- data/lib/active_record/model_schema.rb +52 -58
- data/lib/active_record/nested_attributes.rb +18 -8
- data/lib/active_record/no_touching.rb +1 -1
- data/lib/active_record/persistence.rb +48 -27
- data/lib/active_record/query_cache.rb +3 -3
- data/lib/active_record/querying.rb +10 -7
- data/lib/active_record/railtie.rb +19 -14
- data/lib/active_record/railties/databases.rake +55 -56
- data/lib/active_record/readonly_attributes.rb +0 -1
- data/lib/active_record/reflection.rb +281 -117
- data/lib/active_record/relation/batches.rb +0 -1
- data/lib/active_record/relation/calculations.rb +41 -37
- data/lib/active_record/relation/delegation.rb +1 -1
- data/lib/active_record/relation/finder_methods.rb +71 -48
- data/lib/active_record/relation/merger.rb +39 -29
- data/lib/active_record/relation/predicate_builder/array_handler.rb +32 -13
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
- data/lib/active_record/relation/predicate_builder.rb +42 -12
- data/lib/active_record/relation/query_methods.rb +130 -73
- data/lib/active_record/relation/spawn_methods.rb +10 -3
- data/lib/active_record/relation.rb +57 -25
- data/lib/active_record/result.rb +18 -7
- data/lib/active_record/sanitization.rb +12 -2
- data/lib/active_record/schema.rb +0 -1
- data/lib/active_record/schema_dumper.rb +59 -28
- data/lib/active_record/schema_migration.rb +5 -4
- data/lib/active_record/scoping/default.rb +6 -4
- data/lib/active_record/scoping/named.rb +4 -0
- data/lib/active_record/serializers/xml_serializer.rb +3 -7
- data/lib/active_record/statement_cache.rb +95 -10
- data/lib/active_record/store.rb +5 -5
- data/lib/active_record/tasks/database_tasks.rb +61 -8
- data/lib/active_record/tasks/mysql_database_tasks.rb +32 -17
- data/lib/active_record/tasks/postgresql_database_tasks.rb +20 -9
- data/lib/active_record/timestamp.rb +9 -7
- data/lib/active_record/transactions.rb +54 -28
- data/lib/active_record/type/big_integer.rb +13 -0
- data/lib/active_record/type/binary.rb +50 -0
- data/lib/active_record/type/boolean.rb +31 -0
- data/lib/active_record/type/date.rb +50 -0
- data/lib/active_record/type/date_time.rb +54 -0
- data/lib/active_record/type/decimal.rb +64 -0
- data/lib/active_record/type/decimal_without_scale.rb +11 -0
- data/lib/active_record/type/decorator.rb +14 -0
- data/lib/active_record/type/float.rb +19 -0
- data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
- data/lib/active_record/type/integer.rb +59 -0
- data/lib/active_record/type/mutable.rb +16 -0
- data/lib/active_record/type/numeric.rb +36 -0
- data/lib/active_record/type/serialized.rb +62 -0
- data/lib/active_record/type/string.rb +40 -0
- data/lib/active_record/type/text.rb +11 -0
- data/lib/active_record/type/time.rb +26 -0
- data/lib/active_record/type/time_value.rb +38 -0
- data/lib/active_record/type/type_map.rb +64 -0
- data/lib/active_record/type/unsigned_integer.rb +15 -0
- data/lib/active_record/type/value.rb +110 -0
- data/lib/active_record/type.rb +23 -0
- data/lib/active_record/validations/associated.rb +5 -3
- data/lib/active_record/validations/presence.rb +5 -3
- data/lib/active_record/validations/uniqueness.rb +24 -20
- data/lib/active_record/validations.rb +25 -19
- data/lib/active_record.rb +5 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +8 -4
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +1 -1
- data/lib/rails/generators/active_record/model/templates/model.rb +1 -1
- metadata +66 -11
- data/lib/active_record/connection_adapters/postgresql/cast.rb +0 -168
@@ -1,29 +1,48 @@
|
|
1
|
+
require 'active_support/core_ext/string/filters'
|
2
|
+
|
1
3
|
module ActiveRecord
|
2
4
|
class PredicateBuilder
|
3
5
|
class ArrayHandler # :nodoc:
|
4
6
|
def call(attribute, value)
|
5
7
|
values = value.map { |x| x.is_a?(Base) ? x.id : x }
|
6
|
-
|
8
|
+
nils, values = values.partition(&:nil?)
|
9
|
+
|
10
|
+
if values.any? { |val| val.is_a?(Array) }
|
11
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
12
|
+
Passing a nested array to Active Record finder methods is
|
13
|
+
deprecated and will be removed. Flatten your array before using
|
14
|
+
it for 'IN' conditions.
|
15
|
+
MSG
|
7
16
|
|
8
|
-
|
9
|
-
values =
|
17
|
+
flat_values = values.flatten
|
18
|
+
values = flat_values unless flat_values.include?(nil)
|
19
|
+
end
|
20
|
+
|
21
|
+
return attribute.in([]) if values.empty? && nils.empty?
|
10
22
|
|
23
|
+
ranges, values = values.partition { |v| v.is_a?(Range) }
|
24
|
+
|
25
|
+
values_predicate =
|
11
26
|
case values.length
|
12
|
-
when 0
|
13
|
-
|
14
|
-
|
15
|
-
attribute.eq(values.first).or(attribute.eq(nil))
|
16
|
-
else
|
17
|
-
attribute.in(values).or(attribute.eq(nil))
|
27
|
+
when 0 then NullPredicate
|
28
|
+
when 1 then attribute.eq(values.first)
|
29
|
+
else attribute.in(values)
|
18
30
|
end
|
19
|
-
|
20
|
-
|
31
|
+
|
32
|
+
unless nils.empty?
|
33
|
+
values_predicate = values_predicate.or(attribute.eq(nil))
|
21
34
|
end
|
22
35
|
|
23
|
-
array_predicates = ranges.map { |range| attribute.
|
24
|
-
array_predicates
|
36
|
+
array_predicates = ranges.map { |range| attribute.between(range) }
|
37
|
+
array_predicates.unshift(values_predicate)
|
25
38
|
array_predicates.inject { |composite, predicate| composite.or(predicate) }
|
26
39
|
end
|
40
|
+
|
41
|
+
module NullPredicate # :nodoc:
|
42
|
+
def self.or(other)
|
43
|
+
other
|
44
|
+
end
|
45
|
+
end
|
27
46
|
end
|
28
47
|
end
|
29
48
|
end
|
@@ -6,7 +6,9 @@ module ActiveRecord
|
|
6
6
|
autoload :ArrayHandler, 'active_record/relation/predicate_builder/array_handler'
|
7
7
|
|
8
8
|
def self.resolve_column_aliases(klass, hash)
|
9
|
-
|
9
|
+
# This method is a hot spot, so for now, use Hash[] to dup the hash.
|
10
|
+
# https://bugs.ruby-lang.org/issues/7166
|
11
|
+
hash = Hash[hash]
|
10
12
|
hash.keys.grep(Symbol) do |key|
|
11
13
|
if klass.attribute_alias? key
|
12
14
|
hash[klass.attribute_alias(key)] = hash.delete key
|
@@ -26,7 +28,7 @@ module ActiveRecord
|
|
26
28
|
queries << '1=0'
|
27
29
|
else
|
28
30
|
table = Arel::Table.new(column, default_table.engine)
|
29
|
-
association = klass._reflect_on_association(column
|
31
|
+
association = klass._reflect_on_association(column)
|
30
32
|
|
31
33
|
value.each do |k, v|
|
32
34
|
queries.concat expand(association && association.klass, table, k, v)
|
@@ -55,12 +57,19 @@ module ActiveRecord
|
|
55
57
|
#
|
56
58
|
# For polymorphic relationships, find the foreign key and type:
|
57
59
|
# PriceEstimate.where(estimate_of: treasure)
|
58
|
-
if klass && reflection = klass._reflect_on_association(column
|
59
|
-
|
60
|
+
if klass && reflection = klass._reflect_on_association(column)
|
61
|
+
base_class = polymorphic_base_class_from_value(value)
|
62
|
+
|
63
|
+
if reflection.polymorphic? && base_class
|
60
64
|
queries << build(table[reflection.foreign_type], base_class)
|
61
65
|
end
|
62
66
|
|
63
67
|
column = reflection.foreign_key
|
68
|
+
|
69
|
+
if base_class
|
70
|
+
primary_key = reflection.association_primary_key(base_class)
|
71
|
+
value = convert_value_to_association_ids(value, primary_key)
|
72
|
+
end
|
64
73
|
end
|
65
74
|
|
66
75
|
queries << build(table[column], value)
|
@@ -105,21 +114,42 @@ module ActiveRecord
|
|
105
114
|
@handlers.unshift([klass, handler])
|
106
115
|
end
|
107
116
|
|
108
|
-
|
117
|
+
BASIC_OBJECT_HANDLER = ->(attribute, value) { attribute.eq(value) } # :nodoc:
|
118
|
+
register_handler(BasicObject, BASIC_OBJECT_HANDLER)
|
109
119
|
# FIXME: I think we need to deprecate this behavior
|
110
120
|
register_handler(Class, ->(attribute, value) { attribute.eq(value.name) })
|
111
121
|
register_handler(Base, ->(attribute, value) { attribute.eq(value.id) })
|
112
|
-
register_handler(Range, ->(attribute, value) { attribute.
|
122
|
+
register_handler(Range, ->(attribute, value) { attribute.between(value) })
|
113
123
|
register_handler(Relation, RelationHandler.new)
|
114
124
|
register_handler(Array, ArrayHandler.new)
|
115
125
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
126
|
+
def self.build(attribute, value)
|
127
|
+
handler_for(value).call(attribute, value)
|
128
|
+
end
|
129
|
+
private_class_method :build
|
130
|
+
|
131
|
+
def self.handler_for(object)
|
132
|
+
@handlers.detect { |klass, _| klass === object }.last
|
133
|
+
end
|
134
|
+
private_class_method :handler_for
|
120
135
|
|
121
|
-
|
122
|
-
|
136
|
+
def self.convert_value_to_association_ids(value, primary_key)
|
137
|
+
case value
|
138
|
+
when Relation
|
139
|
+
value.select(primary_key)
|
140
|
+
when Array
|
141
|
+
value.map { |v| convert_value_to_association_ids(v, primary_key) }
|
142
|
+
when Base
|
143
|
+
value._read_attribute(primary_key)
|
144
|
+
else
|
145
|
+
value
|
123
146
|
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def self.can_be_bound?(value) # :nodoc:
|
150
|
+
!value.nil? &&
|
151
|
+
!value.is_a?(Hash) &&
|
152
|
+
handler_for(value) == BASIC_OBJECT_HANDLER
|
153
|
+
end
|
124
154
|
end
|
125
155
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'active_support/core_ext/array/wrap'
|
2
|
+
require 'active_support/core_ext/string/filters'
|
2
3
|
require 'active_model/forbidden_attributes_protection'
|
3
4
|
|
4
5
|
module ActiveRecord
|
@@ -67,6 +68,7 @@ module ActiveRecord
|
|
67
68
|
#
|
68
69
|
def #{name}_values=(values) # def select_values=(values)
|
69
70
|
raise ImmutableRelation if @loaded # raise ImmutableRelation if @loaded
|
71
|
+
check_cached_relation
|
70
72
|
@values[:#{name}] = values # @values[:select] = values
|
71
73
|
end # end
|
72
74
|
CODE
|
@@ -84,11 +86,22 @@ module ActiveRecord
|
|
84
86
|
class_eval <<-CODE, __FILE__, __LINE__ + 1
|
85
87
|
def #{name}_value=(value) # def readonly_value=(value)
|
86
88
|
raise ImmutableRelation if @loaded # raise ImmutableRelation if @loaded
|
89
|
+
check_cached_relation
|
87
90
|
@values[:#{name}] = value # @values[:readonly] = value
|
88
91
|
end # end
|
89
92
|
CODE
|
90
93
|
end
|
91
94
|
|
95
|
+
def check_cached_relation # :nodoc:
|
96
|
+
if defined?(@arel) && @arel
|
97
|
+
@arel = nil
|
98
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
99
|
+
Modifying already cached Relation. The cache will be reset. Use a
|
100
|
+
cloned Relation to prevent this warning.
|
101
|
+
MSG
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
92
105
|
def create_with_value # :nodoc:
|
93
106
|
@values[:create_with] || {}
|
94
107
|
end
|
@@ -173,7 +186,7 @@ module ActiveRecord
|
|
173
186
|
|
174
187
|
# Use to indicate that the given +table_names+ are referenced by an SQL string,
|
175
188
|
# and should therefore be JOINed in any query rather than loaded separately.
|
176
|
-
# This method only works in
|
189
|
+
# This method only works in conjunction with +includes+.
|
177
190
|
# See #includes for more details.
|
178
191
|
#
|
179
192
|
# User.includes(:posts).where("posts.name = 'foo'")
|
@@ -207,7 +220,7 @@ module ActiveRecord
|
|
207
220
|
# fields are retrieved:
|
208
221
|
#
|
209
222
|
# Model.select(:field)
|
210
|
-
# # => [#<Model field:value>]
|
223
|
+
# # => [#<Model id: nil, field: "value">]
|
211
224
|
#
|
212
225
|
# Although in the above example it looks as though this method returns an
|
213
226
|
# array, it actually returns a relation object and can have other query
|
@@ -216,12 +229,12 @@ module ActiveRecord
|
|
216
229
|
# The argument to the method can also be an array of fields.
|
217
230
|
#
|
218
231
|
# Model.select(:field, :other_field, :and_one_more)
|
219
|
-
# # => [#<Model field: "value", other_field: "value", and_one_more: "value">]
|
232
|
+
# # => [#<Model id: nil, field: "value", other_field: "value", and_one_more: "value">]
|
220
233
|
#
|
221
234
|
# You can also use one or more strings, which will be used unchanged as SELECT fields.
|
222
235
|
#
|
223
236
|
# Model.select('field AS field_one', 'other_field AS field_two')
|
224
|
-
# # => [#<Model field: "value", other_field: "value">]
|
237
|
+
# # => [#<Model id: nil, field: "value", other_field: "value">]
|
225
238
|
#
|
226
239
|
# If an alias was specified, it will be accessible from the resulting objects:
|
227
240
|
#
|
@@ -229,7 +242,7 @@ module ActiveRecord
|
|
229
242
|
# # => "value"
|
230
243
|
#
|
231
244
|
# Accessing attributes of an object that do not have fields retrieved by a select
|
232
|
-
# will throw <tt>ActiveModel::MissingAttributeError</tt>:
|
245
|
+
# except +id+ will throw <tt>ActiveModel::MissingAttributeError</tt>:
|
233
246
|
#
|
234
247
|
# Model.select(:field).first.other_field
|
235
248
|
# # => ActiveModel::MissingAttributeError: missing attribute: other_field
|
@@ -245,7 +258,7 @@ module ActiveRecord
|
|
245
258
|
def _select!(*fields) # :nodoc:
|
246
259
|
fields.flatten!
|
247
260
|
fields.map! do |field|
|
248
|
-
klass.attribute_alias?(field) ? klass.attribute_alias(field) : field
|
261
|
+
klass.attribute_alias?(field) ? klass.attribute_alias(field).to_sym : field
|
249
262
|
end
|
250
263
|
self.select_values += fields
|
251
264
|
self
|
@@ -266,6 +279,10 @@ module ActiveRecord
|
|
266
279
|
#
|
267
280
|
# User.group('name AS grouped_name, age')
|
268
281
|
# => [#<User id: 3, name: "Foo", age: 21, ...>, #<User id: 2, name: "Oscar", age: 21, ...>, #<User id: 5, name: "Foo", age: 23, ...>]
|
282
|
+
#
|
283
|
+
# Passing in an array of attributes to group by is also supported.
|
284
|
+
# User.select([:id, :first_name]).group(:id, :first_name).first(3)
|
285
|
+
# => [#<User id: 1, first_name: "Bill">, #<User id: 2, first_name: "Earl">, #<User id: 3, first_name: "Beto">]
|
269
286
|
def group(*args)
|
270
287
|
check_if_method_has_arguments!(:group, args)
|
271
288
|
spawn.group!(*args)
|
@@ -280,15 +297,6 @@ module ActiveRecord
|
|
280
297
|
|
281
298
|
# Allows to specify an order attribute:
|
282
299
|
#
|
283
|
-
# User.order('name')
|
284
|
-
# => SELECT "users".* FROM "users" ORDER BY name
|
285
|
-
#
|
286
|
-
# User.order('name DESC')
|
287
|
-
# => SELECT "users".* FROM "users" ORDER BY name DESC
|
288
|
-
#
|
289
|
-
# User.order('name DESC, email')
|
290
|
-
# => SELECT "users".* FROM "users" ORDER BY name DESC, email
|
291
|
-
#
|
292
300
|
# User.order(:name)
|
293
301
|
# => SELECT "users".* FROM "users" ORDER BY "users"."name" ASC
|
294
302
|
#
|
@@ -297,6 +305,15 @@ module ActiveRecord
|
|
297
305
|
#
|
298
306
|
# User.order(:name, email: :desc)
|
299
307
|
# => SELECT "users".* FROM "users" ORDER BY "users"."name" ASC, "users"."email" DESC
|
308
|
+
#
|
309
|
+
# User.order('name')
|
310
|
+
# => SELECT "users".* FROM "users" ORDER BY name
|
311
|
+
#
|
312
|
+
# User.order('name DESC')
|
313
|
+
# => SELECT "users".* FROM "users" ORDER BY name DESC
|
314
|
+
#
|
315
|
+
# User.order('name DESC, email')
|
316
|
+
# => SELECT "users".* FROM "users" ORDER BY name DESC, email
|
300
317
|
def order(*args)
|
301
318
|
check_if_method_has_arguments!(:order, args)
|
302
319
|
spawn.order!(*args)
|
@@ -410,19 +427,17 @@ module ActiveRecord
|
|
410
427
|
# => SELECT "users".* FROM "users" LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id
|
411
428
|
def joins(*args)
|
412
429
|
check_if_method_has_arguments!(:joins, args)
|
413
|
-
|
414
|
-
args.compact!
|
415
|
-
args.flatten!
|
416
|
-
|
417
430
|
spawn.joins!(*args)
|
418
431
|
end
|
419
432
|
|
420
433
|
def joins!(*args) # :nodoc:
|
434
|
+
args.compact!
|
435
|
+
args.flatten!
|
421
436
|
self.joins_values += args
|
422
437
|
self
|
423
438
|
end
|
424
439
|
|
425
|
-
def bind(value)
|
440
|
+
def bind(value) # :nodoc:
|
426
441
|
spawn.bind!(value)
|
427
442
|
end
|
428
443
|
|
@@ -560,18 +575,14 @@ module ActiveRecord
|
|
560
575
|
end
|
561
576
|
end
|
562
577
|
|
563
|
-
def where!(opts
|
564
|
-
if
|
565
|
-
|
566
|
-
|
567
|
-
if Hash === opts
|
568
|
-
opts = sanitize_forbidden_attributes(opts)
|
569
|
-
references!(PredicateBuilder.references(opts))
|
570
|
-
end
|
571
|
-
|
572
|
-
self.where_values += build_where(opts, rest)
|
573
|
-
self
|
578
|
+
def where!(opts, *rest) # :nodoc:
|
579
|
+
if Hash === opts
|
580
|
+
opts = sanitize_forbidden_attributes(opts)
|
581
|
+
references!(PredicateBuilder.references(opts))
|
574
582
|
end
|
583
|
+
|
584
|
+
self.where_values += build_where(opts, rest)
|
585
|
+
self
|
575
586
|
end
|
576
587
|
|
577
588
|
# Allows you to change a previously set where condition for a given attribute, instead of appending to that condition.
|
@@ -677,11 +688,11 @@ module ActiveRecord
|
|
677
688
|
# end
|
678
689
|
#
|
679
690
|
def none
|
680
|
-
extending(NullRelation)
|
691
|
+
where("1=0").extending!(NullRelation)
|
681
692
|
end
|
682
693
|
|
683
694
|
def none! # :nodoc:
|
684
|
-
extending!(NullRelation)
|
695
|
+
where!("1=0").extending!(NullRelation)
|
685
696
|
end
|
686
697
|
|
687
698
|
# Sets readonly attributes for the returned relation. If value is
|
@@ -746,6 +757,9 @@ module ActiveRecord
|
|
746
757
|
|
747
758
|
def from!(value, subquery_name = nil) # :nodoc:
|
748
759
|
self.from_value = [value, subquery_name]
|
760
|
+
if value.is_a? Relation
|
761
|
+
self.bind_values = value.arel.bind_values + value.bind_values + bind_values
|
762
|
+
end
|
749
763
|
self
|
750
764
|
end
|
751
765
|
|
@@ -833,7 +847,9 @@ module ActiveRecord
|
|
833
847
|
end
|
834
848
|
|
835
849
|
def reverse_order! # :nodoc:
|
836
|
-
|
850
|
+
orders = order_values.uniq
|
851
|
+
orders.reject!(&:blank?)
|
852
|
+
self.order_values = reverse_sql_order(orders)
|
837
853
|
self
|
838
854
|
end
|
839
855
|
|
@@ -849,30 +865,22 @@ module ActiveRecord
|
|
849
865
|
|
850
866
|
build_joins(arel, joins_values.flatten) unless joins_values.empty?
|
851
867
|
|
852
|
-
collapse_wheres(arel, (where_values - [''])
|
868
|
+
collapse_wheres(arel, (where_values - [''])) #TODO: Add uniq with real value comparison / ignore uniqs that have binds
|
853
869
|
|
854
870
|
arel.having(*having_values.uniq.reject(&:blank?)) unless having_values.empty?
|
855
871
|
|
856
872
|
arel.take(connection.sanitize_limit(limit_value)) if limit_value
|
857
873
|
arel.skip(offset_value.to_i) if offset_value
|
858
|
-
|
859
|
-
arel.group(*group_values.uniq.reject(&:blank?)) unless group_values.empty?
|
874
|
+
arel.group(*arel_columns(group_values.uniq.reject(&:blank?))) unless group_values.empty?
|
860
875
|
|
861
876
|
build_order(arel)
|
862
877
|
|
863
|
-
build_select(arel
|
878
|
+
build_select(arel)
|
864
879
|
|
865
880
|
arel.distinct(distinct_value)
|
866
881
|
arel.from(build_from) if from_value
|
867
882
|
arel.lock(lock_value) if lock_value
|
868
883
|
|
869
|
-
# Reorder bind indexes if joins produced bind values
|
870
|
-
bvs = arel.bind_values + bind_values
|
871
|
-
arel.ast.grep(Arel::Nodes::BindParam).each_with_index do |bp, i|
|
872
|
-
column = bvs[i].first
|
873
|
-
bp.replace connection.substitute_at(column, i)
|
874
|
-
end
|
875
|
-
|
876
884
|
arel
|
877
885
|
end
|
878
886
|
|
@@ -886,8 +894,9 @@ module ActiveRecord
|
|
886
894
|
|
887
895
|
case scope
|
888
896
|
when :order
|
889
|
-
self.reverse_order_value = false
|
890
897
|
result = []
|
898
|
+
when :where
|
899
|
+
self.bind_values = []
|
891
900
|
else
|
892
901
|
result = [] unless single_val_method
|
893
902
|
end
|
@@ -898,9 +907,9 @@ module ActiveRecord
|
|
898
907
|
def where_unscoping(target_value)
|
899
908
|
target_value = target_value.to_s
|
900
909
|
|
901
|
-
where_values.reject
|
910
|
+
self.where_values = where_values.reject do |rel|
|
902
911
|
case rel
|
903
|
-
when Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual
|
912
|
+
when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThan, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThan, Arel::Nodes::GreaterThanOrEqual
|
904
913
|
subrelation = (rel.left.kind_of?(Arel::Attributes::Attribute) ? rel.left : rel.right)
|
905
914
|
subrelation.name == target_value
|
906
915
|
end
|
@@ -938,18 +947,14 @@ module ActiveRecord
|
|
938
947
|
def build_where(opts, other = [])
|
939
948
|
case opts
|
940
949
|
when String, Array
|
941
|
-
#TODO: Remove duplication with: /activerecord/lib/active_record/sanitization.rb:113
|
942
|
-
values = Hash === other.first ? other.first.values : other
|
943
|
-
|
944
|
-
values.grep(ActiveRecord::Relation) do |rel|
|
945
|
-
self.bind_values += rel.bind_values
|
946
|
-
end
|
947
|
-
|
948
950
|
[@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))]
|
949
951
|
when Hash
|
950
952
|
opts = PredicateBuilder.resolve_column_aliases(klass, opts)
|
951
|
-
attributes = @klass.send(:expand_hash_conditions_for_aggregates, opts)
|
952
953
|
|
954
|
+
tmp_opts, bind_values = create_binds(opts)
|
955
|
+
self.bind_values += bind_values
|
956
|
+
|
957
|
+
attributes = @klass.send(:expand_hash_conditions_for_aggregates, tmp_opts)
|
953
958
|
add_relations_to_bind_values(attributes)
|
954
959
|
|
955
960
|
PredicateBuilder.build_from_hash(klass, attributes, table)
|
@@ -958,12 +963,50 @@ module ActiveRecord
|
|
958
963
|
end
|
959
964
|
end
|
960
965
|
|
966
|
+
def create_binds(opts)
|
967
|
+
bindable, non_binds = opts.partition do |column, value|
|
968
|
+
PredicateBuilder.can_be_bound?(value) &&
|
969
|
+
@klass.columns_hash.include?(column.to_s) &&
|
970
|
+
!@klass.reflect_on_aggregation(column)
|
971
|
+
end
|
972
|
+
|
973
|
+
association_binds, non_binds = non_binds.partition do |column, value|
|
974
|
+
value.is_a?(Hash) && association_for_table(column)
|
975
|
+
end
|
976
|
+
|
977
|
+
new_opts = {}
|
978
|
+
binds = []
|
979
|
+
|
980
|
+
connection = self.connection
|
981
|
+
|
982
|
+
bindable.each do |(column,value)|
|
983
|
+
binds.push [@klass.columns_hash[column.to_s], value]
|
984
|
+
new_opts[column] = connection.substitute_at(column)
|
985
|
+
end
|
986
|
+
|
987
|
+
association_binds.each do |(column, value)|
|
988
|
+
association_relation = association_for_table(column).klass.send(:relation)
|
989
|
+
association_new_opts, association_bind = association_relation.send(:create_binds, value)
|
990
|
+
new_opts[column] = association_new_opts
|
991
|
+
binds += association_bind
|
992
|
+
end
|
993
|
+
|
994
|
+
non_binds.each { |column,value| new_opts[column] = value }
|
995
|
+
|
996
|
+
[new_opts, binds]
|
997
|
+
end
|
998
|
+
|
999
|
+
def association_for_table(table_name)
|
1000
|
+
table_name = table_name.to_s
|
1001
|
+
@klass._reflect_on_association(table_name) ||
|
1002
|
+
@klass._reflect_on_association(table_name.singularize)
|
1003
|
+
end
|
1004
|
+
|
961
1005
|
def build_from
|
962
1006
|
opts, name = from_value
|
963
1007
|
case opts
|
964
1008
|
when Relation
|
965
1009
|
name ||= 'subquery'
|
966
|
-
self.bind_values = opts.bind_values + self.bind_values
|
967
1010
|
opts.arel.as(name.to_s)
|
968
1011
|
else
|
969
1012
|
opts
|
@@ -999,26 +1042,38 @@ module ActiveRecord
|
|
999
1042
|
join_list
|
1000
1043
|
)
|
1001
1044
|
|
1002
|
-
|
1045
|
+
join_infos = join_dependency.join_constraints stashed_association_joins
|
1003
1046
|
|
1004
|
-
|
1047
|
+
join_infos.each do |info|
|
1048
|
+
info.joins.each { |join| manager.from(join) }
|
1049
|
+
manager.bind_values.concat info.binds
|
1050
|
+
end
|
1005
1051
|
|
1006
1052
|
manager.join_sources.concat(join_list)
|
1007
1053
|
|
1008
1054
|
manager
|
1009
1055
|
end
|
1010
1056
|
|
1011
|
-
def build_select(arel
|
1012
|
-
if
|
1013
|
-
|
1014
|
-
columns_hash.key?(field.to_s) ? arel_table[field] : field
|
1015
|
-
end
|
1016
|
-
arel.project(*expanded_select)
|
1057
|
+
def build_select(arel)
|
1058
|
+
if select_values.any?
|
1059
|
+
arel.project(*arel_columns(select_values.uniq))
|
1017
1060
|
else
|
1018
1061
|
arel.project(@klass.arel_table[Arel.star])
|
1019
1062
|
end
|
1020
1063
|
end
|
1021
1064
|
|
1065
|
+
def arel_columns(columns)
|
1066
|
+
columns.map do |field|
|
1067
|
+
if (Symbol === field || String === field) && columns_hash.key?(field.to_s) && !from_value
|
1068
|
+
arel_table[field]
|
1069
|
+
elsif Symbol === field
|
1070
|
+
connection.quote_table_name(field.to_s)
|
1071
|
+
else
|
1072
|
+
field
|
1073
|
+
end
|
1074
|
+
end
|
1075
|
+
end
|
1076
|
+
|
1022
1077
|
def reverse_sql_order(order_query)
|
1023
1078
|
order_query = ["#{quoted_table_name}.#{quoted_primary_key} ASC"] if order_query.empty?
|
1024
1079
|
|
@@ -1044,15 +1099,19 @@ module ActiveRecord
|
|
1044
1099
|
def build_order(arel)
|
1045
1100
|
orders = order_values.uniq
|
1046
1101
|
orders.reject!(&:blank?)
|
1047
|
-
orders = reverse_sql_order(orders) if reverse_order_value
|
1048
1102
|
|
1049
1103
|
arel.order(*orders) unless orders.empty?
|
1050
1104
|
end
|
1051
1105
|
|
1106
|
+
VALID_DIRECTIONS = [:asc, :desc, :ASC, :DESC,
|
1107
|
+
'asc', 'desc', 'ASC', 'DESC'] # :nodoc:
|
1108
|
+
|
1052
1109
|
def validate_order_args(args)
|
1053
|
-
args.
|
1054
|
-
unless (
|
1055
|
-
|
1110
|
+
args.each do |arg|
|
1111
|
+
next unless arg.is_a?(Hash)
|
1112
|
+
arg.each do |_key, value|
|
1113
|
+
raise ArgumentError, "Direction \"#{value}\" is invalid. Valid " \
|
1114
|
+
"directions are: #{VALID_DIRECTIONS.inspect}" unless VALID_DIRECTIONS.include?(value)
|
1056
1115
|
end
|
1057
1116
|
end
|
1058
1117
|
end
|
@@ -1074,7 +1133,7 @@ module ActiveRecord
|
|
1074
1133
|
when Hash
|
1075
1134
|
arg.map { |field, dir|
|
1076
1135
|
field = klass.attribute_alias(field) if klass.attribute_alias?(field)
|
1077
|
-
table[field].send(dir)
|
1136
|
+
table[field].send(dir.downcase)
|
1078
1137
|
}
|
1079
1138
|
else
|
1080
1139
|
arg
|
@@ -1104,13 +1163,11 @@ module ActiveRecord
|
|
1104
1163
|
end
|
1105
1164
|
end
|
1106
1165
|
|
1107
|
-
# This function is recursive just for better readablity.
|
1108
|
-
# #where argument doesn't support more than one level nested hash in real world.
|
1109
1166
|
def add_relations_to_bind_values(attributes)
|
1110
1167
|
if attributes.is_a?(Hash)
|
1111
1168
|
attributes.each_value do |value|
|
1112
1169
|
if value.is_a?(ActiveRecord::Relation)
|
1113
|
-
self.bind_values += value.bind_values
|
1170
|
+
self.bind_values += value.arel.bind_values + value.bind_values
|
1114
1171
|
else
|
1115
1172
|
add_relations_to_bind_values(value)
|
1116
1173
|
end
|
@@ -12,6 +12,7 @@ module ActiveRecord
|
|
12
12
|
|
13
13
|
# Merges in the conditions from <tt>other</tt>, if <tt>other</tt> is an <tt>ActiveRecord::Relation</tt>.
|
14
14
|
# Returns an array representing the intersection of the resulting records with <tt>other</tt>, if <tt>other</tt> is an array.
|
15
|
+
#
|
15
16
|
# Post.where(published: true).joins(:comments).merge( Comment.where(spam: false) )
|
16
17
|
# # Performs a single join query with both where conditions.
|
17
18
|
#
|
@@ -37,11 +38,14 @@ module ActiveRecord
|
|
37
38
|
end
|
38
39
|
|
39
40
|
def merge!(other) # :nodoc:
|
40
|
-
if
|
41
|
+
if other.is_a?(Hash)
|
42
|
+
Relation::HashMerger.new(self, other).merge
|
43
|
+
elsif other.is_a?(Relation)
|
44
|
+
Relation::Merger.new(self, other).merge
|
45
|
+
elsif other.respond_to?(:to_proc)
|
41
46
|
instance_exec(&other)
|
42
47
|
else
|
43
|
-
|
44
|
-
klass.new(self, other).merge
|
48
|
+
raise ArgumentError, "#{other.inspect} is not an ActiveRecord::Relation"
|
45
49
|
end
|
46
50
|
end
|
47
51
|
|
@@ -58,6 +62,9 @@ module ActiveRecord
|
|
58
62
|
# Post.order('id asc').only(:where) # discards the order condition
|
59
63
|
# Post.order('id asc').only(:where, :order) # uses the specified order
|
60
64
|
def only(*onlies)
|
65
|
+
if onlies.any? { |o| o == :where }
|
66
|
+
onlies << :bind
|
67
|
+
end
|
61
68
|
relation_with values.slice(*onlies)
|
62
69
|
end
|
63
70
|
|