activerecord 5.1.0 → 5.2.3

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 (261) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +596 -450
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +5 -5
  5. data/examples/performance.rb +2 -0
  6. data/examples/simple.rb +2 -0
  7. data/lib/active_record.rb +11 -4
  8. data/lib/active_record/aggregations.rb +6 -5
  9. data/lib/active_record/association_relation.rb +7 -5
  10. data/lib/active_record/associations.rb +77 -85
  11. data/lib/active_record/associations/alias_tracker.rb +23 -32
  12. data/lib/active_record/associations/association.rb +49 -35
  13. data/lib/active_record/associations/association_scope.rb +55 -55
  14. data/lib/active_record/associations/belongs_to_association.rb +30 -11
  15. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +8 -8
  16. data/lib/active_record/associations/builder/association.rb +4 -7
  17. data/lib/active_record/associations/builder/belongs_to.rb +21 -8
  18. data/lib/active_record/associations/builder/collection_association.rb +1 -1
  19. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -1
  20. data/lib/active_record/associations/builder/has_many.rb +2 -0
  21. data/lib/active_record/associations/builder/has_one.rb +2 -0
  22. data/lib/active_record/associations/builder/singular_association.rb +2 -0
  23. data/lib/active_record/associations/collection_association.rb +66 -53
  24. data/lib/active_record/associations/collection_proxy.rb +30 -73
  25. data/lib/active_record/associations/foreign_association.rb +2 -0
  26. data/lib/active_record/associations/has_many_association.rb +13 -2
  27. data/lib/active_record/associations/has_many_through_association.rb +37 -19
  28. data/lib/active_record/associations/has_one_association.rb +14 -1
  29. data/lib/active_record/associations/has_one_through_association.rb +13 -8
  30. data/lib/active_record/associations/join_dependency.rb +52 -96
  31. data/lib/active_record/associations/join_dependency/join_association.rb +22 -75
  32. data/lib/active_record/associations/join_dependency/join_base.rb +9 -8
  33. data/lib/active_record/associations/join_dependency/join_part.rb +9 -9
  34. data/lib/active_record/associations/preloader.rb +17 -37
  35. data/lib/active_record/associations/preloader/association.rb +53 -92
  36. data/lib/active_record/associations/preloader/through_association.rb +72 -73
  37. data/lib/active_record/associations/singular_association.rb +14 -16
  38. data/lib/active_record/associations/through_association.rb +27 -12
  39. data/lib/active_record/attribute_assignment.rb +2 -5
  40. data/lib/active_record/attribute_decorators.rb +3 -2
  41. data/lib/active_record/attribute_methods.rb +65 -24
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +2 -0
  43. data/lib/active_record/attribute_methods/dirty.rb +33 -216
  44. data/lib/active_record/attribute_methods/primary_key.rb +10 -13
  45. data/lib/active_record/attribute_methods/query.rb +2 -0
  46. data/lib/active_record/attribute_methods/read.rb +9 -3
  47. data/lib/active_record/attribute_methods/serialization.rb +23 -0
  48. data/lib/active_record/attribute_methods/time_zone_conversion.rb +6 -8
  49. data/lib/active_record/attribute_methods/write.rb +22 -19
  50. data/lib/active_record/attributes.rb +7 -6
  51. data/lib/active_record/autosave_association.rb +15 -13
  52. data/lib/active_record/base.rb +2 -0
  53. data/lib/active_record/callbacks.rb +12 -6
  54. data/lib/active_record/coders/json.rb +2 -0
  55. data/lib/active_record/coders/yaml_column.rb +2 -0
  56. data/lib/active_record/collection_cache_key.rb +15 -11
  57. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +120 -39
  58. data/lib/active_record/connection_adapters/abstract/database_limits.rb +7 -0
  59. data/lib/active_record/connection_adapters/abstract/database_statements.rb +192 -37
  60. data/lib/active_record/connection_adapters/abstract/query_cache.rb +13 -2
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +15 -25
  62. data/lib/active_record/connection_adapters/abstract/savepoints.rb +2 -0
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +15 -6
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +65 -7
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +31 -53
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +158 -87
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +66 -21
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +86 -98
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +126 -189
  70. data/lib/active_record/connection_adapters/column.rb +4 -2
  71. data/lib/active_record/connection_adapters/connection_specification.rb +17 -3
  72. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +13 -2
  73. data/lib/active_record/connection_adapters/mysql/column.rb +2 -0
  74. data/lib/active_record/connection_adapters/mysql/database_statements.rb +45 -15
  75. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +2 -0
  76. data/lib/active_record/connection_adapters/mysql/quoting.rb +9 -10
  77. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +5 -3
  78. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +7 -10
  79. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +30 -23
  80. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +106 -1
  81. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +2 -0
  82. data/lib/active_record/connection_adapters/mysql2_adapter.rb +8 -2
  83. data/lib/active_record/connection_adapters/postgresql/column.rb +30 -1
  84. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +6 -32
  85. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +2 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid.rb +3 -1
  87. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +13 -1
  88. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +2 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +2 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +2 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +2 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +2 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
  95. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +2 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +2 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +3 -11
  99. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +2 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -1
  101. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +2 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +2 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +8 -2
  104. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +2 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +4 -2
  106. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +3 -1
  107. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +2 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +2 -0
  109. data/lib/active_record/connection_adapters/postgresql/quoting.rb +22 -1
  110. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +19 -25
  111. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +50 -0
  112. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +24 -11
  113. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +20 -13
  114. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +258 -129
  115. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +2 -0
  116. data/lib/active_record/connection_adapters/postgresql/utils.rb +3 -1
  117. data/lib/active_record/connection_adapters/postgresql_adapter.rb +75 -87
  118. data/lib/active_record/connection_adapters/schema_cache.rb +4 -2
  119. data/lib/active_record/connection_adapters/sql_type_metadata.rb +2 -0
  120. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +2 -0
  121. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +24 -1
  122. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +2 -0
  123. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +6 -15
  124. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +3 -2
  125. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +75 -1
  126. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +90 -96
  127. data/lib/active_record/connection_adapters/statement_pool.rb +2 -0
  128. data/lib/active_record/connection_handling.rb +4 -2
  129. data/lib/active_record/core.rb +41 -61
  130. data/lib/active_record/counter_cache.rb +20 -15
  131. data/lib/active_record/define_callbacks.rb +5 -3
  132. data/lib/active_record/dynamic_matchers.rb +9 -9
  133. data/lib/active_record/enum.rb +18 -13
  134. data/lib/active_record/errors.rb +60 -15
  135. data/lib/active_record/explain.rb +3 -1
  136. data/lib/active_record/explain_registry.rb +2 -0
  137. data/lib/active_record/explain_subscriber.rb +2 -0
  138. data/lib/active_record/fixture_set/file.rb +2 -0
  139. data/lib/active_record/fixtures.rb +67 -60
  140. data/lib/active_record/gem_version.rb +4 -2
  141. data/lib/active_record/inheritance.rb +49 -19
  142. data/lib/active_record/integration.rb +58 -19
  143. data/lib/active_record/internal_metadata.rb +2 -0
  144. data/lib/active_record/legacy_yaml_adapter.rb +3 -1
  145. data/lib/active_record/locking/optimistic.rb +30 -42
  146. data/lib/active_record/locking/pessimistic.rb +10 -7
  147. data/lib/active_record/log_subscriber.rb +46 -4
  148. data/lib/active_record/migration.rb +189 -139
  149. data/lib/active_record/migration/command_recorder.rb +11 -9
  150. data/lib/active_record/migration/compatibility.rb +81 -29
  151. data/lib/active_record/migration/join_table.rb +2 -0
  152. data/lib/active_record/model_schema.rb +74 -58
  153. data/lib/active_record/nested_attributes.rb +18 -6
  154. data/lib/active_record/no_touching.rb +3 -1
  155. data/lib/active_record/null_relation.rb +2 -0
  156. data/lib/active_record/persistence.rb +199 -54
  157. data/lib/active_record/query_cache.rb +8 -10
  158. data/lib/active_record/querying.rb +5 -3
  159. data/lib/active_record/railtie.rb +62 -6
  160. data/lib/active_record/railties/console_sandbox.rb +2 -0
  161. data/lib/active_record/railties/controller_runtime.rb +2 -0
  162. data/lib/active_record/railties/databases.rake +48 -38
  163. data/lib/active_record/readonly_attributes.rb +3 -2
  164. data/lib/active_record/reflection.rb +137 -207
  165. data/lib/active_record/relation.rb +132 -207
  166. data/lib/active_record/relation/batches.rb +32 -17
  167. data/lib/active_record/relation/batches/batch_enumerator.rb +2 -0
  168. data/lib/active_record/relation/calculations.rb +66 -25
  169. data/lib/active_record/relation/delegation.rb +45 -29
  170. data/lib/active_record/relation/finder_methods.rb +76 -85
  171. data/lib/active_record/relation/from_clause.rb +2 -8
  172. data/lib/active_record/relation/merger.rb +53 -23
  173. data/lib/active_record/relation/predicate_builder.rb +60 -79
  174. data/lib/active_record/relation/predicate_builder/array_handler.rb +10 -7
  175. data/lib/active_record/relation/predicate_builder/association_query_value.rb +46 -0
  176. data/lib/active_record/relation/predicate_builder/base_handler.rb +2 -2
  177. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +12 -1
  178. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +56 -0
  179. data/lib/active_record/relation/predicate_builder/range_handler.rb +26 -9
  180. data/lib/active_record/relation/predicate_builder/relation_handler.rb +6 -0
  181. data/lib/active_record/relation/query_attribute.rb +28 -2
  182. data/lib/active_record/relation/query_methods.rb +135 -103
  183. data/lib/active_record/relation/record_fetch_warning.rb +2 -0
  184. data/lib/active_record/relation/spawn_methods.rb +4 -2
  185. data/lib/active_record/relation/where_clause.rb +65 -67
  186. data/lib/active_record/relation/where_clause_factory.rb +5 -48
  187. data/lib/active_record/result.rb +2 -0
  188. data/lib/active_record/runtime_registry.rb +2 -0
  189. data/lib/active_record/sanitization.rb +129 -121
  190. data/lib/active_record/schema.rb +4 -2
  191. data/lib/active_record/schema_dumper.rb +36 -26
  192. data/lib/active_record/schema_migration.rb +2 -0
  193. data/lib/active_record/scoping.rb +12 -10
  194. data/lib/active_record/scoping/default.rb +10 -7
  195. data/lib/active_record/scoping/named.rb +40 -12
  196. data/lib/active_record/secure_token.rb +2 -0
  197. data/lib/active_record/serialization.rb +2 -0
  198. data/lib/active_record/statement_cache.rb +22 -12
  199. data/lib/active_record/store.rb +3 -1
  200. data/lib/active_record/suppressor.rb +2 -0
  201. data/lib/active_record/table_metadata.rb +12 -3
  202. data/lib/active_record/tasks/database_tasks.rb +38 -26
  203. data/lib/active_record/tasks/mysql_database_tasks.rb +11 -50
  204. data/lib/active_record/tasks/postgresql_database_tasks.rb +11 -3
  205. data/lib/active_record/tasks/sqlite_database_tasks.rb +25 -3
  206. data/lib/active_record/timestamp.rb +13 -6
  207. data/lib/active_record/touch_later.rb +2 -0
  208. data/lib/active_record/transactions.rb +32 -27
  209. data/lib/active_record/translation.rb +2 -0
  210. data/lib/active_record/type.rb +4 -1
  211. data/lib/active_record/type/adapter_specific_registry.rb +2 -0
  212. data/lib/active_record/type/date.rb +2 -0
  213. data/lib/active_record/type/date_time.rb +2 -0
  214. data/lib/active_record/type/decimal_without_scale.rb +2 -0
  215. data/lib/active_record/type/hash_lookup_type_map.rb +2 -0
  216. data/lib/active_record/type/internal/timezone.rb +2 -0
  217. data/lib/active_record/type/json.rb +30 -0
  218. data/lib/active_record/type/serialized.rb +6 -0
  219. data/lib/active_record/type/text.rb +2 -0
  220. data/lib/active_record/type/time.rb +2 -0
  221. data/lib/active_record/type/type_map.rb +2 -0
  222. data/lib/active_record/type/unsigned_integer.rb +2 -0
  223. data/lib/active_record/type_caster.rb +2 -0
  224. data/lib/active_record/type_caster/connection.rb +2 -0
  225. data/lib/active_record/type_caster/map.rb +3 -1
  226. data/lib/active_record/validations.rb +2 -0
  227. data/lib/active_record/validations/absence.rb +2 -0
  228. data/lib/active_record/validations/associated.rb +2 -0
  229. data/lib/active_record/validations/length.rb +2 -0
  230. data/lib/active_record/validations/presence.rb +2 -0
  231. data/lib/active_record/validations/uniqueness.rb +36 -6
  232. data/lib/active_record/version.rb +2 -0
  233. data/lib/rails/generators/active_record.rb +3 -1
  234. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
  235. data/lib/rails/generators/active_record/{model/templates/application_record.rb → application_record/templates/application_record.rb.tt} +0 -0
  236. data/lib/rails/generators/active_record/migration.rb +2 -0
  237. data/lib/rails/generators/active_record/migration/migration_generator.rb +3 -1
  238. data/lib/rails/generators/active_record/migration/templates/{create_table_migration.rb → create_table_migration.rb.tt} +0 -0
  239. data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +0 -0
  240. data/lib/rails/generators/active_record/model/model_generator.rb +2 -23
  241. data/lib/rails/generators/active_record/model/templates/{model.rb → model.rb.tt} +0 -0
  242. data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
  243. metadata +24 -36
  244. data/lib/active_record/associations/preloader/belongs_to.rb +0 -15
  245. data/lib/active_record/associations/preloader/collection_association.rb +0 -17
  246. data/lib/active_record/associations/preloader/has_many.rb +0 -15
  247. data/lib/active_record/associations/preloader/has_many_through.rb +0 -19
  248. data/lib/active_record/associations/preloader/has_one.rb +0 -15
  249. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  250. data/lib/active_record/associations/preloader/singular_association.rb +0 -18
  251. data/lib/active_record/attribute.rb +0 -240
  252. data/lib/active_record/attribute/user_provided_default.rb +0 -30
  253. data/lib/active_record/attribute_mutation_tracker.rb +0 -113
  254. data/lib/active_record/attribute_set.rb +0 -113
  255. data/lib/active_record/attribute_set/builder.rb +0 -124
  256. data/lib/active_record/attribute_set/yaml_encoder.rb +0 -41
  257. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +0 -10
  258. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  259. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +0 -88
  260. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +0 -59
  261. data/lib/active_record/type/internal/abstract_json.rb +0 -33
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  class Relation
3
5
  module RecordFetchWarning
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "active_support/core_ext/hash/except"
2
4
  require "active_support/core_ext/hash/slice"
