activerecord 4.2.11.3 → 5.0.0.beta1

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