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.

Files changed (186) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +1165 -1591
  3. data/README.rdoc +15 -10
  4. data/lib/active_record/aggregations.rb +15 -8
  5. data/lib/active_record/association_relation.rb +13 -0
  6. data/lib/active_record/associations/alias_tracker.rb +3 -12
  7. data/lib/active_record/associations/association.rb +16 -4
  8. data/lib/active_record/associations/association_scope.rb +84 -43
  9. data/lib/active_record/associations/belongs_to_association.rb +28 -10
  10. data/lib/active_record/associations/builder/association.rb +16 -5
  11. data/lib/active_record/associations/builder/belongs_to.rb +7 -29
  12. data/lib/active_record/associations/builder/collection_association.rb +5 -1
  13. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +9 -14
  14. data/lib/active_record/associations/builder/has_many.rb +1 -1
  15. data/lib/active_record/associations/builder/has_one.rb +2 -2
  16. data/lib/active_record/associations/builder/singular_association.rb +8 -1
  17. data/lib/active_record/associations/collection_association.rb +87 -30
  18. data/lib/active_record/associations/collection_proxy.rb +33 -35
  19. data/lib/active_record/associations/foreign_association.rb +11 -0
  20. data/lib/active_record/associations/has_many_association.rb +83 -22
  21. data/lib/active_record/associations/has_many_through_association.rb +49 -26
  22. data/lib/active_record/associations/has_one_association.rb +1 -1
  23. data/lib/active_record/associations/join_dependency/join_association.rb +25 -15
  24. data/lib/active_record/associations/join_dependency/join_part.rb +0 -1
  25. data/lib/active_record/associations/join_dependency.rb +26 -12
  26. data/lib/active_record/associations/preloader/association.rb +14 -10
  27. data/lib/active_record/associations/preloader/through_association.rb +4 -3
  28. data/lib/active_record/associations/preloader.rb +37 -26
  29. data/lib/active_record/associations/singular_association.rb +17 -2
  30. data/lib/active_record/associations/through_association.rb +16 -12
  31. data/lib/active_record/associations.rb +158 -49
  32. data/lib/active_record/attribute.rb +163 -0
  33. data/lib/active_record/attribute_assignment.rb +20 -12
  34. data/lib/active_record/attribute_decorators.rb +66 -0
  35. data/lib/active_record/attribute_methods/before_type_cast.rb +7 -2
  36. data/lib/active_record/attribute_methods/dirty.rb +107 -43
  37. data/lib/active_record/attribute_methods/primary_key.rb +7 -8
  38. data/lib/active_record/attribute_methods/query.rb +1 -1
  39. data/lib/active_record/attribute_methods/read.rb +22 -59
  40. data/lib/active_record/attribute_methods/serialization.rb +16 -150
  41. data/lib/active_record/attribute_methods/time_zone_conversion.rb +38 -28
  42. data/lib/active_record/attribute_methods/write.rb +9 -24
  43. data/lib/active_record/attribute_methods.rb +57 -95
  44. data/lib/active_record/attribute_set/builder.rb +106 -0
  45. data/lib/active_record/attribute_set.rb +81 -0
  46. data/lib/active_record/attributes.rb +147 -0
  47. data/lib/active_record/autosave_association.rb +30 -12
  48. data/lib/active_record/base.rb +13 -24
  49. data/lib/active_record/callbacks.rb +6 -6
  50. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +85 -53
  51. data/lib/active_record/connection_adapters/abstract/database_statements.rb +52 -50
  52. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
  53. data/lib/active_record/connection_adapters/abstract/quoting.rb +60 -60
  54. data/lib/active_record/connection_adapters/abstract/savepoints.rb +1 -1
  55. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +39 -4
  56. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +139 -57
  57. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -34
  58. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +271 -74
  59. data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -118
  60. data/lib/active_record/connection_adapters/abstract_adapter.rb +177 -60
  61. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +295 -141
  62. data/lib/active_record/connection_adapters/column.rb +29 -240
  63. data/lib/active_record/connection_adapters/connection_specification.rb +15 -24
  64. data/lib/active_record/connection_adapters/mysql2_adapter.rb +17 -33
  65. data/lib/active_record/connection_adapters/mysql_adapter.rb +68 -145
  66. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +15 -27
  67. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  68. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +40 -25
  69. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +100 -0
  70. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  71. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  72. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
  73. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  75. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  79. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  80. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  81. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +19 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid.rb +29 -385
  95. data/lib/active_record/connection_adapters/postgresql/quoting.rb +46 -136
  96. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +4 -4
  97. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  98. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +134 -43
  99. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  100. data/lib/active_record/connection_adapters/postgresql_adapter.rb +224 -477
  101. data/lib/active_record/connection_adapters/schema_cache.rb +14 -28
  102. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -75
  103. data/lib/active_record/connection_handling.rb +1 -1
  104. data/lib/active_record/core.rb +163 -40
  105. data/lib/active_record/counter_cache.rb +60 -6
  106. data/lib/active_record/enum.rb +10 -12
  107. data/lib/active_record/errors.rb +53 -30
  108. data/lib/active_record/explain.rb +1 -1
  109. data/lib/active_record/explain_subscriber.rb +1 -1
  110. data/lib/active_record/fixtures.rb +62 -74
  111. data/lib/active_record/gem_version.rb +4 -4
  112. data/lib/active_record/inheritance.rb +35 -10
  113. data/lib/active_record/integration.rb +4 -4
  114. data/lib/active_record/legacy_yaml_adapter.rb +30 -0
  115. data/lib/active_record/locking/optimistic.rb +46 -26
  116. data/lib/active_record/migration/command_recorder.rb +19 -2
  117. data/lib/active_record/migration/join_table.rb +1 -1
  118. data/lib/active_record/migration.rb +79 -47
  119. data/lib/active_record/model_schema.rb +52 -58
  120. data/lib/active_record/nested_attributes.rb +18 -8
  121. data/lib/active_record/no_touching.rb +1 -1
  122. data/lib/active_record/persistence.rb +48 -27
  123. data/lib/active_record/query_cache.rb +3 -3
  124. data/lib/active_record/querying.rb +10 -7
  125. data/lib/active_record/railtie.rb +19 -14
  126. data/lib/active_record/railties/databases.rake +55 -56
  127. data/lib/active_record/readonly_attributes.rb +0 -1
  128. data/lib/active_record/reflection.rb +281 -117
  129. data/lib/active_record/relation/batches.rb +0 -1
  130. data/lib/active_record/relation/calculations.rb +41 -37
  131. data/lib/active_record/relation/delegation.rb +1 -1
  132. data/lib/active_record/relation/finder_methods.rb +71 -48
  133. data/lib/active_record/relation/merger.rb +39 -29
  134. data/lib/active_record/relation/predicate_builder/array_handler.rb +32 -13
  135. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  136. data/lib/active_record/relation/predicate_builder.rb +42 -12
  137. data/lib/active_record/relation/query_methods.rb +130 -73
  138. data/lib/active_record/relation/spawn_methods.rb +10 -3
  139. data/lib/active_record/relation.rb +57 -25
  140. data/lib/active_record/result.rb +18 -7
  141. data/lib/active_record/sanitization.rb +12 -2
  142. data/lib/active_record/schema.rb +0 -1
  143. data/lib/active_record/schema_dumper.rb +59 -28
  144. data/lib/active_record/schema_migration.rb +5 -4
  145. data/lib/active_record/scoping/default.rb +6 -4
  146. data/lib/active_record/scoping/named.rb +4 -0
  147. data/lib/active_record/serializers/xml_serializer.rb +3 -7
  148. data/lib/active_record/statement_cache.rb +95 -10
  149. data/lib/active_record/store.rb +5 -5
  150. data/lib/active_record/tasks/database_tasks.rb +61 -8
  151. data/lib/active_record/tasks/mysql_database_tasks.rb +32 -17
  152. data/lib/active_record/tasks/postgresql_database_tasks.rb +20 -9
  153. data/lib/active_record/timestamp.rb +9 -7
  154. data/lib/active_record/transactions.rb +54 -28
  155. data/lib/active_record/type/big_integer.rb +13 -0
  156. data/lib/active_record/type/binary.rb +50 -0
  157. data/lib/active_record/type/boolean.rb +31 -0
  158. data/lib/active_record/type/date.rb +50 -0
  159. data/lib/active_record/type/date_time.rb +54 -0
  160. data/lib/active_record/type/decimal.rb +64 -0
  161. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  162. data/lib/active_record/type/decorator.rb +14 -0
  163. data/lib/active_record/type/float.rb +19 -0
  164. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  165. data/lib/active_record/type/integer.rb +59 -0
  166. data/lib/active_record/type/mutable.rb +16 -0
  167. data/lib/active_record/type/numeric.rb +36 -0
  168. data/lib/active_record/type/serialized.rb +62 -0
  169. data/lib/active_record/type/string.rb +40 -0
  170. data/lib/active_record/type/text.rb +11 -0
  171. data/lib/active_record/type/time.rb +26 -0
  172. data/lib/active_record/type/time_value.rb +38 -0
  173. data/lib/active_record/type/type_map.rb +64 -0
  174. data/lib/active_record/type/unsigned_integer.rb +15 -0
  175. data/lib/active_record/type/value.rb +110 -0
  176. data/lib/active_record/type.rb +23 -0
  177. data/lib/active_record/validations/associated.rb +5 -3
  178. data/lib/active_record/validations/presence.rb +5 -3
  179. data/lib/active_record/validations/uniqueness.rb +24 -20
  180. data/lib/active_record/validations.rb +25 -19
  181. data/lib/active_record.rb +5 -0
  182. data/lib/rails/generators/active_record/migration/migration_generator.rb +8 -4
  183. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +1 -1
  184. data/lib/rails/generators/active_record/model/templates/model.rb +1 -1
  185. metadata +66 -11
  186. 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
