activerecord 4.2.0 → 5.2.8.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


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

Files changed (274) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +640 -928
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +10 -11
  5. data/examples/performance.rb +32 -31
  6. data/examples/simple.rb +5 -4
  7. data/lib/active_record/aggregations.rb +264 -247
  8. data/lib/active_record/association_relation.rb +24 -6
  9. data/lib/active_record/associations/alias_tracker.rb +29 -35
  10. data/lib/active_record/associations/association.rb +87 -41
  11. data/lib/active_record/associations/association_scope.rb +106 -132
  12. data/lib/active_record/associations/belongs_to_association.rb +55 -36
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +8 -8
  14. data/lib/active_record/associations/builder/association.rb +29 -38
  15. data/lib/active_record/associations/builder/belongs_to.rb +77 -30
  16. data/lib/active_record/associations/builder/collection_association.rb +14 -23
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +50 -39
  18. data/lib/active_record/associations/builder/has_many.rb +6 -4
  19. data/lib/active_record/associations/builder/has_one.rb +13 -6
  20. data/lib/active_record/associations/builder/singular_association.rb +15 -11
  21. data/lib/active_record/associations/collection_association.rb +145 -266
  22. data/lib/active_record/associations/collection_proxy.rb +242 -138
  23. data/lib/active_record/associations/foreign_association.rb +13 -0
  24. data/lib/active_record/associations/has_many_association.rb +35 -75
  25. data/lib/active_record/associations/has_many_through_association.rb +51 -69
  26. data/lib/active_record/associations/has_one_association.rb +39 -24
  27. data/lib/active_record/associations/has_one_through_association.rb +18 -9
  28. data/lib/active_record/associations/join_dependency/join_association.rb +40 -81
  29. data/lib/active_record/associations/join_dependency/join_base.rb +10 -9
  30. data/lib/active_record/associations/join_dependency/join_part.rb +12 -12
  31. data/lib/active_record/associations/join_dependency.rb +134 -154
  32. data/lib/active_record/associations/preloader/association.rb +85 -116
  33. data/lib/active_record/associations/preloader/through_association.rb +85 -74
  34. data/lib/active_record/associations/preloader.rb +83 -93
  35. data/lib/active_record/associations/singular_association.rb +27 -40
  36. data/lib/active_record/associations/through_association.rb +48 -23
  37. data/lib/active_record/associations.rb +1732 -1596
  38. data/lib/active_record/attribute_assignment.rb +58 -182
  39. data/lib/active_record/attribute_decorators.rb +39 -15
  40. data/lib/active_record/attribute_methods/before_type_cast.rb +12 -5
  41. data/lib/active_record/attribute_methods/dirty.rb +94 -125
  42. data/lib/active_record/attribute_methods/primary_key.rb +86 -71
  43. data/lib/active_record/attribute_methods/query.rb +4 -2
  44. data/lib/active_record/attribute_methods/read.rb +45 -63
  45. data/lib/active_record/attribute_methods/serialization.rb +40 -20
  46. data/lib/active_record/attribute_methods/time_zone_conversion.rb +62 -36
  47. data/lib/active_record/attribute_methods/write.rb +31 -46
  48. data/lib/active_record/attribute_methods.rb +170 -117
  49. data/lib/active_record/attributes.rb +201 -74
  50. data/lib/active_record/autosave_association.rb +118 -45
  51. data/lib/active_record/base.rb +60 -48
  52. data/lib/active_record/callbacks.rb +97 -57
  53. data/lib/active_record/coders/json.rb +3 -1
  54. data/lib/active_record/coders/yaml_column.rb +37 -13
  55. data/lib/active_record/collection_cache_key.rb +53 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +712 -284
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +10 -5
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +254 -87
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +72 -22
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +119 -52
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +6 -4
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +67 -46
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +328 -217
  64. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +81 -36
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +617 -212
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +139 -75
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +332 -191
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +567 -563
  69. data/lib/active_record/connection_adapters/column.rb +50 -41
  70. data/lib/active_record/connection_adapters/connection_specification.rb +147 -135
  71. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +33 -0
  72. data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
  73. data/lib/active_record/connection_adapters/mysql/database_statements.rb +140 -0
  74. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
  75. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -0
  76. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +73 -0
  77. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +87 -0
  78. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +80 -0
  79. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +148 -0
  80. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +35 -0
  81. data/lib/active_record/connection_adapters/mysql2_adapter.rb +42 -195
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +35 -11
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +46 -115
  84. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +50 -57
  86. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +10 -6
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +2 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +5 -2
  89. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +5 -1
  90. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +13 -1
  91. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +9 -13
  92. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +7 -3
  94. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +31 -19
  95. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +2 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +3 -11
  97. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +7 -9
  99. data/lib/active_record/connection_adapters/postgresql/oid/{integer.rb → oid.rb} +6 -2
  100. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +33 -11
  101. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +52 -34
  102. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +4 -1
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +65 -51
  104. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +5 -3
  105. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +3 -1
  106. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +3 -1
  107. data/lib/active_record/connection_adapters/postgresql/oid.rb +23 -25
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +107 -47
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +27 -14
  110. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +65 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +144 -90
  112. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +466 -280
  114. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +39 -0
  115. data/lib/active_record/connection_adapters/postgresql/utils.rb +12 -8
  116. data/lib/active_record/connection_adapters/postgresql_adapter.rb +439 -330
  117. data/lib/active_record/connection_adapters/schema_cache.rb +48 -24
  118. data/lib/active_record/connection_adapters/sql_type_metadata.rb +34 -0
  119. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
  120. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +67 -0
  121. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
  122. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  123. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  124. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +106 -0
  125. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +269 -324
  126. data/lib/active_record/connection_adapters/statement_pool.rb +34 -13
  127. data/lib/active_record/connection_handling.rb +40 -27
  128. data/lib/active_record/core.rb +205 -202
  129. data/lib/active_record/counter_cache.rb +80 -37
  130. data/lib/active_record/define_callbacks.rb +22 -0
  131. data/lib/active_record/dynamic_matchers.rb +87 -105
  132. data/lib/active_record/enum.rb +136 -90
  133. data/lib/active_record/errors.rb +180 -52
  134. data/lib/active_record/explain.rb +23 -11
  135. data/lib/active_record/explain_registry.rb +4 -2
  136. data/lib/active_record/explain_subscriber.rb +11 -6
  137. data/lib/active_record/fixture_set/file.rb +35 -9
  138. data/lib/active_record/fixtures.rb +193 -135
  139. data/lib/active_record/gem_version.rb +5 -3
  140. data/lib/active_record/inheritance.rb +148 -112
  141. data/lib/active_record/integration.rb +70 -28
  142. data/lib/active_record/internal_metadata.rb +45 -0
  143. data/lib/active_record/legacy_yaml_adapter.rb +48 -0
  144. data/lib/active_record/locale/en.yml +3 -2
  145. data/lib/active_record/locking/optimistic.rb +92 -98
  146. data/lib/active_record/locking/pessimistic.rb +15 -3
  147. data/lib/active_record/log_subscriber.rb +95 -33
  148. data/lib/active_record/migration/command_recorder.rb +133 -90
  149. data/lib/active_record/migration/compatibility.rb +217 -0
  150. data/lib/active_record/migration/join_table.rb +8 -6
  151. data/lib/active_record/migration.rb +594 -267
  152. data/lib/active_record/model_schema.rb +292 -111
  153. data/lib/active_record/nested_attributes.rb +266 -214
  154. data/lib/active_record/no_touching.rb +8 -2
  155. data/lib/active_record/null_relation.rb +24 -37
  156. data/lib/active_record/persistence.rb +350 -119
  157. data/lib/active_record/query_cache.rb +13 -24
  158. data/lib/active_record/querying.rb +19 -17
  159. data/lib/active_record/railtie.rb +117 -35
  160. data/lib/active_record/railties/console_sandbox.rb +2 -0
  161. data/lib/active_record/railties/controller_runtime.rb +9 -3
  162. data/lib/active_record/railties/databases.rake +160 -174
  163. data/lib/active_record/readonly_attributes.rb +5 -4
  164. data/lib/active_record/reflection.rb +447 -288
  165. data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
  166. data/lib/active_record/relation/batches.rb +204 -55
  167. data/lib/active_record/relation/calculations.rb +259 -244
  168. data/lib/active_record/relation/delegation.rb +67 -60
  169. data/lib/active_record/relation/finder_methods.rb +290 -253
  170. data/lib/active_record/relation/from_clause.rb +26 -0
  171. data/lib/active_record/relation/merger.rb +91 -68
  172. data/lib/active_record/relation/predicate_builder/array_handler.rb +24 -23
  173. data/lib/active_record/relation/predicate_builder/association_query_value.rb +46 -0
  174. data/lib/active_record/relation/predicate_builder/base_handler.rb +19 -0
  175. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +20 -0
  176. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +56 -0
  177. data/lib/active_record/relation/predicate_builder/range_handler.rb +42 -0
  178. data/lib/active_record/relation/predicate_builder/relation_handler.rb +7 -1
  179. data/lib/active_record/relation/predicate_builder.rb +118 -92
  180. data/lib/active_record/relation/query_attribute.rb +45 -0
  181. data/lib/active_record/relation/query_methods.rb +446 -389
  182. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  183. data/lib/active_record/relation/spawn_methods.rb +18 -16
  184. data/lib/active_record/relation/where_clause.rb +186 -0
  185. data/lib/active_record/relation/where_clause_factory.rb +34 -0
  186. data/lib/active_record/relation.rb +287 -339
  187. data/lib/active_record/result.rb +54 -36
  188. data/lib/active_record/runtime_registry.rb +6 -4
  189. data/lib/active_record/sanitization.rb +155 -124
  190. data/lib/active_record/schema.rb +30 -24
  191. data/lib/active_record/schema_dumper.rb +91 -87
  192. data/lib/active_record/schema_migration.rb +19 -19
  193. data/lib/active_record/scoping/default.rb +102 -84
  194. data/lib/active_record/scoping/named.rb +81 -32
  195. data/lib/active_record/scoping.rb +45 -26
  196. data/lib/active_record/secure_token.rb +40 -0
  197. data/lib/active_record/serialization.rb +5 -5
  198. data/lib/active_record/statement_cache.rb +45 -35
  199. data/lib/active_record/store.rb +42 -36
  200. data/lib/active_record/suppressor.rb +61 -0
  201. data/lib/active_record/table_metadata.rb +82 -0
  202. data/lib/active_record/tasks/database_tasks.rb +136 -95
  203. data/lib/active_record/tasks/mysql_database_tasks.rb +59 -89
  204. data/lib/active_record/tasks/postgresql_database_tasks.rb +84 -31
  205. data/lib/active_record/tasks/sqlite_database_tasks.rb +44 -16
  206. data/lib/active_record/timestamp.rb +70 -38
  207. data/lib/active_record/touch_later.rb +64 -0
  208. data/lib/active_record/transactions.rb +208 -123
  209. data/lib/active_record/translation.rb +2 -0
  210. data/lib/active_record/type/adapter_specific_registry.rb +136 -0
  211. data/lib/active_record/type/date.rb +4 -41
  212. data/lib/active_record/type/date_time.rb +4 -38
  213. data/lib/active_record/type/decimal_without_scale.rb +6 -2
  214. data/lib/active_record/type/hash_lookup_type_map.rb +13 -5
  215. data/lib/active_record/type/internal/timezone.rb +17 -0
  216. data/lib/active_record/type/json.rb +30 -0
  217. data/lib/active_record/type/serialized.rb +30 -15
  218. data/lib/active_record/type/text.rb +2 -2
  219. data/lib/active_record/type/time.rb +11 -16
  220. data/lib/active_record/type/type_map.rb +15 -17
  221. data/lib/active_record/type/unsigned_integer.rb +9 -7
  222. data/lib/active_record/type.rb +79 -23
  223. data/lib/active_record/type_caster/connection.rb +33 -0
  224. data/lib/active_record/type_caster/map.rb +23 -0
  225. data/lib/active_record/type_caster.rb +9 -0
  226. data/lib/active_record/validations/absence.rb +25 -0
  227. data/lib/active_record/validations/associated.rb +13 -4
  228. data/lib/active_record/validations/length.rb +26 -0
  229. data/lib/active_record/validations/presence.rb +14 -13
  230. data/lib/active_record/validations/uniqueness.rb +41 -32
  231. data/lib/active_record/validations.rb +38 -35
  232. data/lib/active_record/version.rb +3 -1
  233. data/lib/active_record.rb +36 -21
  234. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
  235. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  236. data/lib/rails/generators/active_record/migration/migration_generator.rb +43 -35
  237. data/lib/rails/generators/active_record/migration/templates/{create_table_migration.rb → create_table_migration.rb.tt} +8 -6
  238. data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +8 -7
  239. data/lib/rails/generators/active_record/migration.rb +18 -1
  240. data/lib/rails/generators/active_record/model/model_generator.rb +18 -22
  241. data/lib/rails/generators/active_record/model/templates/{model.rb → model.rb.tt} +3 -0
  242. data/lib/rails/generators/active_record.rb +7 -5
  243. metadata +77 -53
  244. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  245. data/lib/active_record/associations/preloader/collection_association.rb +0 -24
  246. data/lib/active_record/associations/preloader/has_many.rb +0 -17
  247. data/lib/active_record/associations/preloader/has_many_through.rb +0 -19
  248. data/lib/active_record/associations/preloader/has_one.rb +0 -23
  249. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  250. data/lib/active_record/associations/preloader/singular_association.rb +0 -21
  251. data/lib/active_record/attribute.rb +0 -149
  252. data/lib/active_record/attribute_set/builder.rb +0 -86
  253. data/lib/active_record/attribute_set.rb +0 -77
  254. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -491
  255. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  256. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  257. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
  258. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +0 -35
  259. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  260. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  261. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  262. data/lib/active_record/type/big_integer.rb +0 -13
  263. data/lib/active_record/type/binary.rb +0 -50
  264. data/lib/active_record/type/boolean.rb +0 -30
  265. data/lib/active_record/type/decimal.rb +0 -40
  266. data/lib/active_record/type/decorator.rb +0 -14
  267. data/lib/active_record/type/float.rb +0 -19
  268. data/lib/active_record/type/integer.rb +0 -55
  269. data/lib/active_record/type/mutable.rb +0 -16
  270. data/lib/active_record/type/numeric.rb +0 -36
  271. data/lib/active_record/type/string.rb +0 -36
  272. data/lib/active_record/type/time_value.rb +0 -38
  273. data/lib/active_record/type/value.rb +0 -101
  274. /data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