3
5
  require "active_record/relation/merger"
@@ -6,7 +8,7 @@ module ActiveRecord
6
8
  module SpawnMethods
7
9
  # This is overridden by Associations::CollectionProxy
8
10
  def spawn #:nodoc:
9
- clone
11
+ @delegate_to_klass ? klass.all : clone
10
12
  end
11
13
 
12
14
  # Merges in the conditions from <tt>other</tt>, if <tt>other</tt> is an ActiveRecord::Relation.
@@ -67,7 +69,7 @@ module ActiveRecord
67
69
  private
68
70
 
69
71
  def relation_with(values)
70
- result = Relation.create(klass, table, predicate_builder, values)
72
+ result = Relation.create(klass, values: values)
71
73
  result.extend(*extending_values) if extending_values.any?
72
74
  result
73
75
  end
@@ -1,65 +1,63 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  class Relation
3
5
  class WhereClause # :nodoc:
4
- attr_reader :binds
5
-
6
6
  delegate :any?, :empty?, to: :predicates
7
7
 
8
- def initialize(predicates, binds)
8
+ def initialize(predicates)
9
9
  @predicates = predicates
10
- @binds = binds
11
10
  end
12
11
 
13
12
  def +(other)
14
13
  WhereClause.new(
15
14
  predicates + other.predicates,
16
- binds + other.binds,
15
+ )
16
+ end
17
+
18
+ def -(other)
19
+ WhereClause.new(
20
+ predicates - other.predicates,
17
21
  )
