activerecord 4.2.11 → 5.2.4.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 +4 -4
  2. data/CHANGELOG.md +580 -1626
  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 +263 -249
  8. data/lib/active_record/association_relation.rb +11 -6
  9. data/lib/active_record/associations/alias_tracker.rb +29 -35
  10. data/lib/active_record/associations/association.rb +77 -43
  11. data/lib/active_record/associations/association_scope.rb +106 -133
  12. data/lib/active_record/associations/belongs_to_association.rb +52 -41
  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 +9 -22
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +42 -35
  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 +139 -280
  22. data/lib/active_record/associations/collection_proxy.rb +231 -133
  23. data/lib/active_record/associations/foreign_association.rb +3 -1
  24. data/lib/active_record/associations/has_many_association.rb +34 -89
  25. data/lib/active_record/associations/has_many_through_association.rb +49 -76
  26. data/lib/active_record/associations/has_one_association.rb +38 -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 -87
  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 +133 -159
  32. data/lib/active_record/associations/preloader/association.rb +85 -120
  33. data/lib/active_record/associations/preloader/through_association.rb +85 -74
  34. data/lib/active_record/associations/preloader.rb +81 -91
  35. data/lib/active_record/associations/singular_association.rb +27 -34
  36. data/lib/active_record/associations/through_association.rb +38 -18
  37. data/lib/active_record/associations.rb +1732 -1597
  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 +10 -8
  41. data/lib/active_record/attribute_methods/dirty.rb +94 -135
  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 +58 -36
  47. data/lib/active_record/attribute_methods/write.rb +30 -45
  48. data/lib/active_record/attribute_methods.rb +166 -109
  49. data/lib/active_record/attributes.rb +201 -82
  50. data/lib/active_record/autosave_association.rb +94 -36
  51. data/lib/active_record/base.rb +57 -44
  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 +24 -12
  55. data/lib/active_record/collection_cache_key.rb +53 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +712 -290
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +10 -5
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +237 -90
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +71 -21
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +118 -52
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +5 -3
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +67 -46
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +318 -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 +570 -228
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +138 -70
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +325 -202
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +542 -601
  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 +41 -180
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +35 -11
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +45 -114
  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 -58
  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 +4 -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 -22
  92. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +5 -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 +5 -7
  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 -5
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +55 -53
  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 +462 -284
  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 +432 -323
  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 -308
  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 +178 -198
  129. data/lib/active_record/counter_cache.rb +79 -36
  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 +135 -88
  133. data/lib/active_record/errors.rb +179 -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 +10 -5
  137. data/lib/active_record/fixture_set/file.rb +35 -9
  138. data/lib/active_record/fixtures.rb +188 -132
  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 +21 -3
  144. data/lib/active_record/locale/en.yml +3 -2
  145. data/lib/active_record/locking/optimistic.rb +88 -96
  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 +581 -282
  152. data/lib/active_record/model_schema.rb +290 -111
  153. data/lib/active_record/nested_attributes.rb +264 -222
  154. data/lib/active_record/no_touching.rb +7 -1
  155. data/lib/active_record/null_relation.rb +24 -37
  156. data/lib/active_record/persistence.rb +347 -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 +94 -32
  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 +149 -156
  163. data/lib/active_record/readonly_attributes.rb +5 -4
  164. data/lib/active_record/reflection.rb +414 -267
  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 +256 -248
  168. data/lib/active_record/relation/delegation.rb +67 -60
  169. data/lib/active_record/relation/finder_methods.rb +288 -239
  170. data/lib/active_record/relation/from_clause.rb +26 -0
  171. data/lib/active_record/relation/merger.rb +86 -86
  172. data/lib/active_record/relation/predicate_builder/array_handler.rb +24 -24
  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 +116 -119
  180. data/lib/active_record/relation/query_attribute.rb +45 -0
  181. data/lib/active_record/relation/query_methods.rb +448 -393
  182. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  183. data/lib/active_record/relation/spawn_methods.rb +11 -13
  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 -340
  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 -16
  193. data/lib/active_record/scoping/default.rb +102 -85
  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 +134 -96
  203. data/lib/active_record/tasks/mysql_database_tasks.rb +56 -100
  204. data/lib/active_record/tasks/postgresql_database_tasks.rb +83 -41
  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 +199 -124
  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 -45
  212. data/lib/active_record/type/date_time.rb +4 -49
  213. data/lib/active_record/type/decimal_without_scale.rb +6 -2
  214. data/lib/active_record/type/hash_lookup_type_map.rb +5 -3
  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 +24 -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 +40 -41
  231. data/lib/active_record/validations.rb +38 -35
  232. data/lib/active_record/version.rb +3 -1
  233. data/lib/active_record.rb +34 -22
  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 -3
  238. data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +8 -1
  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/model/templates/{module.rb → module.rb.tt} +0 -0
  243. data/lib/rails/generators/active_record.rb +7 -5
  244. metadata +72 -50
  245. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  246. data/lib/active_record/associations/preloader/collection_association.rb +0 -24
  247. data/lib/active_record/associations/preloader/has_many.rb +0 -17
  248. data/lib/active_record/associations/preloader/has_many_through.rb +0 -19
  249. data/lib/active_record/associations/preloader/has_one.rb +0 -23
  250. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  251. data/lib/active_record/associations/preloader/singular_association.rb +0 -21
  252. data/lib/active_record/attribute.rb +0 -163
  253. data/lib/active_record/attribute_set/builder.rb +0 -106
  254. data/lib/active_record/attribute_set.rb +0 -81
  255. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -498
  256. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  257. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  258. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
  259. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +0 -35
  260. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  261. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  262. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  263. data/lib/active_record/type/big_integer.rb +0 -13
  264. data/lib/active_record/type/binary.rb +0 -50
  265. data/lib/active_record/type/boolean.rb +0 -31
  266. data/lib/active_record/type/decimal.rb +0 -64
  267. data/lib/active_record/type/decorator.rb +0 -14
  268. data/lib/active_record/type/float.rb +0 -19
  269. data/lib/active_record/type/integer.rb +0 -59
  270. data/lib/active_record/type/mutable.rb +0 -16
  271. data/lib/active_record/type/numeric.rb +0 -36
  272. data/lib/active_record/type/string.rb +0 -40
  273. data/lib/active_record/type/time_value.rb +0 -38
  274. data/lib/active_record/type/value.rb +0 -110
