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.
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