activerecord 3.1.10 → 4.2.11

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 (237) hide show
  1. checksums.yaml +6 -6
  2. data/CHANGELOG.md +1837 -338
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +39 -43
  5. data/examples/performance.rb +51 -20
  6. data/examples/simple.rb +4 -4
  7. data/lib/active_record/aggregations.rb +57 -43
  8. data/lib/active_record/association_relation.rb +35 -0
  9. data/lib/active_record/associations/alias_tracker.rb +47 -39
  10. data/lib/active_record/associations/association.rb +71 -85
  11. data/lib/active_record/associations/association_scope.rb +138 -89
  12. data/lib/active_record/associations/belongs_to_association.rb +65 -25
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +9 -3
  14. data/lib/active_record/associations/builder/association.rb +125 -29
  15. data/lib/active_record/associations/builder/belongs_to.rb +91 -60
  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 -52
  20. data/lib/active_record/associations/builder/singular_association.rb +22 -29
  21. data/lib/active_record/associations/collection_association.rb +294 -187
  22. data/lib/active_record/associations/collection_proxy.rb +961 -94
  23. data/lib/active_record/associations/foreign_association.rb +11 -0
  24. data/lib/active_record/associations/has_many_association.rb +118 -23
  25. data/lib/active_record/associations/has_many_through_association.rb +115 -45
  26. data/lib/active_record/associations/has_one_association.rb +57 -24
  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 -102
  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 +61 -32
  38. data/lib/active_record/associations/preloader.rb +113 -87
  39. data/lib/active_record/associations/singular_association.rb +29 -13
  40. data/lib/active_record/associations/through_association.rb +37 -19
  41. data/lib/active_record/associations.rb +505 -371
  42. data/lib/active_record/attribute.rb +163 -0
  43. data/lib/active_record/attribute_assignment.rb +212 -0
  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 +141 -51
  47. data/lib/active_record/attribute_methods/primary_key.rb +87 -36
  48. data/lib/active_record/attribute_methods/query.rb +5 -4
  49. data/lib/active_record/attribute_methods/read.rb +74 -117
  50. data/lib/active_record/attribute_methods/serialization.rb +70 -0
  51. data/lib/active_record/attribute_methods/time_zone_conversion.rb +49 -47
  52. data/lib/active_record/attribute_methods/write.rb +60 -21
  53. data/lib/active_record/attribute_methods.rb +409 -48
  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 +279 -232
  58. data/lib/active_record/base.rb +84 -1969
  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 +422 -243
  63. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  64. data/lib/active_record/connection_adapters/abstract/database_statements.rb +170 -194
  65. data/lib/active_record/connection_adapters/abstract/query_cache.rb +32 -19
  66. data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -57
  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 +273 -170
  70. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +731 -254
  72. data/lib/active_record/connection_adapters/abstract/transaction.rb +215 -0
  73. data/lib/active_record/connection_adapters/abstract_adapter.rb +339 -95
  74. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +946 -0
  75. data/lib/active_record/connection_adapters/column.rb +33 -221
  76. data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
  77. data/lib/active_record/connection_adapters/mysql2_adapter.rb +140 -602
  78. data/lib/active_record/connection_adapters/mysql_adapter.rb +254 -756
  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 +445 -902
  114. data/lib/active_record/connection_adapters/schema_cache.rb +94 -0
  115. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +578 -25
  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 +159 -102
  119. data/lib/active_record/dynamic_matchers.rb +140 -0
  120. data/lib/active_record/enum.rb +197 -0
  121. data/lib/active_record/errors.rb +102 -34
  122. data/lib/active_record/explain.rb +38 -0
  123. data/lib/active_record/explain_registry.rb +30 -0
  124. data/lib/active_record/explain_subscriber.rb +29 -0
  125. data/lib/active_record/fixture_set/file.rb +56 -0
  126. data/lib/active_record/fixtures.rb +318 -260
  127. data/lib/active_record/gem_version.rb +15 -0
  128. data/lib/active_record/inheritance.rb +247 -0
  129. data/lib/active_record/integration.rb +113 -0
  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 +80 -52
  133. data/lib/active_record/locking/pessimistic.rb +27 -5
  134. data/lib/active_record/log_subscriber.rb +25 -18
  135. data/lib/active_record/migration/command_recorder.rb +130 -38
  136. data/lib/active_record/migration/join_table.rb +15 -0
  137. data/lib/active_record/migration.rb +532 -201
  138. data/lib/active_record/model_schema.rb +342 -0
  139. data/lib/active_record/nested_attributes.rb +229 -139
  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 +304 -99
  143. data/lib/active_record/query_cache.rb +25 -43
  144. data/lib/active_record/querying.rb +68 -0
  145. data/lib/active_record/railtie.rb +86 -45
  146. data/lib/active_record/railties/console_sandbox.rb +3 -4
  147. data/lib/active_record/railties/controller_runtime.rb +7 -4
  148. data/lib/active_record/railties/databases.rake +198 -377
  149. data/lib/active_record/railties/jdbcmysql_error.rb +2 -2
  150. data/lib/active_record/readonly_attributes.rb +23 -0
  151. data/lib/active_record/reflection.rb +516 -165
  152. data/lib/active_record/relation/batches.rb +96 -45
  153. data/lib/active_record/relation/calculations.rb +221 -144
  154. data/lib/active_record/relation/delegation.rb +140 -0
  155. data/lib/active_record/relation/finder_methods.rb +362 -243
  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 -41
  160. data/lib/active_record/relation/query_methods.rb +982 -155
  161. data/lib/active_record/relation/spawn_methods.rb +50 -110
  162. data/lib/active_record/relation.rb +371 -180
  163. data/lib/active_record/result.rb +109 -12
  164. data/lib/active_record/runtime_registry.rb +22 -0
  165. data/lib/active_record/sanitization.rb +191 -0
  166. data/lib/active_record/schema.rb +19 -13
  167. data/lib/active_record/schema_dumper.rb +111 -61
  168. data/lib/active_record/schema_migration.rb +53 -0
  169. data/lib/active_record/scoping/default.rb +135 -0
  170. data/lib/active_record/scoping/named.rb +164 -0
  171. data/lib/active_record/scoping.rb +87 -0
  172. data/lib/active_record/serialization.rb +7 -45
  173. data/lib/active_record/serializers/xml_serializer.rb +14 -65
  174. data/lib/active_record/statement_cache.rb +111 -0
  175. data/lib/active_record/store.rb +205 -0
  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 +35 -14
  181. data/lib/active_record/transactions.rb +141 -74
  182. data/lib/active_record/translation.rb +22 -0
  183. data/lib/active_record/type/big_integer.rb +13 -0
  184. data/lib/active_record/type/binary.rb +50 -0
  185. data/lib/active_record/type/boolean.rb +31 -0
  186. data/lib/active_record/type/date.rb +50 -0
  187. data/lib/active_record/type/date_time.rb +54 -0
  188. data/lib/active_record/type/decimal.rb +64 -0
  189. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  190. data/lib/active_record/type/decorator.rb +14 -0
  191. data/lib/active_record/type/float.rb +19 -0
  192. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  193. data/lib/active_record/type/integer.rb +59 -0
  194. data/lib/active_record/type/mutable.rb +16 -0
  195. data/lib/active_record/type/numeric.rb +36 -0
  196. data/lib/active_record/type/serialized.rb +62 -0
  197. data/lib/active_record/type/string.rb +40 -0
  198. data/lib/active_record/type/text.rb +11 -0
  199. data/lib/active_record/type/time.rb +26 -0
  200. data/lib/active_record/type/time_value.rb +38 -0
  201. data/lib/active_record/type/type_map.rb +64 -0
  202. data/lib/active_record/type/unsigned_integer.rb +15 -0
  203. data/lib/active_record/type/value.rb +110 -0
  204. data/lib/active_record/type.rb +23 -0
  205. data/lib/active_record/validations/associated.rb +27 -18
  206. data/lib/active_record/validations/presence.rb +67 -0
  207. data/lib/active_record/validations/uniqueness.rb +125 -66
  208. data/lib/active_record/validations.rb +37 -30
  209. data/lib/active_record/version.rb +5 -7
  210. data/lib/active_record.rb +80 -25
  211. data/lib/rails/generators/active_record/migration/migration_generator.rb +54 -9
  212. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
  213. data/lib/rails/generators/active_record/migration/templates/migration.rb +25 -11
  214. data/lib/rails/generators/active_record/migration.rb +11 -8
  215. data/lib/rails/generators/active_record/model/model_generator.rb +17 -4
  216. data/lib/rails/generators/active_record/model/templates/model.rb +5 -2
  217. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  218. data/lib/rails/generators/active_record.rb +3 -11
  219. metadata +132 -53
  220. data/examples/associations.png +0 -0
  221. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -62
  222. data/lib/active_record/associations/join_helper.rb +0 -55
  223. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  224. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -135
  225. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -556
  226. data/lib/active_record/dynamic_finder_match.rb +0 -56
  227. data/lib/active_record/dynamic_scope_match.rb +0 -23
  228. data/lib/active_record/identity_map.rb +0 -163
  229. data/lib/active_record/named_scope.rb +0 -200
  230. data/lib/active_record/observer.rb +0 -121
  231. data/lib/active_record/session_store.rb +0 -358
  232. data/lib/active_record/test_case.rb +0 -69
  233. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -17
  234. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  235. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  236. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  237. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -16