@@ -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).to_sym : 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,49 +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]
760
- if value.is_a? Relation
761
- self.bind_values = value.arel.bind_values + value.bind_values + bind_values
762
- end
801
+ self.from_clause = Relation::FromClause.new(value, subquery_name)
763
802
  self
764
803
  end
765
804
 
766
805
  # Specifies whether the records should be unique or not. For example:
767
806
  #
768
807
  # User.select(:name)
769
- # # => Might return two records with the same name
808
+ # # Might return two records with the same name
770
809
  #
771
810
  # User.select(:name).distinct
772
- # # => Returns 1 record per distinct name
811
+ # # Returns 1 record per distinct name
773
812
  #
774
813
  # User.select(:name).distinct.distinct(false)
775
- # # => You can also remove the uniqueness
814
+ # # You can also remove the uniqueness
776
815
  def distinct(value = true)
777
816
  spawn.distinct!(value)
778
817
  end
779
- alias uniq distinct
780
818
 
781
819
  # Like #distinct, but modifies relation in place.
782
820
  def distinct!(value = true) # :nodoc:
783
821
  self.distinct_value = value
784
822
  self
785
823
  end
786
- alias uniq! distinct!
787
824
 
788
825
  # Used to extend a scope with additional methods, either through
789
826
  # a module or through a block provided.
@@ -853,326 +890,344 @@ module ActiveRecord
853
890
  self
854
891
  end
855
892
 
856
- # Returns the Arel object associated with the relation.
857
- def arel # :nodoc:
858
- @arel ||= build_arel
893
+ def skip_query_cache!(value = true) # :nodoc:
894
+ self.skip_query_cache_value = value
895
+ self
859
896
  end
860
897
 