@@ -1,6 +1,10 @@
1
- require 'active_support/core_ext/array/wrap'
2
- require 'active_support/core_ext/string/filters'
3
- require 'active_model/forbidden_attributes_protection'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record/relation/from_clause"
4
+ require "active_record/relation/query_attribute"
5
+ require "active_record/relation/where_clause"
6
+ require "active_record/relation/where_clause_factory"
7
+ require "active_model/forbidden_attributes_protection"
4
8
 
5
9
  module ActiveRecord
6
10
  module QueryMethods
@@ -11,6 +15,8 @@ module ActiveRecord
11
15
  # WhereChain objects act as placeholder for queries in which #where does not have any parameter.
12
16
  # In this case, #where must be chained with #not to return a new relation.
13
17
  class WhereChain
18
+ include ActiveModel::ForbiddenAttributesProtection
19
+
14
20
  def initialize(scope)
15
21
  @scope = scope
16
22
  end
@@ -18,7 +24,7 @@ module ActiveRecord
18
24
  # Returns a new relation expressing WHERE + NOT condition according to
19
25
  # the conditions in the arguments.
20
26
  #
21
- # +not+ accepts conditions as a string, array, or hash. See #where for
27
+ # #not accepts conditions as a string, array, or hash. See QueryMethods#where for
22
28
  # more details on each format.