@@ -1,47 +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, :reorder_value, :reverse_order_value
9
+ include ActiveModel::ForbiddenAttributesProtection
13
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.
14
144
  def includes(*args)
15
- args.reject! {|a| a.blank? }
145
+ check_if_method_has_arguments!(:includes, args)
146
+ spawn.includes!(*args)
147
+ end
16
148
 
17
- return self if args.empty?
149
+ def includes!(*args) # :nodoc:
150
+ args.reject!(&:blank?)
151
+ args.flatten!
18
152
 
19
- relation = clone
20
- relation.includes_values = (relation.includes_values + args).flatten.uniq
21
- relation
153
+ self.includes_values |= args
154
+ self
22
155
  end
23
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"
24
163
  def eager_load(*args)
25
- return self if args.blank?
164
+ check_if_method_has_arguments!(:eager_load, args)
165
+ spawn.eager_load!(*args)
166
+ end
26
167
 
27
- relation = clone
28
- relation.eager_load_values += args
29
- relation
168
+ def eager_load!(*args) # :nodoc:
169
+ self.eager_load_values += args
170
+ self
30
171
  end
31
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)
32
177
  def preload(*args)
33
- return self if args.blank?
178
+ check_if_method_has_arguments!(:preload, args)
179
+ spawn.preload!(*args)
180
+ end
181
+
182
+ def preload!(*args) # :nodoc:
183
+ self.preload_values += args
184
+ self
185
+ end
34
186
 