18
22
  end
19
23
 
20
24
  def merge(other)
21
25
  WhereClause.new(
22
26
  predicates_unreferenced_by(other) + other.predicates,
23
- non_conflicting_binds(other) + other.binds,
24
27
  )
25
28
  end
26
29
 
27
30
  def except(*columns)
28
- WhereClause.new(*except_predicates_and_binds(columns))
31
+ WhereClause.new(except_predicates(columns))
29
32
  end
30
33
 
31
34
  def or(other)
32
- if empty?
33
- self
34
- elsif other.empty?
35
- other
35
+ left = self - other
36
+ common = self - left
37
+ right = other - common
38
+
39
+ if left.empty? || right.empty?
40
+ common
36
41
  else
37
- WhereClause.new(
38
- [ast.or(other.ast)],
39
- binds + other.binds
42
+ or_clause = WhereClause.new(
43
+ [left.ast.or(right.ast)],
40
44
  )
45
+ common + or_clause
41
46
  end
42
47
  end
43
48
 
44
49
  def to_h(table_name = nil)
45
- equalities = predicates.grep(Arel::Nodes::Equality)
50
+ equalities = equalities(predicates)
46
51
  if table_name
47
52
  equalities = equalities.select do |node|
48
53
  node.left.relation.name == table_name
49
54
  end
50
55
  end
51
56
 
52
- binds = self.binds.map { |attr| [attr.name, attr.value] }.to_h
53
-
54
57
  equalities.map { |node|
55
- name = node.left.name
56
- [name, binds.fetch(name.to_s) {
57
- case node.right
58
- when Array then node.right.map(&:val)
59
- when Arel::Nodes::Casted, Arel::Nodes::Quoted
60
- node.right.val
61
- end
62
- }]
58
+ name = node.left.name.to_s
59
+ value = extract_node_value(node.right)
60
+ [name, value]
63
61
  }.to_h
64
62
  end
65
63
 
@@ -69,20 +67,17 @@ module ActiveRecord
69
67
 
70
68
  def ==(other)
71
69
  other.is_a?(WhereClause) &&
72
- predicates == other.predicates &&
73
- binds == other.binds
70
+ predicates == other.predicates
74
71
  end
75
72
 
76
73
  def invert
77
- WhereClause.new(inverted_predicates, binds)
74
+ WhereClause.new(inverted_predicates)
78
75
  end
79
76
 
80
77
  def self.empty
81
- @empty ||= new([], [])
78
+ @empty ||= new([])
82
79
  end
83
80
 
84
- # TODO Change this to private once we've dropped Ruby 2.2 support.
85
- # Workaround for Ruby 2.2 "private attribute?" warning.
86
81
  protected
87
82
 
88
83
  attr_reader :predicates
@@ -95,6 +90,20 @@ module ActiveRecord
95
90
  end
96
91
 
97
92
  private
93
+ def equalities(predicates)
94
+ equalities = []
95
+
96
+ predicates.each do |node|
97
+ case node
98
+ when Arel::Nodes::Equality
99
+ equalities << node
100
+ when Arel::Nodes::And
101
+ equalities.concat equalities(node.children)
102
+ end
103
+ end
104
+
105
+ equalities
106
+ end
98
107
 
99
108
  def predicates_unreferenced_by(other)
100
109
  predicates.reject do |n|
@@ -106,12 +115,6 @@ module ActiveRecord
106
115
  node.respond_to?(:operator) && node.operator == :==
107
116
  end
108
117
 
109
- def non_conflicting_binds(other)
110
- conflicts = referenced_columns & other.referenced_columns
111
- conflicts.map! { |node| node.name.to_s }
112
- binds.reject { |attr| conflicts.include?(attr.name) }
113
- end
114
-
115
118
  def inverted_predicates
116
119
  predicates.map { |node| invert_predicate(node) }
117
120
  end
@@ -131,43 +134,22 @@ module ActiveRecord
131
134
  end
132
135
  end
133
136
 
134
- def except_predicates_and_binds(columns)
135
- except_binds = []
136
- binds_index = 0
137
-
138
- predicates = self.predicates.reject do |node|
139
- except = \
140
- case node
141
- 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
142
- binds_contains = node.grep(Arel::Nodes::BindParam).size
143
- subrelation = (node.left.kind_of?(Arel::Attributes::Attribute) ? node.left : node.right)
144
- columns.include?(subrelation.name.to_s)
145
- end
146
-
147
- if except && binds_contains > 0
148
- (binds_index...(binds_index + binds_contains)).each do |i|
149
- except_binds[i] = true
150
- end
137
+ def except_predicates(columns)
138
+ predicates.reject do |node|
139
+ case node
140
+ 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
141
+ subrelation = (node.left.kind_of?(Arel::Attributes::Attribute) ? node.left : node.right)
142
+ columns.include?(subrelation.name.to_s)
151
143
  end
152
-
153
- binds_index += binds_contains if binds_contains
154
-
155
- except
156
144
  end
157
-
158
- binds = self.binds.reject.with_index do |_, i|
159
- except_binds[i]
160
- end
161
-
162
- [predicates, binds]
163
145
  end
164
146
 
165
147
  def predicates_with_wrapped_sql_literals
166
148
  non_empty_predicates.map do |node|
167
- if Arel::Nodes::Equality === node
168
- node
169
- else
149
+ case node
150
+ when Arel::Nodes::SqlLiteral, ::String
170
151
  wrap_sql_literal(node)
152
+ else node
171
153
  end
172
154
  end
173
155
  end
@@ -183,6 +165,22 @@ module ActiveRecord
183
165
  end
184
166
  Arel::Nodes::Grouping.new(node)
185
167
  end
168
+
169
+ def extract_node_value(node)
170
+ case node
171
+ when Array
172
+ node.map { |v| extract_node_value(v) }
173
+ when Arel::Nodes::Casted, Arel::Nodes::Quoted
174
+ node.val
175
+ when Arel::Nodes::BindParam
176
+ value = node.value
177
+ if value.respond_to?(:value_before_type_cast)
178
+ value.value_before_type_cast
179
+ else
180
+ value
181
+ end
182
+ end
183
+ end
186
184
  end
187
185
  end
188
186
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  class Relation
3
5
  class WhereClauseFactory # :nodoc:
@@ -9,69 +11,24 @@ module ActiveRecord
9
11
  def build(opts, other)
10
12
  case opts
11
13
  when String, Array
12
- parts = [klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))]
14
+ parts = [klass.sanitize_sql(other.empty? ? opts : ([opts] + other))]
13
15
  when Hash
