activerecord 3.2.22.4 → 4.0.13

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 (173) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +2799 -617
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +23 -32
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record/aggregations.rb +40 -34
  7. data/lib/active_record/association_relation.rb +22 -0
  8. data/lib/active_record/associations/alias_tracker.rb +4 -2
  9. data/lib/active_record/associations/association.rb +60 -46
  10. data/lib/active_record/associations/association_scope.rb +46 -40
  11. data/lib/active_record/associations/belongs_to_association.rb +17 -4
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -1
  13. data/lib/active_record/associations/builder/association.rb +81 -28
  14. data/lib/active_record/associations/builder/belongs_to.rb +73 -56
  15. data/lib/active_record/associations/builder/collection_association.rb +54 -40
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +23 -41
  17. data/lib/active_record/associations/builder/has_many.rb +8 -64
  18. data/lib/active_record/associations/builder/has_one.rb +13 -50
  19. data/lib/active_record/associations/builder/singular_association.rb +13 -13
  20. data/lib/active_record/associations/collection_association.rb +130 -96
  21. data/lib/active_record/associations/collection_proxy.rb +916 -63
  22. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +15 -13
  23. data/lib/active_record/associations/has_many_association.rb +35 -8
  24. data/lib/active_record/associations/has_many_through_association.rb +37 -17
  25. data/lib/active_record/associations/has_one_association.rb +42 -19
  26. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  27. data/lib/active_record/associations/join_dependency/join_association.rb +39 -22
  28. data/lib/active_record/associations/join_dependency/join_base.rb +2 -2
  29. data/lib/active_record/associations/join_dependency/join_part.rb +21 -8
  30. data/lib/active_record/associations/join_dependency.rb +30 -9
  31. data/lib/active_record/associations/join_helper.rb +1 -11
  32. data/lib/active_record/associations/preloader/association.rb +29 -33
  33. data/lib/active_record/associations/preloader/collection_association.rb +1 -1
  34. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +2 -2
  35. data/lib/active_record/associations/preloader/has_many_through.rb +6 -2
  36. data/lib/active_record/associations/preloader/has_one.rb +1 -1
  37. data/lib/active_record/associations/preloader/through_association.rb +13 -17
  38. data/lib/active_record/associations/preloader.rb +20 -43
  39. data/lib/active_record/associations/singular_association.rb +11 -11
  40. data/lib/active_record/associations/through_association.rb +3 -3
  41. data/lib/active_record/associations.rb +223 -282
  42. data/lib/active_record/attribute_assignment.rb +134 -154
  43. data/lib/active_record/attribute_methods/before_type_cast.rb +44 -5
  44. data/lib/active_record/attribute_methods/dirty.rb +36 -29
  45. data/lib/active_record/attribute_methods/primary_key.rb +45 -31
  46. data/lib/active_record/attribute_methods/query.rb +5 -4
  47. data/lib/active_record/attribute_methods/read.rb +67 -90
  48. data/lib/active_record/attribute_methods/serialization.rb +133 -70
  49. data/lib/active_record/attribute_methods/time_zone_conversion.rb +51 -45
  50. data/lib/active_record/attribute_methods/write.rb +34 -39
  51. data/lib/active_record/attribute_methods.rb +268 -108
  52. data/lib/active_record/autosave_association.rb +80 -73
  53. data/lib/active_record/base.rb +54 -451
  54. data/lib/active_record/callbacks.rb +60 -22
  55. data/lib/active_record/coders/yaml_column.rb +18 -21
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +347 -197
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +146 -138
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +25 -19
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +19 -3
  61. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +151 -142
  62. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
  63. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +499 -217
  64. data/lib/active_record/connection_adapters/abstract/transaction.rb +208 -0
  65. data/lib/active_record/connection_adapters/abstract_adapter.rb +209 -44
  66. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +169 -61
  67. data/lib/active_record/connection_adapters/column.rb +67 -36
  68. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  69. data/lib/active_record/connection_adapters/mysql2_adapter.rb +28 -29
  70. data/lib/active_record/connection_adapters/mysql_adapter.rb +200 -73
  71. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +98 -0
  72. data/lib/active_record/connection_adapters/postgresql/cast.rb +160 -0
  73. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +240 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid.rb +374 -0
  75. data/lib/active_record/connection_adapters/postgresql/quoting.rb +183 -0
  76. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  77. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +508 -0
  78. data/lib/active_record/connection_adapters/postgresql_adapter.rb +544 -899
  79. data/lib/active_record/connection_adapters/schema_cache.rb +76 -16
  80. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +595 -16
  81. data/lib/active_record/connection_handling.rb +98 -0
  82. data/lib/active_record/core.rb +472 -0
  83. data/lib/active_record/counter_cache.rb +107 -108
  84. data/lib/active_record/dynamic_matchers.rb +115 -63
  85. data/lib/active_record/errors.rb +36 -18
  86. data/lib/active_record/explain.rb +15 -63
  87. data/lib/active_record/explain_registry.rb +30 -0
  88. data/lib/active_record/explain_subscriber.rb +8 -4
  89. data/lib/active_record/fixture_set/file.rb +55 -0
  90. data/lib/active_record/fixtures.rb +159 -155
  91. data/lib/active_record/inheritance.rb +93 -59
  92. data/lib/active_record/integration.rb +8 -8
  93. data/lib/active_record/locale/en.yml +8 -1
  94. data/lib/active_record/locking/optimistic.rb +39 -43
  95. data/lib/active_record/locking/pessimistic.rb +4 -4
  96. data/lib/active_record/log_subscriber.rb +19 -9
  97. data/lib/active_record/migration/command_recorder.rb +102 -33
  98. data/lib/active_record/migration/join_table.rb +15 -0
  99. data/lib/active_record/migration.rb +411 -173
  100. data/lib/active_record/model_schema.rb +81 -94
  101. data/lib/active_record/nested_attributes.rb +173 -131
  102. data/lib/active_record/null_relation.rb +67 -0
  103. data/lib/active_record/persistence.rb +254 -106
  104. data/lib/active_record/query_cache.rb +18 -36
  105. data/lib/active_record/querying.rb +19 -15
  106. data/lib/active_record/railtie.rb +113 -38
  107. data/lib/active_record/railties/console_sandbox.rb +3 -4
  108. data/lib/active_record/railties/controller_runtime.rb +4 -3
  109. data/lib/active_record/railties/databases.rake +115 -368
  110. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  111. data/lib/active_record/readonly_attributes.rb +7 -3
  112. data/lib/active_record/reflection.rb +110 -61
  113. data/lib/active_record/relation/batches.rb +29 -29
  114. data/lib/active_record/relation/calculations.rb +155 -125
  115. data/lib/active_record/relation/delegation.rb +94 -18
  116. data/lib/active_record/relation/finder_methods.rb +151 -203
  117. data/lib/active_record/relation/merger.rb +188 -0
  118. data/lib/active_record/relation/predicate_builder.rb +85 -42
  119. data/lib/active_record/relation/query_methods.rb +793 -146
  120. data/lib/active_record/relation/spawn_methods.rb +43 -150
  121. data/lib/active_record/relation.rb +293 -173
  122. data/lib/active_record/result.rb +48 -7
  123. data/lib/active_record/runtime_registry.rb +17 -0
  124. data/lib/active_record/sanitization.rb +41 -54
  125. data/lib/active_record/schema.rb +19 -12
  126. data/lib/active_record/schema_dumper.rb +41 -41
  127. data/lib/active_record/schema_migration.rb +46 -0
  128. data/lib/active_record/scoping/default.rb +56 -52
  129. data/lib/active_record/scoping/named.rb +78 -103
  130. data/lib/active_record/scoping.rb +54 -124
  131. data/lib/active_record/serialization.rb +6 -2
  132. data/lib/active_record/serializers/xml_serializer.rb +9 -15
  133. data/lib/active_record/statement_cache.rb +26 -0
  134. data/lib/active_record/store.rb +131 -15
  135. data/lib/active_record/tasks/database_tasks.rb +204 -0
  136. data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
  137. data/lib/active_record/tasks/mysql_database_tasks.rb +144 -0
  138. data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
  139. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  140. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  141. data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
  142. data/lib/active_record/test_case.rb +67 -38
  143. data/lib/active_record/timestamp.rb +16 -11
  144. data/lib/active_record/transactions.rb +73 -51
  145. data/lib/active_record/validations/associated.rb +19 -13
  146. data/lib/active_record/validations/presence.rb +65 -0
  147. data/lib/active_record/validations/uniqueness.rb +110 -57
  148. data/lib/active_record/validations.rb +18 -17
  149. data/lib/active_record/version.rb +7 -6
  150. data/lib/active_record.rb +63 -45
  151. data/lib/rails/generators/active_record/migration/migration_generator.rb +45 -8
  152. data/lib/rails/generators/active_record/{model/templates/migration.rb → migration/templates/create_table_migration.rb} +4 -0
  153. data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
  154. data/lib/rails/generators/active_record/model/model_generator.rb +5 -4
  155. data/lib/rails/generators/active_record/model/templates/model.rb +4 -6
  156. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  157. data/lib/rails/generators/active_record.rb +3 -5
  158. metadata +43 -29
  159. data/examples/associations.png +0 -0
  160. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  161. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  162. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  163. data/lib/active_record/dynamic_finder_match.rb +0 -68
  164. data/lib/active_record/dynamic_scope_match.rb +0 -23
  165. data/lib/active_record/fixtures/file.rb +0 -65
  166. data/lib/active_record/identity_map.rb +0 -162
  167. data/lib/active_record/observer.rb +0 -121
  168. data/lib/active_record/session_store.rb +0 -360
  169. data/lib/rails/generators/active_record/migration.rb +0 -15
  170. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  171. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  172. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  173. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,48 +1,185 @@