23
29
  #
24
30
  # User.where.not("name = 'Jon'")
@@ -39,73 +45,37 @@ module ActiveRecord
39
45
  # User.where.not(name: "Jon", role: "admin")
40
46
  # # SELECT * FROM users WHERE name != 'Jon' AND role != 'admin'
41
47
  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
48
+ opts = sanitize_forbidden_attributes(opts)
49
+
50
+ where_clause = @scope.send(:where_clause_factory).build(opts, rest)
56
51
 
57
52
  @scope.references!(PredicateBuilder.references(opts)) if Hash === opts
58
- @scope.where_values += where_value
53
+ @scope.where_clause += where_clause.invert
59
54
  @scope
60
55
  end
61
56
  end
62
57
 
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
58
+ FROZEN_EMPTY_ARRAY = [].freeze
59
+ FROZEN_EMPTY_HASH = {}.freeze
76
60
 
77
- (Relation::SINGLE_VALUE_METHODS - [:create_with]).each do |name|
61
+ Relation::VALUE_METHODS.each do |name|
62
+ method_name = \
63
+ case name
64
+ when *Relation::MULTI_VALUE_METHODS then "#{name}_values"
65
+ when *Relation::SINGLE_VALUE_METHODS then "#{name}_value"
66
+ when *Relation::CLAUSE_METHODS then "#{name}_clause"
67
+ end
78
68
  class_eval <<-CODE, __FILE__, __LINE__ + 1
79
- def #{name}_value # def readonly_value
80
- @values[:#{name}] # @values[:readonly]
69
+ def #{method_name} # def includes_values
70
+ get_value(#{name.inspect}) # get_value(:includes)
81
71
  end # end
82
- CODE
83
- end
84
72
 
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
73
+ def #{method_name}=(value) # def includes_values=(value)
74
+ set_value(#{name.inspect}, value) # set_value(:includes, value)
91
75
  end # end
92
76
  CODE
93
77
  end
94
78
 
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
79
  alias extensions extending_values
110
80
 
111
81
  # Specify relationships to be included in the result set. For
@@ -118,7 +88,7 @@ module ActiveRecord
118
88
  #
119
89
  # allows you to access the +address+ attribute of the +User+ model without
120
90
  # firing an additional query. This will often result in a
121
- # performance improvement over a simple +join+.
91
+ # performance improvement over a simple join.
122
92
  #
123
93
  # You can also specify multiple relationships, like this:
124
94
  #
@@ -139,7 +109,7 @@ module ActiveRecord
139
109
  #
140
110
  # User.includes(:posts).where('posts.name = ?', 'example').references(:posts)
141
111
  #
142
- # Note that +includes+ works with association names while +references+ needs
112
+ # Note that #includes works with association names while #references needs
143
113
  # the actual table name.
144
114
  def includes(*args)
145
115
  check_if_method_has_arguments!(:includes, args)
@@ -157,9 +127,9 @@ module ActiveRecord
157
127
  # Forces eager loading by performing a LEFT OUTER JOIN on +args+:
158
128
  #
159
129
  # 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"
130
+ # # SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ...
131
+ # # FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" =
132
+ # # "users"."id"
163
133
  def eager_load(*args)
164
134
  check_if_method_has_arguments!(:eager_load, args)
165
135
  spawn.eager_load!(*args)
@@ -170,10 +140,10 @@ module ActiveRecord
170
140
  self
171
141
  end
172
142
 
173
- # Allows preloading of +args+, in the same way that +includes+ does:
143
+ # Allows preloading of +args+, in the same way that #includes does:
174
144
  #
175
145
  # User.preload(:posts)
176
- # => SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (1, 2, 3)
146
+ # # SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (1, 2, 3)
177
147
  def preload(*args)
178
148
  check_if_method_has_arguments!(:preload, args)
179
149
  spawn.preload!(*args)
@@ -186,14 +156,14 @@ module ActiveRecord
186
156
 
187
157
  # Use to indicate that the given +table_names+ are referenced by an SQL string,
188
158
  # and should therefore be JOINed in any query rather than loaded separately.
189
- # This method only works in conjunction with +includes+.
159
+ # This method only works in conjunction with #includes.
190
160
  # See #includes for more details.
191
161
  #
192
162
  # User.includes(:posts).where("posts.name = 'foo'")
193
- # # => Doesn't JOIN the posts table, resulting in an error.
163
+ # # Doesn't JOIN the posts table, resulting in an error.
194
164
  #
195
165
  # User.includes(:posts).where("posts.name = 'foo'").references(:posts)
196
- # # => Query now knows the string references posts, so adds a JOIN
166
+ # # Query now knows the string references posts, so adds a JOIN
197
167
  def references(*table_names)
198
168
  check_if_method_has_arguments!(:references, table_names)
199
169
  spawn.references!(*table_names)
@@ -209,12 +179,13 @@ module ActiveRecord
209
179
 
210
180
  # Works in two unique ways.
211
181
  #
212
- # First: takes a block so it can be used just like Array#select.
182
+ # First: takes a block so it can be used just like <tt>Array#select</tt>.
213
183
  #
214
184
  # Model.all.select { |m| m.field == value }
215
185
  #
216
186
  # This will build an array of objects from the database for the scope,
217
- # converting them into an array and iterating through them using Array#select.
187
+ # converting them into an array and iterating through them using
188
+ # <tt>Array#select</tt>.
218
189
  #
219
190
  # Second: Modifies the SELECT statement for the query so that only certain
220
191
  # fields are retrieved:
@@ -242,24 +213,25 @@ module ActiveRecord
242
213
  # # => "value"
243
214
  #
244
215
  # Accessing attributes of an object that do not have fields retrieved by a select
245
- # except +id+ will throw <tt>ActiveModel::MissingAttributeError</tt>:
216
+ # except +id+ will throw ActiveModel::MissingAttributeError:
246
217
  #
247
218
  # Model.select(:field).first.other_field
248
219
  # # => ActiveModel::MissingAttributeError: missing attribute: other_field
249
220
  def select(*fields)
250
221
  if block_given?
251
- to_a.select { |*block_args| yield(*block_args) }
252
- else
253
- raise ArgumentError, 'Call this with at least one field' if fields.empty?
254
- spawn._select!(*fields)
222
+ if fields.any?
223
+ raise ArgumentError, "`select' with block doesn't take arguments."
224
+ end
225
+
226
+ return super()
255
227
  end
228
+
229
+ raise ArgumentError, "Call `select' with at least one field" if fields.empty?
230
+ spawn._select!(*fields)
256
231
  end
257
232
 
258
233
  def _select!(*fields) # :nodoc:
259
234
  fields.flatten!
260
- fields.map! do |field|
261
- klass.attribute_alias?(field) ? klass.attribute_alias(field) : field
262
- end
263
235
  self.select_values += fields
264
236
  self
265
237
  end
@@ -267,22 +239,23 @@ module ActiveRecord
267
239
  # Allows to specify a group attribute:
268
240
  #
269
241
  # User.group(:name)
270
- # => SELECT "users".* FROM "users" GROUP BY name
242
+ # # SELECT "users".* FROM "users" GROUP BY name
271
243
  #
272
244
  # Returns an array with distinct records based on the +group+ attribute:
273
245
  #
274
246
  # User.select([:id, :name])
275
- # => [#<User id: 1, name: "Oscar">, #<User id: 2, name: "Oscar">, #<User id: 3, name: "Foo">
247
+ # # => [#<User id: 1, name: "Oscar">, #<User id: 2, name: "Oscar">, #<User id: 3, name: "Foo">]
276
248
  #
277
249
  # User.group(:name)
278
- # => [#<User id: 3, name: "Foo", ...>, #<User id: 2, name: "Oscar", ...>]
250
+ # # => [#<User id: 3, name: "Foo", ...>, #<User id: 2, name: "Oscar", ...>]
279
251
  #
280
252
  # 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, ...>]
253
+ # # => [#<User id: 3, name: "Foo", age: 21, ...>, #<User id: 2, name: "Oscar", age: 21, ...>, #<User id: 5, name: "Foo", age: 23, ...>]
282
254
  #
283
255
  # Passing in an array of attributes to group by is also supported.
256
+ #
284
257
  # 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">]
258
+ # # => [#<User id: 1, first_name: "Bill">, #<User id: 2, first_name: "Earl">, #<User id: 3, first_name: "Beto">]
286
259
  def group(*args)
287
260
  check_if_method_has_arguments!(:group, args)
288
261
  spawn.group!(*args)
@@ -298,27 +271,28 @@ module ActiveRecord
298
271
  # Allows to specify an order attribute:
299
272
  #
300
273
  # User.order(:name)
301
- # => SELECT "users".* FROM "users" ORDER BY "users"."name" ASC
274
+ # # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC
302
275
  #
303
276
  # User.order(email: :desc)
304
- # => SELECT "users".* FROM "users" ORDER BY "users"."email" DESC
277
+ # # SELECT "users".* FROM "users" ORDER BY "users"."email" DESC
305
278
  #
306
279
  # User.order(:name, email: :desc)
307
- # => SELECT "users".* FROM "users" ORDER BY "users"."name" ASC, "users"."email" DESC
280
+ # # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC, "users"."email" DESC
308
281
  #
309
282
  # User.order('name')
310
- # => SELECT "users".* FROM "users" ORDER BY name
283
+ # # SELECT "users".* FROM "users" ORDER BY name
311
284
  #
312
285
  # User.order('name DESC')
313
- # => SELECT "users".* FROM "users" ORDER BY name DESC
286
+ # # SELECT "users".* FROM "users" ORDER BY name DESC
314
287
  #
315
288
  # User.order('name DESC, email')
316
- # => SELECT "users".* FROM "users" ORDER BY name DESC, email
289
+ # # SELECT "users".* FROM "users" ORDER BY name DESC, email
317
290
  def order(*args)
318
291
  check_if_method_has_arguments!(:order, args)
319
292
  spawn.order!(*args)
320
293
  end
321
294
 
295
+ # Same as #order but operates on relation in-place instead of copying.
322
296
  def order!(*args) # :nodoc:
323
297
  preprocess_order_args(args)
324
298
 
@@ -340,6 +314,7 @@ module ActiveRecord
340
314
  spawn.reorder!(*args)
341
315
  end
342
316
 
317
+ # Same as #reorder but operates on relation in-place instead of copying.
343
318
  def reorder!(*args) # :nodoc:
344
319
  preprocess_order_args(args)
345
320
 
@@ -349,8 +324,8 @@ module ActiveRecord
349
324
  end
350
325
 
351
326
  VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock,
352
- :limit, :offset, :joins, :includes, :from,
353
- :readonly, :having])
327
+ :limit, :offset, :joins, :left_outer_joins,
328
+ :includes, :from, :readonly, :having])
354
329
 