35
- relation = clone
36
- relation.preload_values += args
37
- relation
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
38
208
  end
39
209
 
40
210
  # Works in two unique ways.
41
- #
211
+ #
42
212
  # First: takes a block so it can be used just like Array#select.
43
213
  #
44
- # Model.scoped.select { |m| m.field == value }
214
+ # Model.all.select { |m| m.field == value }
45
215
  #
46
216
  # This will build an array of objects from the database for the scope,
47
217
  # converting them into an array and iterating through them using Array#select.
@@ -49,189 +219,711 @@ module ActiveRecord
49
219
  # Second: Modifies the SELECT statement for the query so that only certain
50
220
  # fields are retrieved:
51
221
  #
52
- # >> Model.select(:field)
53
- # => [#<Model field:value>]
222
+ # Model.select(:field)
223
+ # # => [#<Model id: nil, field: "value">]
54
224
  #
55
225
  # Although in the above example it looks as though this method returns an
56
226
  # array, it actually returns a relation object and can have other query
57
227
  # methods appended to it, such as the other methods in ActiveRecord::QueryMethods.
58
228
  #
59
- # This method will also take multiple parameters:
229
+ # The argument to the method can also be an array of fields.
60
230
  #
61
- # >> Model.select(:field, :other_field, :and_one_more)
62
- # => [#<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">]
63
233
  #
