activerecord 4.2.6 → 5.0.0

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