861
- private
862
-
863
- def build_arel
864
- arel = Arel::SelectManager.new(table.engine, table)
865
-
866
- build_joins(arel, joins_values.flatten) unless joins_values.empty?
898
+ # Returns the Arel object associated with the relation.
899
+ def arel(aliases = nil) # :nodoc:
900
+ @arel ||= build_arel(aliases)
901
+ end
867
902
 
868
- collapse_wheres(arel, (where_values - [''])) #TODO: Add uniq with real value comparison / ignore uniqs that have binds
903
+ # Returns a relation value with a given name
904
+ def get_value(name) # :nodoc:
905
+ @values.fetch(name, DEFAULT_VALUES[name])
906
+ end
869
907
 
870
- arel.having(*having_values.uniq.reject(&:blank?)) unless having_values.empty?
908
+ protected
871
909
 
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?
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
875
915
 
876
- build_order(arel)
916
+ private
877
917
 
878
- build_select(arel)
918
+ def assert_mutability!
919
+ raise ImmutableRelation if @loaded
920
+ raise ImmutableRelation if defined?(@arel) && @arel
921
+ end
879
922
 
880
- arel.distinct(distinct_value)
881
- arel.from(build_from) if from_value
882
- arel.lock(lock_value) if lock_value
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?
883
948
 
884
- arel
885
- end
949
+ build_order(arel)
886
950
 
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
951
+ build_select(arel)
891
952
 
892
- single_val_method = Relation::SINGLE_VALUE_METHODS.include?(scope)
893
- unscope_code = "#{scope}_value#{'s' unless single_val_method}="
953
+ arel.distinct(distinct_value)
954
+ arel.from(build_from) unless from_clause.empty?
955
+ arel.lock(lock_value) if lock_value
894
956
 
895
- case scope
896
- when :order
897
- result = []
898
- when :where
899
- self.bind_values = []
900
- else
901
- result = [] unless single_val_method
957
+ arel
902
958
  end
903
959
 
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
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
915
972
  end
916
973
  end
917
974
 
918
- bind_values.reject! { |col,_| col.name == target_value }
919
- end
920
-
921
- def custom_join_ast(table, joins)
922
- joins = joins.reject(&:blank?)
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
985
+ end
923
986
 
924
- return [] if joins.empty?
987
+ build_join_query(manager, buckets, Arel::Nodes::OuterJoin, aliases)
988
+ end
925
989
 
926
- joins.map! do |join|
927
- case join
928
- when Array
929
- join = Arel.sql(join.join(' ')) if array_of_strings?(join)
930
- when String
931
- join = Arel.sql(join)
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
932
1004
  end
933
- table.create_string_join(join)
934
- end
935
- end
936
1005
 
937
- def collapse_wheres(arel, wheres)
938
- predicates = wheres.map do |where|
939
- next where if ::Arel::Nodes::Equality === where
940
- where = Arel.sql(where) if String === where
941
- Arel::Nodes::Grouping.new(where)
1006
+ build_join_query(manager, buckets, Arel::Nodes::InnerJoin, aliases)
942
1007
  end
943
1008
 
944
- arel.where(Arel::Nodes::And.new(predicates)) if predicates.present?
945
- end
1009
+ def build_join_query(manager, buckets, join_type, aliases)
1010
+ buckets.default = []
946
1011
 
947
- def build_where(opts, other = [])
948
- case opts
949
- when String, Array
950
- [@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))]
951
- when Hash
952
- opts = PredicateBuilder.resolve_column_aliases(klass, opts)
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
953
1016
 
954
- tmp_opts, bind_values = create_binds(opts)
955
- self.bind_values += bind_values
1017
+ join_list = join_nodes + convert_join_strings_to_ast(string_joins)
1018
+ alias_tracker = alias_tracker(join_list, aliases)
956
1019
 
957
- attributes = @klass.send(:expand_hash_conditions_for_aggregates, tmp_opts)
958
- add_relations_to_bind_values(attributes)
1020
+ join_dependency = ActiveRecord::Associations::JoinDependency.new(
1021
+ klass, table, association_joins
1022
+ )
959
1023
 