64
- # Any attributes that do not have fields retrieved by a select
65
- # will return `nil` when the getter method for that attribute is used:
234
+ # You can also use one or more strings, which will be used unchanged as SELECT fields.
66
235
  #
67
- # >> Model.select(:field).first.other_field
68
- # => nil
69
- def select(value = Proc.new)
236
+ # Model.select('field AS field_one', 'other_field AS field_two')
237
+ # # => [#<Model id: nil, field: "value", other_field: "value">]
238
+ #
239
+ # If an alias was specified, it will be accessible from the resulting objects:
240
+ #
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)
70
250
  if block_given?
71
- to_a.select {|*block_args| value.call(*block_args) }
251
+ to_a.select { |*block_args| yield(*block_args) }
72
252
  else
73
- relation = clone
74
- relation.select_values += Array.wrap(value)
75
- relation
253
+ raise ArgumentError, 'Call this with at least one field' if fields.empty?
254
+ spawn._select!(*fields)
76
255
  end
77
256
  end
78
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
262
+ end
263
+ self.select_values += fields
264
+ self
265
+ end
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">]
79
286
  def group(*args)
80
- return self if args.blank?
287
+ check_if_method_has_arguments!(:group, args)
288
+ spawn.group!(*args)
289
+ end
81
290
 
82
- relation = clone
83
- relation.group_values += args.flatten
84
- relation
291
+ def group!(*args) # :nodoc:
292
+ args.flatten!
293
+
294
+ self.group_values += args
295
+ self
85
296
  end
86
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
87
317
  def order(*args)
88
- return self if args.blank?
318
+ check_if_method_has_arguments!(:order, args)
319
+ spawn.order!(*args)
320
+ end
89
321
 
90
- relation = clone
91
- relation.order_values += args.flatten
92
- relation
322
+ def order!(*args) # :nodoc:
323
+ preprocess_order_args(args)
324
+
325
+ self.order_values += args
326
+ self
93
327
  end
94
328
 
329
+ # Replaces any existing order defined on the relation with the specified order.
330
+ #
331
+ # User.order('email DESC').reorder('id ASC') # generated SQL has 'ORDER BY id ASC'
332
+ #
333
+ # Subsequent calls to order on the same relation will be appended. For example:
334
+ #
335
+ # User.order('email DESC').reorder('id ASC').order('name ASC')
336
+ #
337
+ # generates a query with 'ORDER BY id ASC, name ASC'.
95
338
  def reorder(*args)
96
- return self if args.blank?
339
+ check_if_method_has_arguments!(:reorder, args)
340
+ spawn.reorder!(*args)
341
+ end
342
+
343
+ def reorder!(*args) # :nodoc:
344
+ preprocess_order_args(args)
97
345
 
98
- relation = clone
99
- relation.reorder_value = args.flatten
100
- relation
346
+ self.reordering_value = true
347
+ self.order_values = args
348
+ self
101
349
  end
102
350
 
103
- def joins(*args)
104
- 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])
105
354
 
106
- 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
107
392
 
393
+ def unscope!(*args) # :nodoc:
108
394
  args.flatten!
109
- 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
418
+
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)
431
+ end
110
432
 
111
- 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
112
447
  end
113
448
 
114
- def bind(value)
115
- relation = clone
116
- relation.bind_values += [value]
117
- relation
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
118
576
  end
119
577
 
120
- def where(opts, *rest)
121
- 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
122
583
 