14
16
  attributes = predicate_builder.resolve_column_aliases(opts)
15
- attributes = klass.send(:expand_hash_conditions_for_aggregates, attributes)
16
17
  attributes.stringify_keys!
17
18
 
18
- if perform_case_sensitive?(options = other.last)
19
- parts, binds = build_for_case_sensitive(attributes, options)
20
- else
21
- attributes, binds = predicate_builder.create_binds(attributes)
22
- parts = predicate_builder.build_from_hash(attributes)
23
- end
19
+ parts = predicate_builder.build_from_hash(attributes)
24
20
  when Arel::Nodes::Node
25
21
  parts = [opts]
26
22
  else
27
23
  raise ArgumentError, "Unsupported argument type: #{opts} (#{opts.class})"
28
24
  end
29
25
 
30
- WhereClause.new(parts, binds || [])
26
+ WhereClause.new(parts)
31
27
  end
32
28
 
33
- # TODO Change this to private once we've dropped Ruby 2.2 support.
34
- # Workaround for Ruby 2.2 "private attribute?" warning.
35
29
  protected
36
30
 
37
31
  attr_reader :klass, :predicate_builder
38
-
39
- private
40
-
41
- def perform_case_sensitive?(options)
42
- options && options.key?(:case_sensitive)
43
- end
44
-
45
- def build_for_case_sensitive(attributes, options)
46
- parts, binds = [], []
47
- table = klass.arel_table
48
-
49
- attributes.each do |attribute, value|
50
- if reflection = klass._reflect_on_association(attribute)
51
- attribute = reflection.foreign_key.to_s
52
- value = value[reflection.klass.primary_key] unless value.nil?
53
- end
54
-
55
- if value.nil?
56
- parts << table[attribute].eq(value)
57
- else
58
- column = klass.column_for_attribute(attribute)
59
-
60
- binds << predicate_builder.send(:build_bind_param, attribute, value)
61
- value = Arel::Nodes::BindParam.new
62
-
63
- predicate = if options[:case_sensitive]
64
- klass.connection.case_sensitive_comparison(table, attribute, column, value)
65
- else
66
- klass.connection.case_insensitive_comparison(table, attribute, column, value)
67
- end
68
-
69
- parts << predicate
70
- end
71
- end
72
-
73
- [parts, binds]
74
- end
75
32
  end