960
- PredicateBuilder.build_from_hash(klass, attributes, table)
961
- else
962
- [opts]
963
- end
964
- end
1024
+ joins = join_dependency.join_constraints(stashed_joins, join_type, alias_tracker)
1025
+ joins.each { |join| manager.from(join) }
965
1026
 
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
1027
+ manager.join_sources.concat(join_list)
972
1028
 
973
- association_binds, non_binds = non_binds.partition do |column, value|
974
- value.is_a?(Hash) && association_for_table(column)
1029
+ alias_tracker.aliases
975
1030
  end
976
1031
 
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)
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)) }
985
1037
  end
986
1038
 
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
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) })
1044
+ else
1045
+ arel.project(table[Arel.star])
1046
+ end
992
1047
  end
993
1048
 
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
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
1013
1064
  end
1014
- end
1015
1065
 
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
1066
+ def arel_column(field)
1067
+ field = klass.attribute_alias(field) if klass.attribute_alias?(field)
1068
+ from = from_clause.name || from_clause.value
1069
+
1070
+ if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
1071
+ arel_attribute(field)
1027
1072
  else
1028
- raise 'unknown class: %s' % join.class.name
1073
+ yield field
1029
1074
  end
1030
1075
  end
1031
1076
 
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
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)
1079
+ end
1036
1080
 
1037
- join_list = join_nodes + custom_join_ast(manager, string_joins)
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
1038
1087
 
1039
- join_dependency = ActiveRecord::Associations::JoinDependency.new(
1040
- @klass,
1041
- association_joins,
1042
- join_list
1043
- )
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
1105
+ end
1106
+ end
1044
1107
 
1045
- join_infos = join_dependency.join_constraints stashed_association_joins
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)
1046
1112
 
1047
- join_infos.each do |info|
1048
- info.joins.each { |join| manager.from(join) }
1049
- manager.bind_values.concat info.binds
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)
1050
1117
  end
1051
1118
 
1052
- manager.join_sources.concat(join_list)
1119
+ def build_order(arel)
1120
+ orders = order_values.uniq
1121
+ orders.reject!(&:blank?)
1053
1122
 
1054
- manager
1055
- end
1123
+ arel.order(*orders) unless orders.empty?
1124
+ end
1056
1125
 
1057
- def build_select(arel)
1058
- if select_values.any?
1059
- arel.project(*arel_columns(select_values.uniq))
1060
- else
1061
- arel.project(@klass.arel_table[Arel.star])
1126
+ VALID_DIRECTIONS = [:asc, :desc, :ASC, :DESC,
1127
+ "asc", "desc", "ASC", "DESC"].to_set # :nodoc:
1128
+
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
1137
+ end
1138
+ end
1062
1139
  end
1063
- end
1064
1140
 
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
1141
+ def preprocess_order_args(order_args)
1142
+ order_args.map! do |arg|
1143
+ klass.sanitize_sql_for_order(arg)
1073
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!
1074
1176
  end
1075
- end
1076
1177
 
1077
- def reverse_sql_order(order_query)
1078
- order_query = ["#{quoted_table_name}.#{quoted_primary_key} ASC"] if order_query.empty?
1079
-
1080
- order_query.flat_map do |o|
1081
- case o
1082
- when Arel::Nodes::Ordering
1083
- o.reverse
1084
- when String
1085
- o.to_s.split(',').map! do |s|
1086
- s.strip!
1087
- 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))
1088
1184
  end
1089
- else
1090
- o
1091
1185
  end
1092
1186
  end
1093
- end
1094
1187
 
1095
- def array_of_strings?(o)
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)
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."
1115
1207
  end
1116
1208
  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
1209
 
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
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)
1140
1214
  end
1141
- end.flatten!
1142
- end
1215
+ end
1143
1216
 
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."
1217
+ def where_clause_factory
1218
+ @where_clause_factory ||= Relation::WhereClauseFactory.new(klass, predicate_builder)
1163
1219
  end
1164
- end
1220
+ alias having_clause_factory where_clause_factory
1165
1221
 
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
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
1175
1231
  end
1176
- end
1177
1232
  end
1178
1233
  end