123
- relation = clone
124
- relation.where_values += build_where(opts, rest)
125
- relation
584
+ self.where_values += build_where(opts, rest)
585
+ self
586
+ end
587
+
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)
126
598
  end
127
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')
128
604
  def having(opts, *rest)
129
- return self if opts.blank?
605
+ opts.blank? ? self : spawn.having!(opts, *rest)
606
+ end
130
607
 
131
- relation = clone
132
- relation.having_values += build_where(opts, rest)
133
- relation
608
+ def having!(opts, *rest) # :nodoc:
609
+ references!(PredicateBuilder.references(opts)) if Hash === opts
610
+
611
+ self.having_values += build_where(opts, rest)
612
+ self
134
613
  end
135
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'
136
620
  def limit(value)
137
- relation = clone
138
- relation.limit_value = value
139
- relation
621
+ spawn.limit!(value)
622
+ end
623
+
624
+ def limit!(value) # :nodoc:
625
+ self.limit_value = value
626
+ self
140
627
  end
141
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")
142
636
  def offset(value)
143
- relation = clone
144
- relation.offset_value = value
145
- relation
637
+ spawn.offset!(value)
638
+ end
639
+
640
+ def offset!(value) # :nodoc:
641
+ self.offset_value = value
642
+ self
146
643
  end
147
644
 
645
+ # Specifies locking settings (default to +true+). For more information
646
+ # on locking, please see +ActiveRecord::Locking+.
148
647
  def lock(locks = true)
149
- relation = clone
648
+ spawn.lock!(locks)
649
+ end
150
650
 
651
+ def lock!(locks = true) # :nodoc:
151
652
  case locks
152
653
  when String, TrueClass, NilClass
153
- relation.lock_value = locks || true
654
+ self.lock_value = locks || true
154
655
  else
155
- relation.lock_value = false
656
+ self.lock_value = false
156
657
  end
157
658
 
158
- relation
659
+ self
159
660
  end
160
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)
692
+ end
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
161
704
  def readonly(value = true)
162
- relation = clone
163
- relation.readonly_value = value
164
- relation
705
+ spawn.readonly!(value)
706
+ end
707
+
708
+ def readonly!(value = true) # :nodoc:
709
+ self.readonly_value = value
710
+ self
165
711
  end
166
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'
167
726
  def create_with(value)
168
- relation = clone
169
- relation.create_with_value = value ? create_with_value.merge(value) : {}
170
- relation
727
+ spawn.create_with!(value)
171
728
  end
172
729
 
173
- def from(value)
174
- relation = clone
175
- relation.from_value = value
176
- relation
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)
756
+ end
757
+
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
764
+ end
765
+
766
+ # Specifies whether the records should be unique or not. For example:
767
+ #
768
+ # User.select(:name)
769
+ # # => Might return two records with the same name
770
+ #
771
+ # User.select(:name).distinct
772
+ # # => Returns 1 record per distinct name
773
+ #
774
+ # User.select(:name).distinct.distinct(false)
775
+ # # => You can also remove the uniqueness
776
+ def distinct(value = true)
777
+ spawn.distinct!(value)
177
778
  end
779
+ alias uniq distinct
178
780
 
179
- def extending(*modules)
180
- modules << Module.new(&Proc.new) if block_given?
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!
787
+
788
+ # Used to extend a scope with additional methods, either through
789
+ # a module or through a block provided.
790
+ #
791
+ # The object returned is a relation, which can be further extended.
792
+ #
793
+ # === Using a module
794
+ #
795
+ # module Pagination
796
+ # def page(number)
797
+ # # pagination code goes here
798
+ # end
799
+ # end
800
+ #
801
+ # scope = Model.all.extending(Pagination)
802
+ # scope.page(params[:page])
803
+ #
804
+ # You can also pass a list of modules:
805
+ #
806
+ # scope = Model.all.extending(Pagination, SomethingElse)
807
+ #
808
+ # === Using a block
809
+ #
810
+ # scope = Model.all.extending do
811
+ # def page(number)
812
+ # # pagination code goes here
813
+ # end
814
+ # end
815
+ # scope.page(params[:page])
816
+ #
817
+ # You can also use a block and a module list:
818
+ #
819
+ # scope = Model.all.extending(Pagination) do
820
+ # def per_page(number)
821
+ # # pagination code goes here
822
+ # end
823
+ # end
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!
181
835
 