1
1
  require 'active_support/core_ext/array/wrap'
2
- require 'active_support/core_ext/object/blank'
2
+ require 'active_model/forbidden_attributes_protection'
3
3
 
4
4
  module ActiveRecord
5
5
  module QueryMethods
6
6
  extend ActiveSupport::Concern
7
7
 
8
- attr_accessor :includes_values, :eager_load_values, :preload_values,
9
- :select_values, :group_values, :order_values, :joins_values,
10
- :where_values, :having_values, :bind_values,
11
- :limit_value, :offset_value, :lock_value, :readonly_value, :create_with_value,
12
- :from_value, :reordering_value, :reverse_order_value,
13
- :uniq_value
8
+ include ActiveModel::ForbiddenAttributesProtection
14
9
 
10
+ # WhereChain objects act as placeholder for queries in which #where does not have any parameter.
11
+ # In this case, #where must be chained with #not to return a new relation.
12
+ class WhereChain
13
+ def initialize(scope)
14
+ @scope = scope
15
+ end
16
+
17
+ # Returns a new relation expressing WHERE + NOT condition according to
18
+ # the conditions in the arguments.
19
+ #
20
+ # +not+ accepts conditions as a string, array, or hash. See #where for
21
+ # more details on each format.
22
+ #
23
+ # User.where.not("name = 'Jon'")
24
+ # # SELECT * FROM users WHERE NOT (name = 'Jon')
25
+ #
26
+ # User.where.not(["name = ?", "Jon"])
27
+ # # SELECT * FROM users WHERE NOT (name = 'Jon')
28
+ #
29
+ # User.where.not(name: "Jon")
30
+ # # SELECT * FROM users WHERE name != 'Jon'
31
+ #
32
+ # User.where.not(name: nil)
33
+ # # SELECT * FROM users WHERE name IS NOT NULL
34
+ #
35
+ # User.where.not(name: %w(Ko1 Nobu))
36
+ # # SELECT * FROM users WHERE name NOT IN ('Ko1', 'Nobu')
37
+ #
38
+ # User.where.not(name: "Jon", role: "admin")
39
+ # # SELECT * FROM users WHERE name != 'Jon' AND role != 'admin'
40
+ def not(opts, *rest)
41
+ where_value = @scope.send(:build_where, opts, rest).map do |rel|
42
+ case rel
43
+ when Arel::Nodes::In
44
+ Arel::Nodes::NotIn.new(rel.left, rel.right)
45
+ when Arel::Nodes::Equality
46
+ Arel::Nodes::NotEqual.new(rel.left, rel.right)
47
+ when String
48
+ Arel::Nodes::Not.new(Arel::Nodes::SqlLiteral.new(rel))
49
+ else
50
+ Arel::Nodes::Not.new(rel)
51
+ end
52
+ end
53
+ @scope.where_values += where_value
54
+ @scope
55
+ end
56
+ end
57
+
58
+ Relation::MULTI_VALUE_METHODS.each do |name|
59
+ class_eval <<-CODE, __FILE__, __LINE__ + 1
60
+ def #{name}_values # def select_values
61
+ @values[:#{name}] || [] # @values[:select] || []
62
+ end # end
63
+ #
64
+ def #{name}_values=(values) # def select_values=(values)
65
+ raise ImmutableRelation if @loaded # raise ImmutableRelation if @loaded
66
+ @values[:#{name}] = values # @values[:select] = values
67
+ end # end
68
+ CODE
69
+ end
70
+
71
+ (Relation::SINGLE_VALUE_METHODS - [:create_with]).each do |name|
72
+ class_eval <<-CODE, __FILE__, __LINE__ + 1
73
+ def #{name}_value # def readonly_value
74
+ @values[:#{name}] # @values[:readonly]
75
+ end # end
76
+ CODE
77
+ end
78
+
79
+ Relation::SINGLE_VALUE_METHODS.each do |name|
80
+ class_eval <<-CODE, __FILE__, __LINE__ + 1
81
+ def #{name}_value=(value) # def readonly_value=(value)
82
+ raise ImmutableRelation if @loaded # raise ImmutableRelation if @loaded
83
+ @values[:#{name}] = value # @values[:readonly] = value
84
+ end # end
85
+ CODE
86
+ end
87
+
88
+ def create_with_value # :nodoc:
89
+ @values[:create_with] || {}
90
+ end
91
+
92
+ alias extensions extending_values
93
+
94
+ # Specify relationships to be included in the result set. For
95
+ # example:
96
+ #
97
+ # users = User.includes(:address)
98
+ # users.each do |user|
99
+ # user.address.city
100
+ # end
101
+ #
102
+ # allows you to access the +address+ attribute of the +User+ model without
103
+ # firing an additional query. This will often result in a
104
+ # performance improvement over a simple +join+.
105
+ #
106
+ # === conditions
107
+ #
108
+ # If you want to add conditions to your included models you'll have
109
+ # to explicitly reference them. For example:
110
+ #
111
+ # User.includes(:posts).where('posts.name = ?', 'example')
112
+ #
113
+ # Will throw an error, but this will work:
114
+ #
115
+ # User.includes(:posts).where('posts.name = ?', 'example').references(:posts)
15
116
  def includes(*args)
16
- args.reject! {|a| a.blank? }
117
+ check_if_method_has_arguments!("includes", args)
118
+ spawn.includes!(*args)
119
+ end
17
120
 
18
- return self if args.empty?
121
+ def includes!(*args) # :nodoc:
122
+ args.reject! {|a| a.blank? }
19
123
 
20
- relation = clone
21
- relation.includes_values = (relation.includes_values + args).flatten.uniq
22
- relation
124
+ self.includes_values = (includes_values + args).flatten.uniq
125
+ self
23
126
  end
24
127
 
128
+ # Forces eager loading by performing a LEFT OUTER JOIN on +args+:
129
+ #
130
+ # User.eager_load(:posts)
131
+ # => SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ...
132
+ # FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" =
133
+ # "users"."id"
25
134
  def eager_load(*args)
26
- return self if args.blank?
135
+ check_if_method_has_arguments!("eager_load", args)
136
+ spawn.eager_load!(*args)
137
+ end
27
138
 
28
- relation = clone
29
- relation.eager_load_values += args
30
- relation
139
+ def eager_load!(*args) # :nodoc:
140
+ self.eager_load_values += args
141
+ self
31
142
  end
32
143
 
144
+ # Allows preloading of +args+, in the same way that +includes+ does:
145
+ #
146
+ # User.preload(:posts)
147
+ # => SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (1, 2, 3)
33
148
  def preload(*args)
34
- return self if args.blank?
149
+ check_if_method_has_arguments!("preload", args)
150
+ spawn.preload!(*args)
151
+ end
35
152
 
36
- relation = clone
37
- relation.preload_values += args
38
- relation
153
+ def preload!(*args) # :nodoc:
154
+ self.preload_values += args
155
+ self
156
+ end
157
+
158
+ # Used to indicate that an association is referenced by an SQL string, and should
159
+ # therefore be JOINed in any query rather than loaded separately.
160
+ #
161
+ # User.includes(:posts).where("posts.name = 'foo'")
162
+ # # => Doesn't JOIN the posts table, resulting in an error.
163
+ #
164
+ # User.includes(:posts).where("posts.name = 'foo'").references(:posts)
165
+ # # => Query now knows the string references posts, so adds a JOIN
166
+ def references(*args)
167
+ check_if_method_has_arguments!("references", args)
168
+ spawn.references!(*args)
169
+ end
170
+
171
+ def references!(*args) # :nodoc:
172
+ args.flatten!
173
+
174
+ self.references_values = (references_values + args.map!(&:to_s)).uniq
175
+ self
39
176
  end
40
177
 
41
178
  # Works in two unique ways.
42
179
  #
43
180
  # First: takes a block so it can be used just like Array#select.
44
181
  #
45
- # Model.scoped.select { |m| m.field == value }
182
+ # Model.all.select { |m| m.field == value }
46
183
  #
47
184
  # This will build an array of objects from the database for the scope,
48
185
  # converting them into an array and iterating through them using Array#select.
@@ -50,8 +187,8 @@ module ActiveRecord
50
187
  # Second: Modifies the SELECT statement for the query so that only certain
51
188
  # fields are retrieved:
52
189
  #
53
- # >> Model.select(:field)
54
- # => [#<Model field:value>]
190
+ # Model.select(:field)
191
+ # # => [#<Model field:value>]
55
192
  #
56
193
  # Although in the above example it looks as though this method returns an
57
194
  # array, it actually returns a relation object and can have other query
@@ -59,38 +196,106 @@ module ActiveRecord
59
196
  #
60
197
  # The argument to the method can also be an array of fields.
61
198
  #
62
- # >> Model.select([:field, :other_field, :and_one_more])
63
- # => [#<Model field: "value", other_field: "value", and_one_more: "value">]
199
+ # Model.select(:field, :other_field, :and_one_more)
200
+ # # => [#<Model field: "value", other_field: "value", and_one_more: "value">]
64
201
  #
65
- # Any attributes that do not have fields retrieved by a select
66
- # will raise a ActiveModel::MissingAttributeError when the getter method for that attribute is used:
202
+ # You can also use one or more strings, which will be used unchanged as SELECT fields.
67
203
  #
68
- # >> Model.select(:field).first.other_field
69
- # => ActiveModel::MissingAttributeError: missing attribute: other_field
70
- def select(value = Proc.new)
204
+ # Model.select('field AS field_one', 'other_field AS field_two')
205
+ # # => [#<Model field: "value", other_field: "value">]
206
+ #
207
+ # If an alias was specified, it will be accessible from the resulting objects:
208
+ #
209
+ # Model.select('field AS field_one').first.field_one
210
+ # # => "value"
211
+ #
212
+ # Accessing attributes of an object that do not have fields retrieved by a select
213
+ # will throw <tt>ActiveModel::MissingAttributeError</tt>:
214
+ #
215
+ # Model.select(:field).first.other_field
216
+ # # => ActiveModel::MissingAttributeError: missing attribute: other_field
217
+ def select(*fields)
71
218
  if block_given?
72
- to_a.select {|*block_args| value.call(*block_args) }
219
+ to_a.select { |*block_args| yield(*block_args) }
73
220
  else
74
- relation = clone
75
- relation.select_values += Array.wrap(value)
76
- relation
221
+ raise ArgumentError, 'Call this with at least one field' if fields.empty?
222
+ spawn._select!(*fields)
77
223
  end
78
224
  end
79
225
 
226
+ def _select!(*fields) # :nodoc:
227
+ self.select_values += fields.flatten
228
+ self
229
+ end
230
+
231
+ # Allows to specify a group attribute:
232
+ #
233
+ # User.group(:name)
234
+ # => SELECT "users".* FROM "users" GROUP BY name
235
+ #
236
+ # Returns an array with distinct records based on the +group+ attribute:
237
+ #
238
+ # User.select([:id, :name])
239
+ # => [#<User id: 1, name: "Oscar">, #<User id: 2, name: "Oscar">, #<User id: 3, name: "Foo">
240
+ #
241
+ # User.group(:name)
242
+ # => [#<User id: 3, name: "Foo", ...>, #<User id: 2, name: "Oscar", ...>]
243
+ #
244
+ # User.group('name AS grouped_name, age')
245
+ # => [#<User id: 3, name: "Foo", age: 21, ...>, #<User id: 2, name: "Oscar", age: 21, ...>, #<User id: 5, name: "Foo", age: 23, ...>]
80
246
  def group(*args)
81
- return self if args.blank?
247
+ check_if_method_has_arguments!("group", args)
248
+ spawn.group!(*args)
249
+ end
250
+
251
+ def group!(*args) # :nodoc:
252
+ args.flatten!
82
253
 
83
- relation = clone
84
- relation.group_values += args.flatten
85
- relation
254
+ self.group_values += args
255
+ self
86
256
  end
87
257
 
258
+ # Allows to specify an order attribute:
259
+ #
260
+ # User.order('name')
261
+ # => SELECT "users".* FROM "users" ORDER BY name
262
+ #
263
+ # User.order('name DESC')
264
+ # => SELECT "users".* FROM "users" ORDER BY name DESC
265
+ #
266
+ # User.order('name DESC, email')
267
+ # => SELECT "users".* FROM "users" ORDER BY name DESC, email
268
+ #
269
+ # User.order(:name)
270
+ # => SELECT "users".* FROM "users" ORDER BY "users"."name" ASC
271
+ #
272
+ # User.order(email: :desc)
273
+ # => SELECT "users".* FROM "users" ORDER BY "users"."email" DESC
274
+ #
275
+ # User.order(:name, email: :desc)
276
+ # => SELECT "users".* FROM "users" ORDER BY "users"."name" ASC, "users"."email" DESC
88
277
  def order(*args)
89
- return self if args.blank?
278
+ check_if_method_has_arguments!("order", args)
279
+ spawn.order!(*args)
280
+ end
281
+
282
+ def order!(*args) # :nodoc:
283
+ args.flatten!
284
+ validate_order_args args
285
+
286
+ references = args.reject { |arg| Arel::Node === arg }
287
+ references.map! { |arg| arg =~ /^([a-zA-Z]\w*)\.(\w+)/ && $1 }.compact!
288
+ references!(references) if references.any?
90
289
 
91
- relation = clone
92
- relation.order_values += args.flatten
93
- relation
290
+ # if a symbol is given we prepend the quoted table name
291
+ args = args.map! { |arg|
292
+ arg.is_a?(Symbol) ?
293
+ Arel::Nodes::Ascending.new(klass.arel_table[arg]) :
294
+ arg
295
+ }
296
+
297
+ self.order_values += args
298
+ self
94
299
  end
95
300
 
96
301
  # Replaces any existing order defined on the relation with the specified order.
@@ -102,90 +307,413 @@ module ActiveRecord
102
307
  # User.order('email DESC').reorder('id ASC').order('name ASC')
103
308
  #
104
309
  # generates a query with 'ORDER BY id ASC, name ASC'.
105
- #
106
310
  def reorder(*args)
107
- return self if args.blank?
311
+ check_if_method_has_arguments!("reorder", args)
312
+ spawn.reorder!(*args)
313
+ end
314
+
315
+ def reorder!(*args) # :nodoc:
316
+ args.flatten!
317
+ validate_order_args args
108
318
 
109
- relation = clone
110
- relation.reordering_value = true
111
- relation.order_values = args.flatten
112
- relation
319
+ self.reordering_value = true
320
+ self.order_values = args
321
+ self
113
322
  end
114
323
 
115
- def joins(*args)
116
- return self if args.compact.blank?
324
+ VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock,
325
+ :limit, :offset, :joins, :includes, :from,
326
+ :readonly, :having])
117
327
 
118
- relation = clone
328
+ # Removes an unwanted relation that is already defined on a chain of relations.
329
+ # This is useful when passing around chains of relations and would like to
330
+ # modify the relations without reconstructing the entire chain.
331
+ #
332
+ # User.order('email DESC').unscope(:order) == User.all
333
+ #
334
+ # The method arguments are symbols which correspond to the names of the methods
335
+ # which should be unscoped. The valid arguments are given in VALID_UNSCOPING_VALUES.
336
+ # The method can also be called with multiple arguments. For example:
337
+ #
338
+ # User.order('email DESC').select('id').where(name: "John")
339
+ # .unscope(:order, :select, :where) == User.all
340
+ #
341
+ # One can additionally pass a hash as an argument to unscope specific :where values.
342
+ # This is done by passing a hash with a single key-value pair. The key should be
343
+ # :where and the value should be the where value to unscope. For example:
344
+ #
345
+ # User.where(name: "John", active: true).unscope(where: :name)
346
+ # == User.where(active: true)
347
+ #
348
+ # Note that this method is more generalized than ActiveRecord::SpawnMethods#except
349
+ # because #except will only affect a particular relation's values. It won't wipe
350
+ # the order, grouping, etc. when that relation is merged. For example:
351
+ #
352
+ # Post.comments.except(:order)
353
+ #
354
+ # will still have an order if it comes from the default_scope on Comment.
355
+ def unscope(*args)
356
+ check_if_method_has_arguments!("unscope", args)
357
+ spawn.unscope!(*args)
358
+ end
119
359
 
360
+ def unscope!(*args) # :nodoc:
120
361
  args.flatten!
121
- relation.joins_values += args
122
362
 
123
- relation
363
+ args.each do |scope|
364
+ case scope
365
+ when Symbol
366
+ symbol_unscoping(scope)
367
+ when Hash
368
+ scope.each do |key, target_value|
369
+ if key != :where
370
+ raise ArgumentError, "Hash arguments in .unscope(*args) must have :where as the key."
371
+ end
372
+
373
+ Array(target_value).each do |val|
374
+ where_unscoping(val)
375
+ end
376
+ end
377
+ else
378
+ raise ArgumentError, "Unrecognized scoping: #{args.inspect}. Use .unscope(where: :attribute_name) or .unscope(:order), for example."
379
+ end
380
+ end
381
+
382
+ self
383
+ end
384
+
385
+ # Performs a joins on +args+:
386
+ #
387
+ # User.joins(:posts)
388
+ # => SELECT "users".* FROM "users" INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
389
+ #
390
+ # You can use strings in order to customize your joins:
391
+ #
392
+ # User.joins("LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id")
393
+ # => SELECT "users".* FROM "users" LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id
394
+ def joins(*args)
395
+ check_if_method_has_arguments!("joins", args)
396
+ spawn.joins!(*args.compact.flatten)
397
+ end
398
+
399
+ def joins!(*args) # :nodoc:
400
+ self.joins_values += args
401
+ self
124
402
  end
125
403
 
126
404
  def bind(value)
127
- relation = clone
128
- relation.bind_values += [value]
129
- relation
405
+ spawn.bind!(value)
406
+ end
407
+
408
+ def bind!(value) # :nodoc:
409
+ self.bind_values += [value]
410
+ self
411
+ end
412
+
413
+ # Returns a new relation, which is the result of filtering the current relation
414
+ # according to the conditions in the arguments.
415
+ #
416
+ # #where accepts conditions in one of several formats. In the examples below, the resulting
417
+ # SQL is given as an illustration; the actual query generated may be different depending
418
+ # on the database adapter.
419
+ #
420
+ # === string
421
+ #
422
+ # A single string, without additional arguments, is passed to the query
423
+ # constructor as a SQL fragment, and used in the where clause of the query.
424
+ #
425
+ # Client.where("orders_count = '2'")
426
+ # # SELECT * from clients where orders_count = '2';
427
+ #
428
+ # Note that building your own string from user input may expose your application
429
+ # to injection attacks if not done properly. As an alternative, it is recommended
430
+ # to use one of the following methods.
431
+ #
432
+ # === array
433
+ #
434
+ # If an array is passed, then the first element of the array is treated as a template, and
435
+ # the remaining elements are inserted into the template to generate the condition.
436
+ # Active Record takes care of building the query to avoid injection attacks, and will
437
+ # convert from the ruby type to the database type where needed. Elements are inserted
438
+ # into the string in the order in which they appear.
439
+ #
440
+ # User.where(["name = ? and email = ?", "Joe", "joe@example.com"])
441
+ # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
442
+ #
443
+ # Alternatively, you can use named placeholders in the template, and pass a hash as the
444
+ # second element of the array. The names in the template are replaced with the corresponding
445
+ # values from the hash.
446
+ #
447
+ # User.where(["name = :name and email = :email", { name: "Joe", email: "joe@example.com" }])
448
+ # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
449
+ #
450
+ # This can make for more readable code in complex queries.
451
+ #
452
+ # Lastly, you can use sprintf-style % escapes in the template. This works slightly differently
453
+ # than the previous methods; you are responsible for ensuring that the values in the template
454
+ # are properly quoted. The values are passed to the connector for quoting, but the caller
455
+ # is responsible for ensuring they are enclosed in quotes in the resulting SQL. After quoting,
456
+ # the values are inserted using the same escapes as the Ruby core method <tt>Kernel::sprintf</tt>.
457
+ #
458
+ # User.where(["name = '%s' and email = '%s'", "Joe", "joe@example.com"])
459
+ # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
460
+ #
461
+ # If #where is called with multiple arguments, these are treated as if they were passed as
462
+ # the elements of a single array.
463
+ #
464
+ # User.where("name = :name and email = :email", { name: "Joe", email: "joe@example.com" })
465
+ # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
466
+ #
467
+ # When using strings to specify conditions, you can use any operator available from
468
+ # the database. While this provides the most flexibility, you can also unintentionally introduce
469
+ # dependencies on the underlying database. If your code is intended for general consumption,
470
+ # test with multiple database backends.
471
+ #
472
+ # === hash
473
+ #
474
+ # #where will also accept a hash condition, in which the keys are fields and the values
475
+ # are values to be searched for.
476
+ #
477
+ # Fields can be symbols or strings. Values can be single values, arrays, or ranges.
478
+ #
479
+ # User.where({ name: "Joe", email: "joe@example.com" })
480
+ # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com'
481
+ #
482
+ # User.where({ name: ["Alice", "Bob"]})
483
+ # # SELECT * FROM users WHERE name IN ('Alice', 'Bob')
484
+ #
485
+ # User.where({ created_at: (Time.now.midnight - 1.day)..Time.now.midnight })
486
+ # # SELECT * FROM users WHERE (created_at BETWEEN '2012-06-09 07:00:00.000000' AND '2012-06-10 07:00:00.000000')
487
+ #
488
+ # In the case of a belongs_to relationship, an association key can be used
489
+ # to specify the model if an ActiveRecord object is used as the value.
490
+ #
491
+ # author = Author.find(1)
492
+ #
493
+ # # The following queries will be equivalent:
494
+ # Post.where(author: author)
495
+ # Post.where(author_id: author)
496
+ #
497
+ # This also works with polymorphic belongs_to relationships:
498
+ #
499
+ # treasure = Treasure.create(name: 'gold coins')
500
+ # treasure.price_estimates << PriceEstimate.create(price: 125)
501
+ #
502
+ # # The following queries will be equivalent:
503
+ # PriceEstimate.where(estimate_of: treasure)
504
+ # PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: treasure)
505
+ #
506
+ # === Joins
507
+ #
508
+ # If the relation is the result of a join, you may create a condition which uses any of the
509
+ # tables in the join. For string and array conditions, use the table name in the condition.
510
+ #
511
+ # User.joins(:posts).where("posts.created_at < ?", Time.now)
512
+ #
513
+ # For hash conditions, you can either use the table name in the key, or use a sub-hash.
514
+ #
515
+ # User.joins(:posts).where({ "posts.published" => true })
516
+ # User.joins(:posts).where({ posts: { published: true } })
517
+ #
518
+ # === no argument
519
+ #
520
+ # If no argument is passed, #where returns a new instance of WhereChain, that
521
+ # can be chained with #not to return a new relation that negates the where clause.
522
+ #
523
+ # User.where.not(name: "Jon")
524
+ # # SELECT * FROM users WHERE name != 'Jon'
525
+ #
526
+ # See WhereChain for more details on #not.
527
+ #
528
+ # === blank condition
529
+ #
530
+ # If the condition is any blank-ish object, then #where is a no-op and returns
531
+ # the current relation.
532
+ def where(opts = :chain, *rest)
533
+ if opts == :chain
534
+ WhereChain.new(spawn)
535
+ elsif opts.blank?
536
+ self
537
+ else
538
+ spawn.where!(opts, *rest)
539
+ end
130
540
  end
131
541
 
132
- def where(opts, *rest)
133
- return self if opts.blank?
542
+ def where!(opts = :chain, *rest) # :nodoc:
543
+ if opts == :chain
544
+ WhereChain.new(self)
545
+ else
546
+ if Hash === opts
547
+ opts = sanitize_forbidden_attributes(opts)
548
+ references!(PredicateBuilder.references(opts))
549
+ end
134
550
 
135
- relation = clone
136
- relation.where_values += build_where(opts, rest)
137
- relation
551
+ self.where_values += build_where(opts, rest)
552
+ self
553
+ end
138
554
  end
139
555
 
556
+ # Allows to specify a HAVING clause. Note that you can't use HAVING
557
+ # without also specifying a GROUP clause.
558
+ #
559
+ # Order.having('SUM(price) > 30').group('user_id')
140
560
  def having(opts, *rest)
141
- return self if opts.blank?
561
+ opts.blank? ? self : spawn.having!(opts, *rest)
562
+ end
563
+
564
+ def having!(opts, *rest) # :nodoc:
565
+ references!(PredicateBuilder.references(opts)) if Hash === opts
142
566
 
143
- relation = clone
144
- relation.having_values += build_where(opts, rest)
145
- relation
567
+ self.having_values += build_where(opts, rest)
568
+ self
146
569
  end
147
570
 
571
+ # Specifies a limit for the number of records to retrieve.
572
+ #
573
+ # User.limit(10) # generated SQL has 'LIMIT 10'
574
+ #
575
+ # User.limit(10).limit(20) # generated SQL has 'LIMIT 20'
148
576
  def limit(value)
149
- relation = clone
150
- relation.limit_value = value
151
- relation
577
+ spawn.limit!(value)
578
+ end
579
+
580
+ def limit!(value) # :nodoc:
581
+ self.limit_value = value
582
+ self
152
583
  end
153
584
 
585
+ # Specifies the number of rows to skip before returning rows.
586
+ #
587
+ # User.offset(10) # generated SQL has "OFFSET 10"
588
+ #
589
+ # Should be used with order.
590
+ #
591
+ # User.offset(10).order("name ASC")
154
592
  def offset(value)
155
- relation = clone
156
- relation.offset_value = value
157
- relation
593
+ spawn.offset!(value)
158
594
  end
159
595
 
596
+ def offset!(value) # :nodoc:
597
+ self.offset_value = value
598
+ self
599
+ end
600
+
601
+ # Specifies locking settings (default to +true+). For more information
602
+ # on locking, please see +ActiveRecord::Locking+.
160
603
  def lock(locks = true)
161
- relation = clone
604
+ spawn.lock!(locks)
605
+ end
162
606
 
607
+ def lock!(locks = true) # :nodoc:
163
608
  case locks
164
609
  when String, TrueClass, NilClass
165
- relation.lock_value = locks || true
610
+ self.lock_value = locks || true
166
611
  else
167
- relation.lock_value = false
612
+ self.lock_value = false
168
613
  end
169
614
 
170
- relation
615
+ self
171
616
  end
172
617
 
618
+ # Returns a chainable relation with zero records.
619
+ #
620
+ # The returned relation implements the Null Object pattern. It is an
621
+ # object with defined null behavior and always returns an empty array of
622
+ # records without querying the database.
623
+ #
624
+ # Any subsequent condition chained to the returned relation will continue
625
+ # generating an empty relation and will not fire any query to the database.
626
+ #
627
+ # Used in cases where a method or scope could return zero records but the
628
+ # result needs to be chainable.
629
+ #
630
+ # For example:
631
+ #
632
+ # @posts = current_user.visible_posts.where(name: params[:name])
633
+ # # => the visible_posts method is expected to return a chainable Relation
634
+ #
635
+ # def visible_posts
636
+ # case role
637
+ # when 'Country Manager'
638
+ # Post.where(country: country)
639
+ # when 'Reviewer'
640
+ # Post.published
641
+ # when 'Bad User'
642
+ # Post.none # => returning [] instead breaks the previous code
643
+ # end
644
+ # end
645
+ #
646
+ def none
647
+ extending(NullRelation)
648
+ end
649
+
650
+ def none! # :nodoc:
651
+ extending!(NullRelation)
652
+ end
653
+
654
+ # Sets readonly attributes for the returned relation. If value is
655
+ # true (default), attempting to update a record will result in an error.
656
+ #
657
+ # users = User.readonly
658
+ # users.first.save
659
+ # => ActiveRecord::ReadOnlyRecord: ActiveRecord::ReadOnlyRecord
173
660
  def readonly(value = true)
174
- relation = clone
175
- relation.readonly_value = value
176
- relation
661
+ spawn.readonly!(value)
662
+ end
663
+
664
+ def readonly!(value = true) # :nodoc:
665
+ self.readonly_value = value
666
+ self
177
667
  end
178
668
 
669
+ # Sets attributes to be used when creating new records from a
670
+ # relation object.
671
+ #
672
+ # users = User.where(name: 'Oscar')
673
+ # users.new.name # => 'Oscar'
674
+ #
675
+ # users = users.create_with(name: 'DHH')
676
+ # users.new.name # => 'DHH'
677
+ #
678
+ # You can pass +nil+ to +create_with+ to reset attributes:
679
+ #
680
+ # users = users.create_with(nil)
681
+ # users.new.name # => 'Oscar'
179
682
  def create_with(value)
180
- relation = clone
181
- relation.create_with_value = value ? create_with_value.merge(value) : {}
182
- relation
683
+ spawn.create_with!(value)
684
+ end
685
+
686
+ def create_with!(value) # :nodoc:
687
+ if value
688
+ value = sanitize_forbidden_attributes(value)
689
+ self.create_with_value = create_with_value.merge(value)
690
+ else
691
+ self.create_with_value = {}
692
+ end
693
+
694
+ self
695
+ end
696
+
697
+ # Specifies table from which the records will be fetched. For example:
698
+ #
699
+ # Topic.select('title').from('posts')
700
+ # #=> SELECT title FROM posts
701
+ #
702
+ # Can accept other relation objects. For example:
703
+ #
704
+ # Topic.select('title').from(Topic.approved)
705
+ # # => SELECT title FROM (SELECT * FROM topics WHERE approved = 't') subquery
706
+ #
707
+ # Topic.select('a.title').from(Topic.approved, :a)
708
+ # # => SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a
709
+ #
710
+ def from(value, subquery_name = nil)
711
+ spawn.from!(value, subquery_name)
183
712
  end
184
713
 
185
- def from(value)
186
- relation = clone
187
- relation.from_value = value
188
- relation
714
+ def from!(value, subquery_name = nil) # :nodoc:
715
+ self.from_value = [value, subquery_name]
716
+ self
189
717
  end
190
718
 
191
719
  # Specifies whether the records should be unique or not. For example:
@@ -193,16 +721,22 @@ module ActiveRecord
193
721
  # User.select(:name)
194
722
  # # => Might return two records with the same name
195
723
  #
196
- # User.select(:name).uniq
197
- # # => Returns 1 record per unique name
724
+ # User.select(:name).distinct
725
+ # # => Returns 1 record per distinct name
198
726
  #
199
- # User.select(:name).uniq.uniq(false)
727
+ # User.select(:name).distinct.distinct(false)
200
728
  # # => You can also remove the uniqueness
201
- def uniq(value = true)
202
- relation = clone
203
- relation.uniq_value = value
204
- relation
729
+ def distinct(value = true)
730
+ spawn.distinct!(value)
731
+ end
732
+ alias uniq distinct
733
+
734
+ # Like #distinct, but modifies relation in place.
735
+ def distinct!(value = true) # :nodoc:
736
+ self.distinct_value = value
737
+ self
205
738
  end
739
+ alias uniq! distinct!
206
740
 
207
741
  # Used to extend a scope with additional methods, either through
208
742
  # a module or through a block provided.
@@ -217,16 +751,16 @@ module ActiveRecord
217
751
  # end
218
752
  # end
219
753
  #
220
- # scope = Model.scoped.extending(Pagination)
754
+ # scope = Model.all.extending(Pagination)
221
755
  # scope.page(params[:page])
222
756
  #
223
757
  # You can also pass a list of modules:
224
758
  #
225
- # scope = Model.scoped.extending(Pagination, SomethingElse)
759
+ # scope = Model.all.extending(Pagination, SomethingElse)
226
760
  #
227
761
  # === Using a block
228
762
  #
229
- # scope = Model.scoped.extending do
763
+ # scope = Model.all.extending do
230
764
  # def page(number)
231
765
  # # pagination code goes here
232
766
  # end
@@ -235,60 +769,106 @@ module ActiveRecord
235
769
  #
236
770
  # You can also use a block and a module list:
237
771
  #
238
- # scope = Model.scoped.extending(Pagination) do
772
+ # scope = Model.all.extending(Pagination) do
239
773
  # def per_page(number)
240
774
  # # pagination code goes here
241
775
  # end
242
776
  # end
243
- def extending(*modules)
244
- modules << Module.new(&Proc.new) if block_given?
777
+ def extending(*modules, &block)
778
+ if modules.any? || block
779
+ spawn.extending!(*modules, &block)
780
+ else
781
+ self
782
+ end
783
+ end
245
784
 
246
- return self if modules.empty?
785
+ def extending!(*modules, &block) # :nodoc:
786
+ modules << Module.new(&block) if block_given?
247
787
 
248
- relation = clone
249
- relation.send(:apply_modules, modules.flatten)
250
- relation
788
+ self.extending_values += modules.flatten
789
+ extend(*extending_values) if extending_values.any?
790
+
791
+ self
251
792
  end
252
793
 
794
+ # Reverse the existing order clause on the relation.
795
+ #
796
+ # User.order('name ASC').reverse_order # generated SQL has 'ORDER BY name DESC'
253
797
  def reverse_order
254
- relation = clone
255
- relation.reverse_order_value = !relation.reverse_order_value
256
- relation
798
+ spawn.reverse_order!
257
799
  end
258
800
 
801
+ def reverse_order! # :nodoc:
802
+ self.reverse_order_value = !reverse_order_value
803
+ self
804
+ end
805
+
806
+ # Returns the Arel object associated with the relation.
259
807
  def arel
260
808
  @arel ||= with_default_scope.build_arel
261
809
  end
262
810
 
811
+ # Like #arel, but ignores the default scope of the model.
263
812
  def build_arel
264
- arel = table.from table
813
+ arel = Arel::SelectManager.new(table.engine, table)
265
814
 
266
- build_joins(arel, @joins_values) unless @joins_values.empty?
815
+ build_joins(arel, joins_values.flatten) unless joins_values.empty?
267
816
 
268
- collapse_wheres(arel, (@where_values - ['']).uniq)
817
+ collapse_wheres(arel, (where_values - ['']).uniq)
269
818
 
270
- arel.having(*@having_values.uniq.reject{|h| h.blank?}) unless @having_values.empty?
819
+ arel.having(*having_values.uniq.reject{|h| h.blank?}) unless having_values.empty?
271
820
 
272
- arel.take(connection.sanitize_limit(@limit_value)) if @limit_value
273
- arel.skip(@offset_value.to_i) if @offset_value
821
+ arel.take(connection.sanitize_limit(limit_value)) if limit_value
822
+ arel.skip(offset_value.to_i) if offset_value
274
823
 
275
- arel.group(*@group_values.uniq.reject{|g| g.blank?}) unless @group_values.empty?
824
+ arel.group(*group_values.uniq.reject{|g| g.blank?}) unless group_values.empty?
276
825
 
277
- order = @order_values
278
- order = reverse_sql_order(order) if @reverse_order_value
279
- arel.order(*order.uniq.reject{|o| o.blank?}) unless order.empty?
826
+ build_order(arel)
280
827
 
281
- build_select(arel, @select_values.uniq)
828
+ build_select(arel, select_values.uniq)
282
829
 
283
- arel.distinct(@uniq_value)
284
- arel.from(@from_value) if @from_value
285
- arel.lock(@lock_value) if @lock_value
830
+ arel.distinct(distinct_value)
831
+ arel.from(build_from) if from_value
832
+ arel.lock(lock_value) if lock_value
286
833
 
287
834
  arel
288
835
  end
289
836
 
290
837
  private
291
838
 
839
+ def symbol_unscoping(scope)
840
+ if !VALID_UNSCOPING_VALUES.include?(scope)
841
+ raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
842
+ end
843
+
844
+ single_val_method = Relation::SINGLE_VALUE_METHODS.include?(scope)
845
+ unscope_code = :"#{scope}_value#{'s' unless single_val_method}="
846
+
847
+ case scope
848
+ when :order
849
+ self.send(:reverse_order_value=, false)
850
+ result = []
851
+ else
852
+ result = [] unless single_val_method
853
+ end
854
+
855
+ self.send(unscope_code, result)
856
+ end
857
+
858
+ def where_unscoping(target_value)
859
+ target_value_sym = target_value.to_sym
860
+
861
+ where_values.reject! do |rel|
862
+ case rel
863
+ when Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual
864
+ subrelation = (rel.left.kind_of?(Arel::Attributes::Attribute) ? rel.left : rel.right)
865
+ subrelation.name.to_sym == target_value_sym
866
+ else
867
+ raise "unscope(where: #{target_value.inspect}) failed: unscoping #{rel.class} is unimplemented."
868
+ end
869
+ end
870
+ end
871
+
292
872
  def custom_join_ast(table, joins)
293
873
  joins = joins.reject { |join| join.blank? }
294
874
 
@@ -308,50 +888,71 @@ module ActiveRecord
308
888
  end
309
889
 
310
890
  def collapse_wheres(arel, wheres)
311
- equalities = wheres.grep(Arel::Nodes::Equality)
312
-
313
- arel.where(Arel::Nodes::And.new(equalities)) unless equalities.empty?
314
-
315
- (wheres - equalities).each do |where|
891
+ predicates = wheres.map do |where|
892
+ next where if ::Arel::Nodes::Equality === where
316
893
  where = Arel.sql(where) if String === where
317
- arel.where(Arel::Nodes::Grouping.new(where))
894
+ Arel::Nodes::Grouping.new(where)
318
895
  end
896
+
897
+ arel.where(Arel::Nodes::And.new(predicates)) if predicates.present?
319
898
  end
320
899
 
321
900
  def build_where(opts, other = [])
322
901
  case opts
323
902
  when String, Array
903
+ #TODO: Remove duplication with: /activerecord/lib/active_record/sanitization.rb:113
904
+ values = Hash === other.first ? other.first.values : other
905
+
906
+ values.grep(ActiveRecord::Relation) do |rel|
907
+ self.bind_values += rel.bind_values
908
+ end
909
+
324
910
  [@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))]