355
330
  # Removes an unwanted relation that is already defined on a chain of relations.
356
331
  # This is useful when passing around chains of relations and would like to
@@ -365,15 +340,15 @@ module ActiveRecord
365
340
  # User.order('email DESC').select('id').where(name: "John")
366
341
  # .unscope(:order, :select, :where) == User.all
367
342
  #
368
- # One can additionally pass a hash as an argument to unscope specific :where values.
343
+ # One can additionally pass a hash as an argument to unscope specific +:where+ values.
369
344
  # 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:
345
+ # +:where+ and the value should be the where value to unscope. For example:
371
346
  #
372
347
  # User.where(name: "John", active: true).unscope(where: :name)
373
348
  # == User.where(active: true)
374
349
  #
375
- # This method is similar to <tt>except</tt>, but unlike
376
- # <tt>except</tt>, it persists across merges:
350
+ # This method is similar to #except, but unlike
351
+ # #except, it persists across merges:
377
352
  #
378
353
  # User.order('email').merge(User.except(:order))
379
354
  # == User.order('email')
@@ -383,7 +358,7 @@ module ActiveRecord
383
358
  #
384
359
  # This means it can be used in association definitions:
385
360
  #
386
- # has_many :comments, -> { unscope where: :trashed }
361
+ # has_many :comments, -> { unscope(where: :trashed) }
387
362
  #
388
363
  def unscope(*args)
389
364
  check_if_method_has_arguments!(:unscope, args)
@@ -397,16 +372,19 @@ module ActiveRecord
397
372
  args.each do |scope|
398
373
  case scope
399
374
  when Symbol
400
- symbol_unscoping(scope)
375
+ scope = :left_outer_joins if scope == :left_joins
376
+ if !VALID_UNSCOPING_VALUES.include?(scope)
377
+ raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
378
+ end
379
+ set_value(scope, DEFAULT_VALUES[scope])
401
380
  when Hash
402
381
  scope.each do |key, target_value|
403
382
  if key != :where
404
383
  raise ArgumentError, "Hash arguments in .unscope(*args) must have :where as the key."
405
384
  end
406
385
 
407
- Array(target_value).each do |val|
408
- where_unscoping(val)
409
- end
386
+ target_values = Array(target_value).map(&:to_s)
387
+ self.where_clause = where_clause.except(*target_values)
410
388
  end
411
389
  else
412
390
  raise ArgumentError, "Unrecognized scoping: #{args.inspect}. Use .unscope(where: :attribute_name) or .unscope(:order), for example."
@@ -416,15 +394,35 @@ module ActiveRecord
416
394
  self
417
395
  end
418
396
 
419
- # Performs a joins on +args+:
397
+ # Performs a joins on +args+. The given symbol(s) should match the name of
398
+ # the association(s).
420
399
  #
421
400
  # User.joins(:posts)
422
- # => SELECT "users".* FROM "users" INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
401
+ # # SELECT "users".*
402
+ # # FROM "users"
403
+ # # INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
404
+ #
405
+ # Multiple joins:
406
+ #
407
+ # User.joins(:posts, :account)
408
+ # # SELECT "users".*
409
+ # # FROM "users"
410
+ # # INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
411
+ # # INNER JOIN "accounts" ON "accounts"."id" = "users"."account_id"
412
+ #
413
+ # Nested joins:
414
+ #
415
+ # User.joins(posts: [:comments])
416
+ # # SELECT "users".*
417
+ # # FROM "users"
418
+ # # INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
419
+ # # INNER JOIN "comments" "comments_posts"
420
+ # # ON "comments_posts"."post_id" = "posts"."id"
423
421
  #
424
422
  # You can use strings in order to customize your joins:
425
423
  #
426
424
  # 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
425
+ # # SELECT "users".* FROM "users" LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id
428
426
  def joins(*args)
429
427
  check_if_method_has_arguments!(:joins, args)
430
428
  spawn.joins!(*args)
@@ -437,12 +435,21 @@ module ActiveRecord
437
435
  self
438
436
  end
439
437
 
440
- def bind(value) # :nodoc:
441
- spawn.bind!(value)
438
+ # Performs a left outer joins on +args+:
439
+ #
440
+ # User.left_outer_joins(:posts)
441
+ # => SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
442
+ #
443
+ def left_outer_joins(*args)
444
+ check_if_method_has_arguments!(__callee__, args)
445
+ spawn.left_outer_joins!(*args)
442
446
  end
447
+ alias :left_joins :left_outer_joins
443
448
 