76
33
  end
77
34
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  ###
3
5
  # This class encapsulates a result returned from calling
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "active_support/per_thread_registry"
2
4
 
3
5
  module ActiveRecord
@@ -1,74 +1,139 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module Sanitization
3
5
  extend ActiveSupport::Concern
4
6
 
5
7
  module ClassMethods
6
- private
8
+ # Accepts an array or string of SQL conditions and sanitizes
9
+ # them into a valid SQL fragment for a WHERE clause.
10
+ #
11
+ # sanitize_sql_for_conditions(["name=? and group_id=?", "foo'bar", 4])
12
+ # # => "name='foo''bar' and group_id=4"
13
+ #
14
+ # sanitize_sql_for_conditions(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4])
15
+ # # => "name='foo''bar' and group_id='4'"
16
+ #
17
+ # sanitize_sql_for_conditions(["name='%s' and group_id='%s'", "foo'bar", 4])
18
+ # # => "name='foo''bar' and group_id='4'"
19
+ #
20
+ # sanitize_sql_for_conditions("name='foo''bar' and group_id='4'")
21
+ # # => "name='foo''bar' and group_id='4'"
22
+ def sanitize_sql_for_conditions(condition)
23
+ return nil if condition.blank?
7
24
 
8
- # Accepts an array or string of SQL conditions and sanitizes
9
- # them into a valid SQL fragment for a WHERE clause.
10
- #
11
- # sanitize_sql_for_conditions(["name=? and group_id=?", "foo'bar", 4])
12
- # # => "name='foo''bar' and group_id=4"
13
- #
14
- # sanitize_sql_for_conditions(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4])
15
- # # => "name='foo''bar' and group_id='4'"
16
- #
17
- # sanitize_sql_for_conditions(["name='%s' and group_id='%s'", "foo'bar", 4])
18
- # # => "name='foo''bar' and group_id='4'"
19
- #
20
- # sanitize_sql_for_conditions("name='foo''bar' and group_id='4'")
21
- # # => "name='foo''bar' and group_id='4'"
22
- def sanitize_sql_for_conditions(condition) # :doc:
23
- return nil if condition.blank?
24
-
25
- case condition
26
- when Array; sanitize_sql_array(condition)
27
- else condition
28
- end
25
+ case condition
26
+ when Array; sanitize_sql_array(condition)
27
+ else condition
29
28
  end