325
911
  when Hash
326
912
  attributes = @klass.send(:expand_hash_conditions_for_aggregates, opts)
327
- PredicateBuilder.build_from_hash(table.engine, attributes, table)
913
+
914
+ attributes.values.grep(ActiveRecord::Relation) do |rel|
915
+ self.bind_values += rel.bind_values
916
+ end
917
+
918
+ PredicateBuilder.build_from_hash(klass, attributes, table)
328
919
  else
329
920
  [opts]
330
921
  end
331
922
  end
332
923
 
924
+ def build_from
925
+ opts, name = from_value
926
+ case opts
927
+ when Relation
928
+ name ||= 'subquery'
929
+ self.bind_values = opts.bind_values + self.bind_values
930
+ opts.arel.as(name.to_s)
931
+ else
932
+ opts
933
+ end
934
+ end
935
+
333
936
  def build_joins(manager, joins)
334
937
  buckets = joins.group_by do |join|
335
938
  case join
336
939
  when String
337
- 'string_join'
940
+ :string_join
338
941
  when Hash, Symbol, Array
339
- 'association_join'
942
+ :association_join
340
943
  when ActiveRecord::Associations::JoinDependency::JoinAssociation
341
- 'stashed_join'
944
+ :stashed_join
342
945
  when Arel::Nodes::Join