444
- def bind!(value) # :nodoc:
445
- self.bind_values += [value]
449
+ def left_outer_joins!(*args) # :nodoc:
450
+ args.compact!
451
+ args.flatten!
452
+ self.left_outer_joins_values += args
446
453
  self
447
454
  end
448
455
 
@@ -489,7 +496,7 @@ module ActiveRecord
489
496
  # than the previous methods; you are responsible for ensuring that the values in the template
490
497
  # are properly quoted. The values are passed to the connector for quoting, but the caller
491
498
  # 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>.
499
+ # the values are inserted using the same escapes as the Ruby core method +Kernel::sprintf+.
493
500
  #
494
501
  # User.where(["name = '%s' and email = '%s'", "Joe", "joe@example.com"])
495
502
  # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
@@ -566,7 +573,7 @@ module ActiveRecord
566
573
  # If the condition is any blank-ish object, then #where is a no-op and returns
567
574
  # the current relation.
568
575
  def where(opts = :chain, *rest)
569
- if opts == :chain
576
+ if :chain == opts
570
577
  WhereChain.new(spawn)
571
578
  elsif opts.blank?
572
579
  self
@@ -576,27 +583,61 @@ module ActiveRecord
576
583
  end
577
584
 
578
585
  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)
586
+ opts = sanitize_forbidden_attributes(opts)
587
+ references!(PredicateBuilder.references(opts)) if Hash === opts
588
+ self.where_clause += where_clause_factory.build(opts, rest)
585
589
  self
586
590
  end
587
591
 
588
592
  # Allows you to change a previously set where condition for a given attribute, instead of appending to that condition.
589
593
  #
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
594
+ # Post.where(trashed: true).where(trashed: false)
595
+ # # WHERE `trashed` = 1 AND `trashed` = 0
593
596
  #
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.
597
+ # Post.where(trashed: true).rewhere(trashed: false)
598
+ # # WHERE `trashed` = 0
599
+ #
600
+ # Post.where(active: true).where(trashed: true).rewhere(trashed: false)
601
+ # # WHERE `active` = 1 AND `trashed` = 0
602
+ #
603
+ # This is short-hand for <tt>unscope(where: conditions.keys).where(conditions)</tt>.
604
+ # Note that unlike reorder, we're only unscoping the named conditions -- not the entire where statement.
596
605
  def rewhere(conditions)
597
606
  unscope(where: conditions.keys).where(conditions)
598
607
  end
599
608
 
609
+ # Returns a new relation, which is the logical union of this relation and the one passed as an
610
+ # argument.
611
+ #
612
+ # The two relations must be structurally compatible: they must be scoping the same model, and
613
+ # they must differ only by #where (if no #group has been defined) or #having (if a #group is
614
+ # present). Neither relation may have a #limit, #offset, or #distinct set.
615
+ #
616
+ # Post.where("id = 1").or(Post.where("author_id = 3"))
617
+ # # SELECT `posts`.* FROM `posts` WHERE ((id = 1) OR (author_id = 3))
618
+ #
619
+ def or(other)
620
+ unless other.is_a? Relation
621
+ raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead."
622
+ end
623
+
624
+ spawn.or!(other)
625
+ end
626
+
627
+ def or!(other) # :nodoc:
628
+ incompatible_values = structurally_incompatible_values_for_or(other)
629
+
630
+ unless incompatible_values.empty?
631
+ raise ArgumentError, "Relation passed to #or must be structurally compatible. Incompatible values: #{incompatible_values}"
632
+ end
633
+
634
+ self.where_clause = self.where_clause.or(other.where_clause)
635
+ self.having_clause = having_clause.or(other.having_clause)
636
+ self.references_values += other.references_values
637
+
638
+ self
639
+ end
640
+
600
641
  # Allows to specify a HAVING clause. Note that you can't use HAVING
601
642
  # without also specifying a GROUP clause.
602
643
  #
@@ -606,9 +647,10 @@ module ActiveRecord
606
647
  end
607
648
 
608
649
  def having!(opts, *rest) # :nodoc:
650
+ opts = sanitize_forbidden_attributes(opts)
609
651
  references!(PredicateBuilder.references(opts)) if Hash === opts
610
652
 
611
- self.having_values += build_where(opts, rest)
653
+ self.having_clause += having_clause_factory.build(opts, rest)
612
654
  self
613
655
  end
614
656
 
@@ -643,7 +685,7 @@ module ActiveRecord
643
685
  end
644
686
 
645
687
  # Specifies locking settings (default to +true+). For more information
646
- # on locking, please see +ActiveRecord::Locking+.
688
+ # on locking, please see ActiveRecord::Locking.
647
689
  def lock(locks = true)
648
690
  spawn.lock!(locks)
649
691
  end
@@ -674,7 +716,7 @@ module ActiveRecord
674
716
  # For example:
675
717
  #
676
718
  # @posts = current_user.visible_posts.where(name: params[:name])
677
- # # => the visible_posts method is expected to return a chainable Relation
719
+ # # the visible_posts method is expected to return a chainable Relation
678
720
  #
679
721
  # def visible_posts
680
722
  # case role
@@ -688,7 +730,7 @@ module ActiveRecord
688
730
  # end
689
731
  #
690
732
  def none
691
- where("1=0").extending!(NullRelation)
733
+ spawn.none!
692
734
  end
693
735
 
694
736
  def none! # :nodoc:
@@ -700,7 +742,7 @@ module ActiveRecord
700
742
  #
701
743
  # users = User.readonly
702
744
  # users.first.save
703
- # => ActiveRecord::ReadOnlyRecord: ActiveRecord::ReadOnlyRecord
745
+ # => ActiveRecord::ReadOnlyRecord: User is marked as readonly
704
746
  def readonly(value = true)
705
747
  spawn.readonly!(value)
706
748
  end
@@ -719,7 +761,7 @@ module ActiveRecord
719
761
  # users = users.create_with(name: 'DHH')
720
762
  # users.new.name # => 'DHH'
721
763
  #
722
- # You can pass +nil+ to +create_with+ to reset attributes:
764
+ # You can pass +nil+ to #create_with to reset attributes:
723
765
  #
724
766
  # users = users.create_with(nil)
725
767
  # users.new.name # => 'Oscar'
@@ -732,7 +774,7 @@ module ActiveRecord
732
774
  value = sanitize_forbidden_attributes(value)
733
775
  self.create_with_value = create_with_value.merge(value)
734
776
  else
735
- self.create_with_value = {}
777
+ self.create_with_value = FROZEN_EMPTY_HASH
736
778
  end
737
779
 
738
780
  self
@@ -741,46 +783,44 @@ module ActiveRecord
741
783
  # Specifies table from which the records will be fetched. For example:
742
784
  #
743
785
  # Topic.select('title').from('posts')
744
- # # => SELECT title FROM posts
786
+ # # SELECT title FROM posts
745
787
  #
746
788
  # Can accept other relation objects. For example:
747
789
  #
748
790
  # Topic.select('title').from(Topic.approved)
749
- # # => SELECT title FROM (SELECT * FROM topics WHERE approved = 't') subquery
791
+ # # SELECT title FROM (SELECT * FROM topics WHERE approved = 't') subquery
750
792
  #
751
793
  # Topic.select('a.title').from(Topic.approved, :a)
752
- # # => SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a
794
+ # # SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a
753
795
  #
754
796
  def from(value, subquery_name = nil)
755
797
  spawn.from!(value, subquery_name)
756
798
  end
757
799
 
758
800
  def from!(value, subquery_name = nil) # :nodoc:
759
- self.from_value = [value, subquery_name]
801
+ self.from_clause = Relation::FromClause.new(value, subquery_name)
760
802
  self
761
803
  end
762
804
 
763
805
  # Specifies whether the records should be unique or not. For example:
764
806
  #
765
807
  # User.select(:name)
766
- # # => Might return two records with the same name
808
+ # # Might return two records with the same name
767
809
  #
768
810
  # User.select(:name).distinct
