activerecord 3.2.22.5 → 4.2.11.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

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