182
- return self if modules.empty?
836
+ self.extending_values += modules
837
+ extend(*extending_values) if extending_values.any?
183
838
 
184
- relation = clone
185
- relation.send(:apply_modules, modules.flatten)
186
- relation
839
+ self
187
840
  end
188
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'
189
845
  def reverse_order
190
- relation = clone
191
- relation.reverse_order_value = !relation.reverse_order_value
192
- relation
846
+ spawn.reverse_order!
193
847
  end
194
848
 
195
- def arel
196
- @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
197
854
  end
198
855
 
199
- def build_arel
200
- arel = table.from table
856
+ # Returns the Arel object associated with the relation.
857
+ def arel # :nodoc:
858
+ @arel ||= build_arel
859
+ end
201
860
 
202
- build_joins(arel, @joins_values) unless @joins_values.empty?
861
+ private
203
862
 
204
- collapse_wheres(arel, (@where_values - ['']).uniq)
863
+ def build_arel
864
+ arel = Arel::SelectManager.new(table.engine, table)
205
865
 
206
- arel.having(*@having_values.uniq.reject{|h| h.blank?}) unless @having_values.empty?
866
+ build_joins(arel, joins_values.flatten) unless joins_values.empty?
207
867
 
208
- arel.take(connection.sanitize_limit(@limit_value)) if @limit_value
209
- arel.skip(@offset_value.to_i) if @offset_value
868
+ collapse_wheres(arel, (where_values - [''])) #TODO: Add uniq with real value comparison / ignore uniqs that have binds
210
869
 
211
- arel.group(*@group_values.uniq.reject{|g| g.blank?}) unless @group_values.empty?
870
+ arel.having(*having_values.uniq.reject(&:blank?)) unless having_values.empty?
212
871
 
213
- order = @reorder_value ? @reorder_value : @order_values
214
- order = reverse_sql_order(order) if @reverse_order_value
215
- arel.order(*order.uniq.reject{|o| o.blank?}) unless order.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?
216
875
 
217
- build_select(arel, @select_values.uniq)
876
+ build_order(arel)
218
877
 
219
- arel.from(@from_value) if @from_value
220
- arel.lock(@lock_value) if @lock_value
878
+ build_select(arel)
879
+
880
+ arel.distinct(distinct_value)
881
+ arel.from(build_from) if from_value
882
+ arel.lock(lock_value) if lock_value
221
883
 
222
884
  arel
223
885
  end
224
886
 
225
- 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
226
920
 
227
921
  def custom_join_ast(table, joins)
228
- joins = joins.reject { |join| join.blank? }
922
+ joins = joins.reject(&:blank?)
229
923
 
230
924
  return [] if joins.empty?
231
925
 
232
- @implicit_readonly = true
233
-
234
- joins.map do |join|
926
+ joins.map! do |join|
235
927
  case join
236
928
  when Array
237
929
  join = Arel.sql(join.join(' ')) if array_of_strings?(join)
@@ -243,14 +935,13 @@ module ActiveRecord
243
935
  end
244
936
 
245
937
  def collapse_wheres(arel, wheres)
246
- equalities = wheres.grep(Arel::Nodes::Equality)
247
-
248
- arel.where(Arel::Nodes::And.new(equalities)) unless equalities.empty?
249
-
250
- (wheres - equalities).each do |where|
938
+ predicates = wheres.map do |where|
939
+ next where if ::Arel::Nodes::Equality === where
251
940
  where = Arel.sql(where) if String === where
252
- arel.where(Arel::Nodes::Grouping.new(where))
941
+ Arel::Nodes::Grouping.new(where)
253
942
  end