30
- alias :sanitize_sql :sanitize_sql_for_conditions
31
- alias :sanitize_conditions :sanitize_sql
32
- deprecate sanitize_conditions: :sanitize_sql
29
+ end
30
+ alias :sanitize_sql :sanitize_sql_for_conditions
33
31
 
34
- # Accepts an array, hash, or string of SQL conditions and sanitizes
35
- # them into a valid SQL fragment for a SET clause.
36
- #
37
- # sanitize_sql_for_assignment(["name=? and group_id=?", nil, 4])
38
- # # => "name=NULL and group_id=4"
39
- #
40
- # sanitize_sql_for_assignment(["name=:name and group_id=:group_id", name: nil, group_id: 4])
41
- # # => "name=NULL and group_id=4"
42
- #
43
- # Post.send(:sanitize_sql_for_assignment, { name: nil, group_id: 4 })
44
- # # => "`posts`.`name` = NULL, `posts`.`group_id` = 4"
45
- #
46
- # sanitize_sql_for_assignment("name=NULL and group_id='4'")
47
- # # => "name=NULL and group_id='4'"
48
- def sanitize_sql_for_assignment(assignments, default_table_name = table_name) # :doc:
49
- case assignments
50
- when Array; sanitize_sql_array(assignments)
51
- when Hash; sanitize_sql_hash_for_assignment(assignments, default_table_name)
52
- else assignments
53
- end
32
+ # Accepts an array, hash, or string of SQL conditions and sanitizes
33
+ # them into a valid SQL fragment for a SET clause.
34
+ #
35
+ # sanitize_sql_for_assignment(["name=? and group_id=?", nil, 4])
36
+ # # => "name=NULL and group_id=4"
37
+ #
38
+ # sanitize_sql_for_assignment(["name=:name and group_id=:group_id", name: nil, group_id: 4])
39
+ # # => "name=NULL and group_id=4"
40
+ #
41
+ # Post.sanitize_sql_for_assignment({ name: nil, group_id: 4 })
42
+ # # => "`posts`.`name` = NULL, `posts`.`group_id` = 4"
43
+ #
44
+ # sanitize_sql_for_assignment("name=NULL and group_id='4'")
45
+ # # => "name=NULL and group_id='4'"
46
+ def sanitize_sql_for_assignment(assignments, default_table_name = table_name)
47
+ case assignments
48
+ when Array; sanitize_sql_array(assignments)
49
+ when Hash; sanitize_sql_hash_for_assignment(assignments, default_table_name)
50
+ else assignments
54
51
  end