343
- 'join_node'
946
+ :join_node
344
947
  else
345
948
  raise 'unknown class: %s' % join.class.name
346
949
  end
347
950
  end
348
951
 
349
- association_joins = buckets['association_join'] || []
350
- stashed_association_joins = buckets['stashed_join'] || []
351
- join_nodes = (buckets['join_node'] || []).uniq
352
- string_joins = (buckets['string_join'] || []).map { |x|
353
- x.strip
354
- }.uniq
952
+ association_joins = buckets[:association_join] || []
953
+ stashed_association_joins = buckets[:stashed_join] || []
954
+ join_nodes = (buckets[:join_node] || []).uniq
955
+ string_joins = (buckets[:string_join] || []).map { |x| x.strip }.uniq
355
956
 
356
957
  join_list = join_nodes + custom_join_ast(manager, string_joins)
357
958
 
@@ -384,34 +985,80 @@ module ActiveRecord
384
985
  end
385
986
  end
386
987
 
387
- def apply_modules(modules)
388
- unless modules.empty?
389
- @extensions += modules
390
- modules.each {|extension| extend(extension) }
391
- end
392
- end
393
-
394
988
  def reverse_sql_order(order_query)
395
989
  order_query = ["#{quoted_table_name}.#{quoted_primary_key} ASC"] if order_query.empty?