- ranges, values = values.partition { |v| v.is_a?(Range) }
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
- values_predicate = if values.include?(nil)
9
- values = values.compact
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
- attribute.eq(nil)
14
- when 1
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
- else
20
- attribute.in(values)
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.in(range) }
24
- array_predicates << values_predicate
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,7 @@ module ActiveRecord
6
6
  value = value.select(value.klass.arel_table[value.klass.primary_key])
7
7
  end
8
8
 
9
- attribute.in(value.arel.ast)
9
+ attribute.in(value.arel)
10
10
  end
11
11
  end
12
12
  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
- hash = hash.dup
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.to_sym)
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.to_sym)
59
- if reflection.polymorphic? && base_class = polymorphic_base_class_from_value(value)
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
- register_handler(BasicObject, ->(attribute, value) { attribute.eq(value) })
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.in(value) })
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
- private
117
- def self.build(attribute, value)
118
- handler_for(value).call(attribute, value)
119
- end
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
- def self.handler_for(object)
122
- @handlers.detect { |klass, _| klass === object }.last
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 conjuction with +includes+.
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 = :chain, *rest) # :nodoc:
564
- if opts == :chain
565
- WhereChain.new(self)
566
- else
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
- self.reverse_order_value = !reverse_order_value
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 - ['']).uniq)
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, select_values.uniq)
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! do |rel|
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
- joins = join_dependency.join_constraints stashed_association_joins
1045
+ join_infos = join_dependency.join_constraints stashed_association_joins
1003
1046
 
1004
- joins.each { |join| manager.from(join) }
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, selects)
1012
- if !selects.empty?
1013
- expanded_select = selects.map do |field|
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.grep(Hash) do |h|
1054
- unless (h.values - [:asc, :desc]).empty?
1055
- raise ArgumentError, 'Direction should be :asc or :desc'
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 !other.is_a?(Relation) && other.respond_to?(:to_proc)
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
- klass = other.is_a?(Hash) ? Relation::HashMerger : Relation::Merger
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