943
+
944
+ arel.where(Arel::Nodes::And.new(predicates)) if predicates.present?
254
945
  end
255
946
 
256
947
  def build_where(opts, other = [])
@@ -258,35 +949,90 @@ module ActiveRecord
258
949
  when String, Array
259
950
  [@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))]
260
951
  when Hash
261
- attributes = @klass.send(:expand_hash_conditions_for_aggregates, opts)
262
- 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)
263
961
  else
264
962
  [opts]
265
963
  end
266
964
  end
267
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
+
268
1016
  def build_joins(manager, joins)
269
1017
  buckets = joins.group_by do |join|
270
1018
  case join
271
1019
  when String
272
- 'string_join'
1020
+ :string_join
273
1021
  when Hash, Symbol, Array
274
- 'association_join'
275
- when ActiveRecord::Associations::JoinDependency::JoinAssociation
276
- 'stashed_join'
1022
+ :association_join
1023
+ when ActiveRecord::Associations::JoinDependency
1024
+ :stashed_join
277
1025
  when Arel::Nodes::Join
278
- 'join_node'
1026
+ :join_node
279
1027
  else
280
1028
  raise 'unknown class: %s' % join.class.name
281
1029
  end
282
1030
  end
283
1031
 
284
- association_joins = buckets['association_join'] || []
285
- stashed_association_joins = buckets['stashed_join'] || []
286
- join_nodes = (buckets['join_node'] || []).uniq
287
- string_joins = (buckets['string_join'] || []).map { |x|
288
- x.strip
289
- }.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
290
1036
 
291
1037
  join_list = join_nodes + custom_join_ast(manager, string_joins)
292
1038
 
@@ -296,56 +1042,137 @@ module ActiveRecord
296
1042
  join_list
297
1043
  )
298
1044
 
299
- join_dependency.graft(*stashed_association_joins)
300
-
301
- @implicit_readonly = true unless association_joins.empty? && stashed_association_joins.empty?
1045
+ join_infos = join_dependency.join_constraints stashed_association_joins
302
1046
 
303
- # FIXME: refactor this to build an AST
304
- join_dependency.join_associations.each do |association|
305
- 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
306
1050
  end
307
1051
 
308
- manager.join_sources.concat join_list
1052
+ manager.join_sources.concat(join_list)
309
1053
 
310
1054
  manager
311
1055
  end
312
1056
 
313
- def build_select(arel, selects)
314
- unless selects.empty?
315
- @implicit_readonly = false
316
- arel.project(*selects)
1057
+ def build_select(arel)
1058
+ if select_values.any?
1059
+ arel.project(*arel_columns(select_values.uniq))
317
1060
  else
318
1061
  arel.project(@klass.arel_table[Arel.star])
319
1062
  end
320
1063
  end
321
1064
 
322
- def apply_modules(modules)
323
- unless modules.empty?
324
- @extensions += modules
325
- 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
326
1074
  end
327
1075
  end
328
1076
 
329
1077
  def reverse_sql_order(order_query)
330
1078
  order_query = ["#{quoted_table_name}.#{quoted_primary_key} ASC"] if order_query.empty?
331
1079
 
332
- order_query.map do |o|
1080
+ order_query.flat_map do |o|
333
1081
  case o
334
- when Arel::Nodes::Ascending, Arel::Nodes::Descending
1082
+ when Arel::Nodes::Ordering
335
1083
  o.reverse
336
- when String, Symbol
337
- o.to_s.split(',').collect do |s|
1084
+ when String
1085
+ o.to_s.split(',').map! do |s|
1086
+ s.strip!
338
1087
  s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC')
339
1088
  end
340
1089
  else
341
1090
  o
342
1091
  end
343
- end.flatten
1092
+ end
344
1093
  end
345
1094
 
346
1095
  def array_of_strings?(o)
347
- 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
1117
+ end
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
348
1164
  end
349
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
350
1177
  end
351
1178
  end