396
990
 
397
- order_query.map do |o|
991
+ order_query.flat_map do |o|
398
992
  case o
399
993
  when Arel::Nodes::Ordering
400
994
  o.reverse
401
- when String, Symbol
995
+ when String
402
996
  o.to_s.split(',').collect do |s|
403
997
  s.strip!
404
998
  s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC')
405
999
  end
1000
+ when Symbol
1001
+ { o => :desc }
1002
+ when Hash
1003
+ o.each_with_object({}) do |(field, dir), memo|
1004
+ memo[field] = (dir == :asc ? :desc : :asc )
1005
+ end
406
1006
  else
407
1007
  o
408
1008
  end
409
- end.flatten
1009
+ end
410
1010
  end
411
1011
 
412
1012
  def array_of_strings?(o)
413
1013
  o.is_a?(Array) && o.all?{|obj| obj.is_a?(String)}
414
1014
  end
415
1015
 
1016
+ def build_order(arel)
1017
+ orders = order_values
1018
+ orders = reverse_sql_order(orders) if reverse_order_value
1019
+
1020
+ orders = orders.uniq.reject(&:blank?).flat_map do |order|
1021
+ case order
1022
+ when Symbol
1023
+ table[order].asc
1024
+ when Hash
1025
+ order.map { |field, dir| table[field].send(dir) }
1026
+ else
1027
+ order
1028
+ end
1029
+ end
1030
+
1031
+ arel.order(*orders) unless orders.empty?
1032
+ end
1033
+
1034
+ def validate_order_args(args)
1035
+ args.select { |a| Hash === a }.each do |h|
1036
+ unless (h.values - [:asc, :desc]).empty?
1037
+ raise ArgumentError, 'Direction should be :asc or :desc'
1038
+ end
1039
+ end
1040
+ end
1041
+
1042
+ # Checks to make sure that the arguments are not blank. Note that if some
1043
+ # blank-like object were initially passed into the query method, then this
1044
+ # method will not raise an error.
1045
+ #
1046
+ # Example:
1047
+ #
1048
+ # Post.references() # => raises an error
1049
+ # Post.references([]) # => does not raise an error
1050
+ #
1051
+ # This particular method should be called with a method_name and the args
1052
+ # passed into that method as an input. For example:
1053
+ #
1054
+ # def references(*args)
1055
+ # check_if_method_has_arguments!("references", args)
1056
+ # ...
1057
+ # end
1058
+ def check_if_method_has_arguments!(method_name, args)
1059
+ if args.blank?
1060
+ raise ArgumentError, "The method .#{method_name}() must contain arguments."
1061
+ end
1062
+ end
416
1063
  end
417
1064
  end