52
+ end
55
53
 
56
- # Accepts an array, or string of SQL conditions and sanitizes
57
- # them into a valid SQL fragment for an ORDER clause.
58
- #
59
- # sanitize_sql_for_order(["field(id, ?)", [1,3,2]])
60
- # # => "field(id, 1,3,2)"
61
- #
62
- # sanitize_sql_for_order("id ASC")
63
- # # => "id ASC"
64
- def sanitize_sql_for_order(condition) # :doc:
65
- if condition.is_a?(Array) && condition.first.to_s.include?("?")
66
- sanitize_sql_array(condition)
67
- else
68
- condition
54
+ # Accepts an array, or string of SQL conditions and sanitizes
55
+ # them into a valid SQL fragment for an ORDER clause.
56
+ #
57
+ # sanitize_sql_for_order(["field(id, ?)", [1,3,2]])
58
+ # # => "field(id, 1,3,2)"
59
+ #
60
+ # sanitize_sql_for_order("id ASC")
61
+ # # => "id ASC"
62
+ def sanitize_sql_for_order(condition)
63
+ if condition.is_a?(Array) && condition.first.to_s.include?("?")
64
+ enforce_raw_sql_whitelist([condition.first],
65
+ whitelist: AttributeMethods::ClassMethods::COLUMN_NAME_ORDER_WHITELIST
66
+ )
67
+
68
+ # Ensure we aren't dealing with a subclass of String that might
69
+ # override methods we use (eg. Arel::Nodes::SqlLiteral).
70
+ if condition.first.kind_of?(String) && !condition.first.instance_of?(String)
71
+ condition = [String.new(condition.first), *condition[1..-1]]
69
72
  end
73
+
74
+ Arel.sql(sanitize_sql_array(condition))
75
+ else
76
+ condition
77
+ end
78
+ end
79
+
80
+ # Sanitizes a hash of attribute/value pairs into SQL conditions for a SET clause.
81
+ #
82
+ # sanitize_sql_hash_for_assignment({ status: nil, group_id: 1 }, "posts")
83
+ # # => "`posts`.`status` = NULL, `posts`.`group_id` = 1"
84
+ def sanitize_sql_hash_for_assignment(attrs, table)
85
+ c = connection
86
+ attrs.map do |attr, value|
87
+ type = type_for_attribute(attr)
88
+ value = type.serialize(type.cast(value))
89
+ "#{c.quote_table_name_for_assignment(table, attr)} = #{c.quote(value)}"
90
+ end.join(", ")
91
+ end
92
+
93
+ # Sanitizes a +string+ so that it is safe to use within an SQL
94
+ # LIKE statement. This method uses +escape_character+ to escape all occurrences of "\", "_" and "%".
95
+ #
96
+ # sanitize_sql_like("100%")
97
+ # # => "100\\%"
98
+ #
99
+ # sanitize_sql_like("snake_cased_string")
100
+ # # => "snake\\_cased\\_string"
101
+ #
102
+ # sanitize_sql_like("100%", "!")
103
+ # # => "100!%"
104
+ #
105
+ # sanitize_sql_like("snake_cased_string", "!")
106
+ # # => "snake!_cased!_string"
107
+ def sanitize_sql_like(string, escape_character = "\\")
108
+ pattern = Regexp.union(escape_character, "%", "_")
109
+ string.gsub(pattern) { |x| [escape_character, x].join }
110
+ end
111
+
112
+ # Accepts an array of conditions. The array has each value
113
+ # sanitized and interpolated into the SQL statement.
114
+ #
115
+ # sanitize_sql_array(["name=? and group_id=?", "foo'bar", 4])
116
+ # # => "name='foo''bar' and group_id=4"
117
+ #
118
+ # sanitize_sql_array(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4])
119
+ # # => "name='foo''bar' and group_id=4"
120
+ #
121
+ # sanitize_sql_array(["name='%s' and group_id='%s'", "foo'bar", 4])
122
+ # # => "name='foo''bar' and group_id='4'"
123
+ def sanitize_sql_array(ary)
124
+ statement, *values = ary
125
+ if values.first.is_a?(Hash) && /:\w+/.match?(statement)
126
+ replace_named_bind_variables(statement, values.first)
127
+ elsif statement.include?("?")
128
+ replace_bind_variables(statement, values)
129
+ elsif statement.blank?
130
+ statement
131
+ else
132
+ statement % values.collect { |value| connection.quote_string(value.to_s) }
70
133
  end