769
- # # => Returns 1 record per distinct name
811
+ # # Returns 1 record per distinct name
770
812
  #
771
813
  # User.select(:name).distinct.distinct(false)
772
- # # => You can also remove the uniqueness
814
+ # # You can also remove the uniqueness
773
815
  def distinct(value = true)
774
816
  spawn.distinct!(value)
775
817
  end
776
- alias uniq distinct
777
818
 
778
819
  # Like #distinct, but modifies relation in place.
779
820
  def distinct!(value = true) # :nodoc:
780
821
  self.distinct_value = value
781
822
  self
782
823
  end
783
- alias uniq! distinct!
784
824
 
785
825
  # Used to extend a scope with additional methods, either through
786
826
  # a module or through a block provided.
@@ -850,327 +890,344 @@ module ActiveRecord
850
890
  self
851
891
  end
852
892
 
853
- # Returns the Arel object associated with the relation.
854
- def arel # :nodoc:
855
- @arel ||= build_arel
893
+ def skip_query_cache!(value = true) # :nodoc:
894
+ self.skip_query_cache_value = value
895
+ self
856
896
  end
857
897
 
858
- private
859
-
860
- def build_arel
861
- arel = Arel::SelectManager.new(table.engine, table)
898
+ # Returns the Arel object associated with the relation.
899
+ def arel(aliases = nil) # :nodoc:
900
+ @arel ||= build_arel(aliases)
901
+ end
862
902
 
863
- build_joins(arel, joins_values.flatten) unless joins_values.empty?
903
+ # Returns a relation value with a given name
904
+ def get_value(name) # :nodoc:
905
+ @values.fetch(name, DEFAULT_VALUES[name])
906
+ end
864
907
 
865
- collapse_wheres(arel, (where_values - [''])) #TODO: Add uniq with real value comparison / ignore uniqs that have binds
908
+ protected
866
909
 
867
- arel.having(*having_values.uniq.reject(&:blank?)) unless having_values.empty?
910
+ # Sets the relation value with the given name
911
+ def set_value(name, value) # :nodoc:
912
+ assert_mutability!
913
+ @values[name] = value
914
+ end
868
915
 
869
- arel.take(connection.sanitize_limit(limit_value)) if limit_value
870
- arel.skip(offset_value.to_i) if offset_value
916
+ private
871
917
 
872
- arel.group(*group_values.uniq.reject(&:blank?)) unless group_values.empty?
918
+ def assert_mutability!
919
+ raise ImmutableRelation if @loaded
920
+ raise ImmutableRelation if defined?(@arel) && @arel
921
+ end
873
922
 
874
- build_order(arel)
923
+ def build_arel(aliases)
924
+ arel = Arel::SelectManager.new(table)
925
+
926
+ aliases = build_joins(arel, joins_values.flatten, aliases) unless joins_values.empty?
927
+ build_left_outer_joins(arel, left_outer_joins_values.flatten, aliases) unless left_outer_joins_values.empty?
928
+
929
+ arel.where(where_clause.ast) unless where_clause.empty?
930
+ arel.having(having_clause.ast) unless having_clause.empty?
931
+ if limit_value
932
+ limit_attribute = ActiveModel::Attribute.with_cast_value(
933
+ "LIMIT".freeze,
934
+ connection.sanitize_limit(limit_value),
935
+ Type.default_value,
936
+ )
937
+ arel.take(Arel::Nodes::BindParam.new(limit_attribute))
938
+ end
939
+ if offset_value
940
+ offset_attribute = ActiveModel::Attribute.with_cast_value(
941
+ "OFFSET".freeze,
942
+ offset_value.to_i,
943
+ Type.default_value,
944
+ )
945
+ arel.skip(Arel::Nodes::BindParam.new(offset_attribute))
946
+ end
947
+ arel.group(*arel_columns(group_values.uniq.reject(&:blank?))) unless group_values.empty?
875
948
 
876
- build_select(arel, select_values.uniq)
949
+ build_order(arel)
877
950
 
878
- arel.distinct(distinct_value)
879
- arel.from(build_from) if from_value
880
- arel.lock(lock_value) if lock_value
951
+ build_select(arel)
881
952
 
882
- arel
883
- end
953
+ arel.distinct(distinct_value)
954
+ arel.from(build_from) unless from_clause.empty?
955
+ arel.lock(lock_value) if lock_value
884
956
 
885
- def symbol_unscoping(scope)
886
- if !VALID_UNSCOPING_VALUES.include?(scope)
887
- raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
957
+ arel
888
958
  end
889
959
 
890
- single_val_method = Relation::SINGLE_VALUE_METHODS.include?(scope)
891
- unscope_code = "#{scope}_value#{'s' unless single_val_method}="
892
-
893
- case scope
894
- when :order
895
- result = []
896
- when :where
897
- self.bind_values = []
898
- else
899
- result = [] unless single_val_method
960
+ def build_from
961
+ opts = from_clause.value
962
+ name = from_clause.name
963
+ case opts
964
+ when Relation
965
+ if opts.eager_loading?
966
+ opts = opts.send(:apply_join_dependency)
967
+ end
968
+ name ||= "subquery"
969
+ opts.arel.as(name.to_s)
970
+ else
971
+ opts
972
+ end
900
973
  end
901
974
 
902
- self.send(unscope_code, result)
903
- end
904
-
905
- def where_unscoping(target_value)
906
- target_value = target_value.to_s
907
-
908
- where_values.reject! do |rel|
909
- case rel
910
- when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThanOrEqual
911
- subrelation = (rel.left.kind_of?(Arel::Attributes::Attribute) ? rel.left : rel.right)
912
- subrelation.name == target_value
975
+ def build_left_outer_joins(manager, outer_joins, aliases)
976
+ buckets = outer_joins.group_by do |join|
977
+ case join
978
+ when Hash, Symbol, Array
979
+ :association_join
980
+ when ActiveRecord::Associations::JoinDependency
981
+ :stashed_join
982
+ else
983
+ raise ArgumentError, "only Hash, Symbol and Array are allowed"
984
+ end
913
985
  end
986
+
987
+ build_join_query(manager, buckets, Arel::Nodes::OuterJoin, aliases)
914
988
  end
915
989
 
916
- bind_values.reject! { |col,_| col.name == target_value }
917
- end
990
+ def build_joins(manager, joins, aliases)
991
+ buckets = joins.group_by do |join|
992
+ case join
993
+ when String
994
+ :string_join
995
+ when Hash, Symbol, Array
996
+ :association_join
997
+ when ActiveRecord::Associations::JoinDependency
998
+ :stashed_join
999
+ when Arel::Nodes::Join
1000
+ :join_node
1001
+ else
1002
+ raise "unknown class: %s" % join.class.name
1003
+ end
1004
+ end
918
1005
 
919
- def custom_join_ast(table, joins)
920
- joins = joins.reject(&:blank?)
1006
+ build_join_query(manager, buckets, Arel::Nodes::InnerJoin, aliases)
1007
+ end
921
1008
 
922
- return [] if joins.empty?
1009
+ def build_join_query(manager, buckets, join_type, aliases)
1010
+ buckets.default = []
923
1011
 
924
- joins.map! do |join|
925
- case join
926
- when Array
927
- join = Arel.sql(join.join(' ')) if array_of_strings?(join)
928
- when String
929
- join = Arel.sql(join)
930
- end
931
- table.create_string_join(join)
932
- end
933
- end
1012
+ association_joins = buckets[:association_join]
1013
+ stashed_joins = buckets[:stashed_join]
1014
+ join_nodes = buckets[:join_node].uniq
1015
+ string_joins = buckets[:string_join].map(&:strip).uniq
934
1016
 