134
+ end
71
135
 
136
+ private
72
137
  # Accepts a hash of SQL conditions and replaces those attributes
73
138
  # that correspond to a {#composed_of}[rdoc-ref:Aggregations::ClassMethods#composed_of]
74
139
  # relationship with their expanded aggregate attribute values.
@@ -90,10 +155,12 @@ module ActiveRecord
90
155
  if aggregation = reflect_on_aggregation(attr.to_sym)
91
156
  mapping = aggregation.mapping
92
157
  mapping.each do |field_attr, aggregate_attr|
93
- if mapping.size == 1 && !value.respond_to?(aggregate_attr)
94
- expanded_attrs[field_attr] = value
158
+ expanded_attrs[field_attr] = if value.is_a?(Array)
159
+ value.map { |it| it.send(aggregate_attr) }
160
+ elsif mapping.size == 1 && !value.respond_to?(aggregate_attr)
161
+ value
95
162
  else
96
- expanded_attrs[field_attr] = value.send(aggregate_attr)
163
+ value.send(aggregate_attr)
97
164
  end
98
165
  end
99
166
  else
@@ -102,61 +169,7 @@ module ActiveRecord
102
169
  end
103
170
  expanded_attrs
104
171
  end
105
-
106
- # Sanitizes a hash of attribute/value pairs into SQL conditions for a SET clause.
107
- #
108
- # sanitize_sql_hash_for_assignment({ status: nil, group_id: 1 }, "posts")
109
- # # => "`posts`.`status` = NULL, `posts`.`group_id` = 1"
110
- def sanitize_sql_hash_for_assignment(attrs, table) # :doc:
111
- c = connection
112
- attrs.map do |attr, value|
113
- value = type_for_attribute(attr.to_s).serialize(value)
114
- "#{c.quote_table_name_for_assignment(table, attr)} = #{c.quote(value)}"
115
- end.join(", ")
116
- end
117
-
118
- # Sanitizes a +string+ so that it is safe to use within an SQL
119
- # LIKE statement. This method uses +escape_character+ to escape all occurrences of "\", "_" and "%".
120
- #
121
- # sanitize_sql_like("100%")
122
- # # => "100\\%"
123
- #
124
- # sanitize_sql_like("snake_cased_string")
125
- # # => "snake\\_cased\\_string"
126
- #
127
- # sanitize_sql_like("100%", "!")
128
- # # => "100!%"
129
- #
130
- # sanitize_sql_like("snake_cased_string", "!")
131
- # # => "snake!_cased!_string"
132
- def sanitize_sql_like(string, escape_character = "\\") # :doc:
133
- pattern = Regexp.union(escape_character, "%", "_")
134
- string.gsub(pattern) { |x| [escape_character, x].join }
135
- end
136
-
137
- # Accepts an array of conditions. The array has each value
138
- # sanitized and interpolated into the SQL statement.
139
- #
140
- # sanitize_sql_array(["name=? and group_id=?", "foo'bar", 4])
141
- # # => "name='foo''bar' and group_id=4"
142
- #
143
- # sanitize_sql_array(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4])
144
- # # => "name='foo''bar' and group_id=4"
145
- #
146
- # sanitize_sql_array(["name='%s' and group_id='%s'", "foo'bar", 4])
147
- # # => "name='foo''bar' and group_id='4'"
148
- def sanitize_sql_array(ary) # :doc:
149
- statement, *values = ary
150
- if values.first.is_a?(Hash) && /:\w+/.match?(statement)
151
- replace_named_bind_variables(statement, values.first)
152
- elsif statement.include?("?")
153
- replace_bind_variables(statement, values)
154
- elsif statement.blank?
155
- statement
156
- else
157
- statement % values.collect { |value| connection.quote_string(value.to_s) }
158
- end
159
- end
172
+ deprecate :expand_hash_conditions_for_aggregates
160
173
 
161
174
  def replace_bind_variables(statement, values)
162
175
  raise_if_bind_arity_mismatch(statement, statement.count("?"), values.size)
@@ -205,10 +218,5 @@ module ActiveRecord
205
218
  end
206
219
  end
207
220
  end
208
-
209
- def quoted_id # :nodoc:
210
- self.class.connection.quote(@attributes[self.class.primary_key].value_for_database)
211
- end
212
- deprecate :quoted_id
213
221
  end
214
222
  end