935
- def collapse_wheres(arel, wheres)
936
- predicates = wheres.map do |where|
937
- next where if ::Arel::Nodes::Equality === where
938
- where = Arel.sql(where) if String === where
939
- Arel::Nodes::Grouping.new(where)
940
- end
1017
+ join_list = join_nodes + convert_join_strings_to_ast(string_joins)
1018
+ alias_tracker = alias_tracker(join_list, aliases)
941
1019
 
942
- arel.where(Arel::Nodes::And.new(predicates)) if predicates.present?
943
- end
1020
+ join_dependency = ActiveRecord::Associations::JoinDependency.new(
1021
+ klass, table, association_joins
1022
+ )
944
1023
 
945
- def build_where(opts, other = [])
946
- case opts
947
- when String, Array
948
- [@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))]
949
- when Hash
950
- opts = PredicateBuilder.resolve_column_aliases(klass, opts)
1024
+ joins = join_dependency.join_constraints(stashed_joins, join_type, alias_tracker)
1025
+ joins.each { |join| manager.from(join) }
951
1026
 
952
- tmp_opts, bind_values = create_binds(opts)
953
- self.bind_values += bind_values
1027
+ manager.join_sources.concat(join_list)
954
1028
 
955
- attributes = @klass.send(:expand_hash_conditions_for_aggregates, tmp_opts)
956
- add_relations_to_bind_values(attributes)
1029
+ alias_tracker.aliases
1030
+ end
957
1031
 
958
- PredicateBuilder.build_from_hash(klass, attributes, table)
959
- else
960
- [opts]
1032
+ def convert_join_strings_to_ast(joins)
1033
+ joins
1034
+ .flatten
1035
+ .reject(&:blank?)
1036
+ .map { |join| table.create_string_join(Arel.sql(join)) }
961
1037
  end
962
- end
963
1038
 
964
- def create_binds(opts)
965
- bindable, non_binds = opts.partition do |column, value|
966
- case value
967
- when String, Integer, ActiveRecord::StatementCache::Substitute
968
- @klass.columns_hash.include? column.to_s
1039
+ def build_select(arel)
1040
+ if select_values.any?
1041
+ arel.project(*arel_columns(select_values.uniq))
1042
+ elsif klass.ignored_columns.any?
1043
+ arel.project(*klass.column_names.map { |field| arel_attribute(field) })
969
1044
  else
970
- false
1045
+ arel.project(table[Arel.star])
971
1046
  end
972
1047
  end
973
1048
 
974
- association_binds, non_binds = non_binds.partition do |column, value|
975
- value.is_a?(Hash) && association_for_table(column)
1049
+ def arel_columns(columns)
1050
+ columns.flat_map do |field|
1051
+ case field
1052
+ when Symbol
1053
+ arel_column(field.to_s) do |attr_name|
1054
+ connection.quote_table_name(attr_name)
1055
+ end
1056
+ when String
1057
+ arel_column(field, &:itself)
1058
+ when Proc
1059
+ field.call
1060
+ else
1061
+ field
1062
+ end
1063
+ end
976
1064
  end
977
1065
 
978
- new_opts = {}
979
- binds = []
1066
+ def arel_column(field)
1067
+ field = klass.attribute_alias(field) if klass.attribute_alias?(field)
1068
+ from = from_clause.name || from_clause.value
980
1069
 
981
- bindable.each do |(column,value)|
982
- binds.push [@klass.columns_hash[column.to_s], value]
983
- new_opts[column] = connection.substitute_at(column)
1070
+ if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
1071
+ arel_attribute(field)
1072
+ else
1073
+ yield field
1074
+ end
984
1075
  end
985
1076
 
986
- association_binds.each do |(column, value)|
987
- association_relation = association_for_table(column).klass.send(:relation)
988
- association_new_opts, association_bind = association_relation.send(:create_binds, value)
989
- new_opts[column] = association_new_opts
990
- binds += association_bind
1077
+ def table_name_matches?(from)
1078
+ /(?:\A|(?<!FROM)\s)(?:\b#{table.name}\b|#{connection.quote_table_name(table.name)})(?!\.)/i.match?(from.to_s)
991
1079
  end
992
1080
 
993
- non_binds.each { |column,value| new_opts[column] = value }
994
-
995
- [new_opts, binds]
996
- end
997
-
998
- def association_for_table(table_name)
999
- table_name = table_name.to_s
1000
- @klass._reflect_on_association(table_name) ||
1001
- @klass._reflect_on_association(table_name.singularize)
1002
- end
1003
-
1004
- def build_from
1005
- opts, name = from_value
1006
- case opts
1007
- when Relation
1008
- name ||= 'subquery'
1009
- self.bind_values = opts.bind_values + self.bind_values
1010
- opts.arel.as(name.to_s)
1011
- else
1012
- opts
1013
- end
1014
- end
1081
+ def reverse_sql_order(order_query)
1082
+ if order_query.empty?
1083
+ return [arel_attribute(primary_key).desc] if primary_key
1084
+ raise IrreversibleOrderError,
1085
+ "Relation has no current order and table has no primary key to be used as default order"
1086
+ end
1015
1087
 
1016
- def build_joins(manager, joins)
1017
- buckets = joins.group_by do |join|
1018
- case join
1019
- when String
1020
- :string_join
1021
- when Hash, Symbol, Array
1022
- :association_join
1023
- when ActiveRecord::Associations::JoinDependency
1024
- :stashed_join
1025
- when Arel::Nodes::Join
1026
- :join_node
1027
- else
1028
- raise 'unknown class: %s' % join.class.name
1088
+ order_query.flat_map do |o|
1089
+ case o
1090
+ when Arel::Attribute
1091
+ o.desc
1092
+ when Arel::Nodes::Ordering
1093
+ o.reverse
1094
+ when String
1095
+ if does_not_support_reverse?(o)
1096
+ raise IrreversibleOrderError, "Order #{o.inspect} can not be reversed automatically"
1097
+ end
1098
+ o.split(",").map! do |s|
1099
+ s.strip!
1100
+ s.gsub!(/\sasc\Z/i, " DESC") || s.gsub!(/\sdesc\Z/i, " ASC") || (s << " DESC")
1101
+ end
1102
+ else
1103
+ o
1104
+ end
1029
1105
  end
1030
1106
  end
1031
1107
 
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
1108
+ def does_not_support_reverse?(order)
1109
+ # Account for String subclasses like Arel::Nodes::SqlLiteral that
1110
+ # override methods like #count.
1111
+ order = String.new(order) unless order.instance_of?(String)
1036
1112
 
1037
- join_list = join_nodes + custom_join_ast(manager, string_joins)
1038
-
1039
- join_dependency = ActiveRecord::Associations::JoinDependency.new(
1040
- @klass,
1041
- association_joins,
1042
- join_list
1043
- )
1113
+ # Uses SQL function with multiple arguments.
1114
+ (order.include?(",") && order.split(",").find { |section| section.count("(") != section.count(")") }) ||
1115
+ # Uses "nulls first" like construction.
1116
+ /nulls (first|last)\Z/i.match?(order)
1117
+ end
1044
1118
 
1045
- join_infos = join_dependency.join_constraints stashed_association_joins
1119
+ def build_order(arel)
1120
+ orders = order_values.uniq
1121
+ orders.reject!(&:blank?)
1046
1122
 
1047
- join_infos.each do |info|
1048
- info.joins.each { |join| manager.from(join) }
1049
- manager.bind_values.concat info.binds
1123
+ arel.order(*orders) unless orders.empty?
1050
1124
  end
1051
1125
 
1052
- manager.join_sources.concat(join_list)
1126
+ VALID_DIRECTIONS = [:asc, :desc, :ASC, :DESC,
1127
+ "asc", "desc", "ASC", "DESC"].to_set # :nodoc:
1053
1128
 
1054
- manager
1055
- end
1056
-
1057
- def build_select(arel, selects)
1058
- if !selects.empty?
1059
- expanded_select = selects.map do |field|
1060
- if (Symbol === field || String === field) && columns_hash.key?(field.to_s)
1061
- arel_table[field]
1062
- else
1063
- field
1129
+ def validate_order_args(args)
1130
+ args.each do |arg|
1131
+ next unless arg.is_a?(Hash)
1132
+ arg.each do |_key, value|
1133
+ unless VALID_DIRECTIONS.include?(value)
1134
+ raise ArgumentError,
1135
+ "Direction \"#{value}\" is invalid. Valid directions are: #{VALID_DIRECTIONS.to_a.inspect}"
1136
+ end
1064
1137
  end
1065
1138
  end
1139
+ end
1066
1140
 
1067
- arel.project(*expanded_select)
1068
- else
1069
- arel.project(@klass.arel_table[Arel.star])
1141
+ def preprocess_order_args(order_args)
1142
+ order_args.map! do |arg|
1143
+ klass.sanitize_sql_for_order(arg)
1144
+ end
1145
+ order_args.flatten!
1146
+
1147
+ @klass.enforce_raw_sql_whitelist(
1148
+ order_args.flat_map { |a| a.is_a?(Hash) ? a.keys : a },
1149
+ whitelist: AttributeMethods::ClassMethods::COLUMN_NAME_ORDER_WHITELIST
1150
+ )
1151
+
1152
+ validate_order_args(order_args)
1153
+
1154
+ references = order_args.grep(String)
1155
+ references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
1156
+ references!(references) if references.any?
1157
+
1158
+ # if a symbol is given we prepend the quoted table name
1159
+ order_args.map! do |arg|
1160
+ case arg
1161
+ when Symbol
1162
+ order_column(arg.to_s).asc
1163
+ when Hash
1164
+ arg.map { |field, dir|
1165
+ case field
1166
+ when Arel::Nodes::SqlLiteral
1167
+ field.send(dir.downcase)
1168
+ else
1169
+ order_column(field.to_s).send(dir.downcase)
1170
+ end
1171
+ }
1172
+ else
1173
+ arg
1174
+ end
1175
+ end.flatten!
1070
1176
  end
1071
- end
1072
1177
 
1073
- def reverse_sql_order(order_query)
1074
- order_query = ["#{quoted_table_name}.#{quoted_primary_key} ASC"] if order_query.empty?
1075
-
1076
- order_query.flat_map do |o|
1077
- case o
1078
- when Arel::Nodes::Ordering
1079
- o.reverse
1080
- when String
1081
- o.to_s.split(',').map! do |s|
1082
- s.strip!
1083
- s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC')
1178
+ def order_column(field)
1179
+ arel_column(field) do |attr_name|
1180
+ if attr_name == "count" && !group_values.empty?
1181
+ arel_attribute(attr_name)
1182
+ else
1183
+ Arel.sql(connection.quote_table_name(attr_name))
1084
1184
  end
1085
- else
1086
- o
1087
1185
  end
1088
1186
  end
1089
- end
1090
1187
 
1091
- def array_of_strings?(o)
1092
- o.is_a?(Array) && o.all? { |obj| obj.is_a?(String) }
1093
- end
1094
-
1095
- def build_order(arel)
1096
- orders = order_values.uniq
1097
- orders.reject!(&:blank?)
1098
-
1099
- arel.order(*orders) unless orders.empty?
1100
- end
1101
-
1102
- VALID_DIRECTIONS = [:asc, :desc, :ASC, :DESC,
1103
- 'asc', 'desc', 'ASC', 'DESC'] # :nodoc:
1104
-
1105
- def validate_order_args(args)
1106
- args.each do |arg|
1107
- next unless arg.is_a?(Hash)
1108
- arg.each do |_key, value|
1109
- raise ArgumentError, "Direction \"#{value}\" is invalid. Valid " \
1110
- "directions are: #{VALID_DIRECTIONS.inspect}" unless VALID_DIRECTIONS.include?(value)
1188
+ # Checks to make sure that the arguments are not blank. Note that if some
1189
+ # blank-like object were initially passed into the query method, then this
1190
+ # method will not raise an error.
1191
+ #
1192
+ # Example:
1193
+ #
1194
+ # Post.references() # raises an error
1195
+ # Post.references([]) # does not raise an error
1196
+ #
1197
+ # This particular method should be called with a method_name and the args
1198
+ # passed into that method as an input. For example:
1199
+ #
1200
+ # def references(*args)
1201
+ # check_if_method_has_arguments!("references", args)
1202
+ # ...
1203
+ # end
1204
+ def check_if_method_has_arguments!(method_name, args)
1205
+ if args.blank?
1206
+ raise ArgumentError, "The method .#{method_name}() must contain arguments."
1111
1207
  end
1112
1208
  end
1113
- end
1114
-
1115
- def preprocess_order_args(order_args)
1116
- order_args.flatten!
1117
- validate_order_args(order_args)
1118
-
1119
- references = order_args.grep(String)
1120
- references.map! { |arg| arg =~ /^([a-zA-Z]\w*)\.(\w+)/ && $1 }.compact!
1121
- references!(references) if references.any?
1122
1209
 
1123
- # if a symbol is given we prepend the quoted table name
1124
- order_args.map! do |arg|
1125
- case arg
1126
- when Symbol
1127
- arg = klass.attribute_alias(arg) if klass.attribute_alias?(arg)
1128
- table[arg].asc
1129
- when Hash
1130
- arg.map { |field, dir|
1131
- field = klass.attribute_alias(field) if klass.attribute_alias?(field)
1132
- table[field].send(dir.downcase)
1133
- }
1134
- else
1135
- arg
1210
+ STRUCTURAL_OR_METHODS = Relation::VALUE_METHODS - [:extending, :where, :having, :unscope, :references]
1211
+ def structurally_incompatible_values_for_or(other)
1212
+ STRUCTURAL_OR_METHODS.reject do |method|
1213
+ get_value(method) == other.get_value(method)
1136
1214
  end
1137
- end.flatten!
1138
- end
1215
+ end
1139
1216
 
1140
- # Checks to make sure that the arguments are not blank. Note that if some
1141
- # blank-like object were initially passed into the query method, then this
1142
- # method will not raise an error.
1143
- #
1144
- # Example:
1145
- #
1146
- # Post.references() # => raises an error
1147
- # Post.references([]) # => does not raise an error
1148
- #
1149
- # This particular method should be called with a method_name and the args
1150
- # passed into that method as an input. For example:
1151
- #
1152
- # def references(*args)
1153
- # check_if_method_has_arguments!("references", args)
1154
- # ...
1155
- # end
1156
- def check_if_method_has_arguments!(method_name, args)
1157
- if args.blank?
1158
- raise ArgumentError, "The method .#{method_name}() must contain arguments."
1217
+ def where_clause_factory
1218
+ @where_clause_factory ||= Relation::WhereClauseFactory.new(klass, predicate_builder)
1159
1219
  end
1160
- end
1220
+ alias having_clause_factory where_clause_factory
1161
1221
 
1162
- # This function is recursive just for better readablity.
1163
- # #where argument doesn't support more than one level nested hash in real world.
1164
- def add_relations_to_bind_values(attributes)
1165
- if attributes.is_a?(Hash)
1166
- attributes.each_value do |value|
1167
- if value.is_a?(ActiveRecord::Relation)
1168
- self.bind_values += value.bind_values
1169
- else
1170
- add_relations_to_bind_values(value)
1171
- end
1172
- end
1222
+ DEFAULT_VALUES = {
1223
+ create_with: FROZEN_EMPTY_HASH,
1224
+ where: Relation::WhereClause.empty,
1225
+ having: Relation::WhereClause.empty,
1226
+ from: Relation::FromClause.empty
1227
+ }
1228
+
1229
+ Relation::MULTI_VALUE_METHODS.each do |value|
1230
+ DEFAULT_VALUES[value] ||= FROZEN_EMPTY_ARRAY
1173
1231
  end
1174
- end
1175
1232
  end
1176
1233
  end