activerecord 4.2.11.3 → 5.0.7.2

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 (251) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1638 -1132
  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.rb +7 -2
  8. data/lib/active_record/aggregations.rb +34 -21
  9. data/lib/active_record/association_relation.rb +7 -4
  10. data/lib/active_record/associations.rb +347 -218
  11. data/lib/active_record/associations/alias_tracker.rb +19 -16
  12. data/lib/active_record/associations/association.rb +22 -10
  13. data/lib/active_record/associations/association_scope.rb +75 -104
  14. data/lib/active_record/associations/belongs_to_association.rb +21 -32
  15. data/lib/active_record/associations/builder/association.rb +28 -34
  16. data/lib/active_record/associations/builder/belongs_to.rb +43 -18
  17. data/lib/active_record/associations/builder/collection_association.rb +7 -19
  18. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +16 -11
  19. data/lib/active_record/associations/builder/has_many.rb +4 -4
  20. data/lib/active_record/associations/builder/has_one.rb +11 -6
  21. data/lib/active_record/associations/builder/singular_association.rb +13 -11
  22. data/lib/active_record/associations/collection_association.rb +85 -69
  23. data/lib/active_record/associations/collection_proxy.rb +104 -46
  24. data/lib/active_record/associations/foreign_association.rb +1 -1
  25. data/lib/active_record/associations/has_many_association.rb +21 -78
  26. data/lib/active_record/associations/has_many_through_association.rb +6 -47
  27. data/lib/active_record/associations/has_one_association.rb +12 -5
  28. data/lib/active_record/associations/join_dependency.rb +38 -22
  29. data/lib/active_record/associations/join_dependency/join_association.rb +15 -14
  30. data/lib/active_record/associations/join_dependency/join_part.rb +2 -2
  31. data/lib/active_record/associations/preloader.rb +14 -4
  32. data/lib/active_record/associations/preloader/association.rb +52 -71
  33. data/lib/active_record/associations/preloader/collection_association.rb +0 -7
  34. data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
  35. data/lib/active_record/associations/preloader/has_one.rb +0 -8
  36. data/lib/active_record/associations/preloader/singular_association.rb +0 -1
  37. data/lib/active_record/associations/preloader/through_association.rb +36 -17
  38. data/lib/active_record/associations/singular_association.rb +13 -1
  39. data/lib/active_record/associations/through_association.rb +12 -4
  40. data/lib/active_record/attribute.rb +69 -19
  41. data/lib/active_record/attribute/user_provided_default.rb +28 -0
  42. data/lib/active_record/attribute_assignment.rb +19 -140
  43. data/lib/active_record/attribute_decorators.rb +6 -5
  44. data/lib/active_record/attribute_methods.rb +69 -44
  45. data/lib/active_record/attribute_methods/before_type_cast.rb +1 -1
  46. data/lib/active_record/attribute_methods/dirty.rb +46 -86
  47. data/lib/active_record/attribute_methods/primary_key.rb +16 -3
  48. data/lib/active_record/attribute_methods/query.rb +2 -2
  49. data/lib/active_record/attribute_methods/read.rb +31 -59
  50. data/lib/active_record/attribute_methods/serialization.rb +13 -16
  51. data/lib/active_record/attribute_methods/time_zone_conversion.rb +61 -14
  52. data/lib/active_record/attribute_methods/write.rb +13 -37
  53. data/lib/active_record/attribute_mutation_tracker.rb +70 -0
  54. data/lib/active_record/attribute_set.rb +32 -3
  55. data/lib/active_record/attribute_set/builder.rb +42 -16
  56. data/lib/active_record/attributes.rb +199 -81
  57. data/lib/active_record/autosave_association.rb +54 -17
  58. data/lib/active_record/base.rb +32 -23
  59. data/lib/active_record/callbacks.rb +39 -43
  60. data/lib/active_record/coders/json.rb +1 -1
  61. data/lib/active_record/coders/yaml_column.rb +20 -8
  62. data/lib/active_record/collection_cache_key.rb +50 -0
  63. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +467 -189
  64. data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -3
  65. data/lib/active_record/connection_adapters/abstract/database_statements.rb +66 -62
  66. data/lib/active_record/connection_adapters/abstract/query_cache.rb +39 -4
  67. data/lib/active_record/connection_adapters/abstract/quoting.rb +86 -13
  68. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  69. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +61 -39
  70. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +236 -188
  71. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +72 -17
  72. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +407 -156
  73. data/lib/active_record/connection_adapters/abstract/transaction.rb +51 -34
  74. data/lib/active_record/connection_adapters/abstract_adapter.rb +177 -71
  75. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +433 -399
  76. data/lib/active_record/connection_adapters/column.rb +28 -43
  77. data/lib/active_record/connection_adapters/connection_specification.rb +15 -27
  78. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
  79. data/lib/active_record/connection_adapters/mysql/column.rb +50 -0
  80. data/lib/active_record/connection_adapters/mysql/database_statements.rb +108 -0
  81. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +70 -0
  82. data/lib/active_record/connection_adapters/mysql/quoting.rb +51 -0
  83. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +67 -0
  84. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +93 -0
  85. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +54 -0
  86. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +32 -0
  87. data/lib/active_record/connection_adapters/mysql2_adapter.rb +25 -166
  88. data/lib/active_record/connection_adapters/postgresql/column.rb +33 -11
  89. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +18 -72
  90. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid.rb +1 -6
  92. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +37 -57
  93. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +3 -3
  94. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +2 -2
  95. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -1
  96. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +7 -22
  97. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +13 -3
  98. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +1 -26
  99. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +2 -2
  100. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +0 -4
  101. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +4 -4
  102. data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +31 -17
  104. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +0 -4
  105. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +2 -2
  106. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +1 -1
  107. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +1 -1
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +56 -19
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +29 -10
  110. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -79
  111. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +47 -0
  112. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +250 -154
  113. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
  114. data/lib/active_record/connection_adapters/postgresql/utils.rb +2 -2
  115. data/lib/active_record/connection_adapters/postgresql_adapter.rb +264 -170
  116. data/lib/active_record/connection_adapters/schema_cache.rb +36 -23
  117. data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
  118. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +19 -0
  119. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +48 -0
  120. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  121. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +151 -194
  122. data/lib/active_record/connection_adapters/statement_pool.rb +31 -12
  123. data/lib/active_record/connection_handling.rb +37 -14
  124. data/lib/active_record/core.rb +92 -108
  125. data/lib/active_record/counter_cache.rb +13 -24
  126. data/lib/active_record/dynamic_matchers.rb +1 -20
  127. data/lib/active_record/enum.rb +116 -76
  128. data/lib/active_record/errors.rb +87 -48
  129. data/lib/active_record/explain.rb +20 -9
  130. data/lib/active_record/explain_registry.rb +1 -1
  131. data/lib/active_record/explain_subscriber.rb +1 -1
  132. data/lib/active_record/fixture_set/file.rb +26 -5
  133. data/lib/active_record/fixtures.rb +77 -41
  134. data/lib/active_record/gem_version.rb +4 -4
  135. data/lib/active_record/inheritance.rb +32 -40
  136. data/lib/active_record/integration.rb +17 -14
  137. data/lib/active_record/internal_metadata.rb +56 -0
  138. data/lib/active_record/legacy_yaml_adapter.rb +18 -2
  139. data/lib/active_record/locale/en.yml +3 -2
  140. data/lib/active_record/locking/optimistic.rb +15 -15
  141. data/lib/active_record/locking/pessimistic.rb +1 -1
  142. data/lib/active_record/log_subscriber.rb +48 -24
  143. data/lib/active_record/migration.rb +362 -111
  144. data/lib/active_record/migration/command_recorder.rb +59 -18
  145. data/lib/active_record/migration/compatibility.rb +126 -0
  146. data/lib/active_record/model_schema.rb +270 -73
  147. data/lib/active_record/nested_attributes.rb +58 -29
  148. data/lib/active_record/no_touching.rb +4 -0
  149. data/lib/active_record/null_relation.rb +16 -8
  150. data/lib/active_record/persistence.rb +152 -90
  151. data/lib/active_record/query_cache.rb +18 -23
  152. data/lib/active_record/querying.rb +12 -11
  153. data/lib/active_record/railtie.rb +23 -16
  154. data/lib/active_record/railties/controller_runtime.rb +1 -1
  155. data/lib/active_record/railties/databases.rake +52 -41
  156. data/lib/active_record/readonly_attributes.rb +1 -1
  157. data/lib/active_record/reflection.rb +302 -115
  158. data/lib/active_record/relation.rb +187 -120
  159. data/lib/active_record/relation/batches.rb +141 -36
  160. data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
  161. data/lib/active_record/relation/calculations.rb +92 -117
  162. data/lib/active_record/relation/delegation.rb +8 -20
  163. data/lib/active_record/relation/finder_methods.rb +173 -89
  164. data/lib/active_record/relation/from_clause.rb +32 -0
  165. data/lib/active_record/relation/merger.rb +16 -42
  166. data/lib/active_record/relation/predicate_builder.rb +120 -107
  167. data/lib/active_record/relation/predicate_builder/array_handler.rb +11 -16
  168. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +88 -0
  169. data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
  170. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
  171. data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
  172. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +57 -0
  173. data/lib/active_record/relation/predicate_builder/range_handler.rb +33 -0
  174. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  175. data/lib/active_record/relation/query_attribute.rb +19 -0
  176. data/lib/active_record/relation/query_methods.rb +308 -244
  177. data/lib/active_record/relation/record_fetch_warning.rb +49 -0
  178. data/lib/active_record/relation/spawn_methods.rb +4 -7
  179. data/lib/active_record/relation/where_clause.rb +174 -0
  180. data/lib/active_record/relation/where_clause_factory.rb +38 -0
  181. data/lib/active_record/result.rb +11 -4
  182. data/lib/active_record/runtime_registry.rb +1 -1
  183. data/lib/active_record/sanitization.rb +105 -66
  184. data/lib/active_record/schema.rb +26 -22
  185. data/lib/active_record/schema_dumper.rb +54 -37
  186. data/lib/active_record/schema_migration.rb +11 -14
  187. data/lib/active_record/scoping.rb +34 -16
  188. data/lib/active_record/scoping/default.rb +28 -10
  189. data/lib/active_record/scoping/named.rb +59 -26
  190. data/lib/active_record/secure_token.rb +38 -0
  191. data/lib/active_record/serialization.rb +3 -5
  192. data/lib/active_record/statement_cache.rb +17 -15
  193. data/lib/active_record/store.rb +8 -3
  194. data/lib/active_record/suppressor.rb +58 -0
  195. data/lib/active_record/table_metadata.rb +69 -0
  196. data/lib/active_record/tasks/database_tasks.rb +66 -49
  197. data/lib/active_record/tasks/mysql_database_tasks.rb +6 -14
  198. data/lib/active_record/tasks/postgresql_database_tasks.rb +12 -3
  199. data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
  200. data/lib/active_record/timestamp.rb +20 -9
  201. data/lib/active_record/touch_later.rb +63 -0
  202. data/lib/active_record/transactions.rb +139 -57
  203. data/lib/active_record/type.rb +66 -17
  204. data/lib/active_record/type/adapter_specific_registry.rb +130 -0
  205. data/lib/active_record/type/date.rb +2 -45
  206. data/lib/active_record/type/date_time.rb +2 -49
  207. data/lib/active_record/type/internal/abstract_json.rb +33 -0
  208. data/lib/active_record/type/internal/timezone.rb +15 -0
  209. data/lib/active_record/type/serialized.rb +15 -14
  210. data/lib/active_record/type/time.rb +10 -16
  211. data/lib/active_record/type/type_map.rb +4 -4
  212. data/lib/active_record/type_caster.rb +7 -0
  213. data/lib/active_record/type_caster/connection.rb +29 -0
  214. data/lib/active_record/type_caster/map.rb +19 -0
  215. data/lib/active_record/validations.rb +33 -32
  216. data/lib/active_record/validations/absence.rb +23 -0
  217. data/lib/active_record/validations/associated.rb +10 -3
  218. data/lib/active_record/validations/length.rb +24 -0
  219. data/lib/active_record/validations/presence.rb +11 -12
  220. data/lib/active_record/validations/uniqueness.rb +33 -33
  221. data/lib/rails/generators/active_record/migration.rb +15 -0
  222. data/lib/rails/generators/active_record/migration/migration_generator.rb +8 -5
  223. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +8 -3
  224. data/lib/rails/generators/active_record/migration/templates/migration.rb +8 -1
  225. data/lib/rails/generators/active_record/model/model_generator.rb +33 -16
  226. data/lib/rails/generators/active_record/model/templates/application_record.rb +5 -0
  227. data/lib/rails/generators/active_record/model/templates/model.rb +3 -0
  228. metadata +58 -34
  229. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -498
  230. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  231. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +0 -11
  232. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  233. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
  234. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +0 -11
  235. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  236. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  237. data/lib/active_record/type/big_integer.rb +0 -13
  238. data/lib/active_record/type/binary.rb +0 -50
  239. data/lib/active_record/type/boolean.rb +0 -31
  240. data/lib/active_record/type/decimal.rb +0 -64
  241. data/lib/active_record/type/decimal_without_scale.rb +0 -11
  242. data/lib/active_record/type/decorator.rb +0 -14
  243. data/lib/active_record/type/float.rb +0 -19
  244. data/lib/active_record/type/integer.rb +0 -59
  245. data/lib/active_record/type/mutable.rb +0 -16
  246. data/lib/active_record/type/numeric.rb +0 -36
  247. data/lib/active_record/type/string.rb +0 -40
  248. data/lib/active_record/type/text.rb +0 -11
  249. data/lib/active_record/type/time_value.rb +0 -38
  250. data/lib/active_record/type/unsigned_integer.rb +0 -15
  251. data/lib/active_record/type/value.rb +0 -110
@@ -0,0 +1,49 @@
1
+ module ActiveRecord
2
+ class Relation
3
+ module RecordFetchWarning
4
+ # When this module is prepended to ActiveRecord::Relation and
5
+ # `config.active_record.warn_on_records_fetched_greater_than` is
6
+ # set to an integer, if the number of records a query returns is
7
+ # greater than the value of `warn_on_records_fetched_greater_than`,
8
+ # a warning is logged. This allows for the detection of queries that
9
+ # return a large number of records, which could cause memory bloat.
10
+ #
11
+ # In most cases, fetching large number of records can be performed
12
+ # efficiently using the ActiveRecord::Batches methods.
13
+ # See active_record/lib/relation/batches.rb for more information.
14
+ def exec_queries
15
+ QueryRegistry.reset
16
+
17
+ super.tap do
18
+ if logger && warn_on_records_fetched_greater_than
19
+ if @records.length > warn_on_records_fetched_greater_than
20
+ logger.warn "Query fetched #{@records.size} #{@klass} records: #{QueryRegistry.queries.join(";")}"
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ # :stopdoc:
27
+ ActiveSupport::Notifications.subscribe("sql.active_record") do |*, payload|
28
+ QueryRegistry.queries << payload[:sql]
29
+ end
30
+ # :startdoc:
31
+
32
+ class QueryRegistry # :nodoc:
33
+ extend ActiveSupport::PerThreadRegistry
34
+
35
+ attr_reader :queries
36
+
37
+ def initialize
38
+ @queries = []
39
+ end
40
+
41
+ def reset
42
+ @queries.clear
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ ActiveRecord::Relation.prepend ActiveRecord::Relation::RecordFetchWarning
@@ -10,7 +10,7 @@ module ActiveRecord
10
10
  clone
11
11
  end
12
12
 
13
- # Merges in the conditions from <tt>other</tt>, if <tt>other</tt> is an <tt>ActiveRecord::Relation</tt>.
13
+ # Merges in the conditions from <tt>other</tt>, if <tt>other</tt> is an ActiveRecord::Relation.
14
14
  # Returns an array representing the intersection of the resulting records with <tt>other</tt>, if <tt>other</tt> is an array.
15
15
  #
16
16
  # Post.where(published: true).joins(:comments).merge( Comment.where(spam: false) )
@@ -29,11 +29,11 @@ module ActiveRecord
29
29
  # This is mainly intended for sharing common conditions between multiple associations.
30
30
  def merge(other)
31
31
  if other.is_a?(Array)
32
- to_a & other
32
+ records & other
33
33
  elsif other
34
34
  spawn.merge!(other)
35
35
  else
36
- self
36
+ raise ArgumentError, "invalid argument: #{other.inspect}."
37
37
  end
38
38
  end
39
39
 
@@ -62,16 +62,13 @@ module ActiveRecord
62
62
  # Post.order('id asc').only(:where) # discards the order condition
63
63
  # Post.order('id asc').only(:where, :order) # uses the specified order
64
64
  def only(*onlies)
65
- if onlies.any? { |o| o == :where }
66
- onlies << :bind
67
- end
68
65
  relation_with values.slice(*onlies)
69
66
  end
70
67
 
71
68
  private
72
69
 
73
70
  def relation_with(values) # :nodoc:
74
- result = Relation.create(klass, table, values)
71
+ result = Relation.create(klass, table, predicate_builder, values)
75
72
  result.extend(*extending_values) if extending_values.any?
76
73
  result
77
74
  end
@@ -0,0 +1,174 @@
1
+ module ActiveRecord
2
+ class Relation
3
+ class WhereClause # :nodoc:
4
+ attr_reader :binds
5
+
6
+ delegate :any?, :empty?, to: :predicates
7
+
8
+ def initialize(predicates, binds)
9
+ @predicates = predicates
10
+ @binds = binds
11
+ end
12
+
13
+ def +(other)
14
+ WhereClause.new(
15
+ predicates + other.predicates,
16
+ binds + other.binds,
17
+ )
18
+ end
19
+
20
+ def merge(other)
21
+ WhereClause.new(
22
+ predicates_unreferenced_by(other) + other.predicates,
23
+ non_conflicting_binds(other) + other.binds,
24
+ )
25
+ end
26
+
27
+ def except(*columns)
28
+ WhereClause.new(
29
+ predicates_except(columns),
30
+ binds_except(columns),
31
+ )
32
+ end
33
+
34
+ def or(other)
35
+ if empty?
36
+ self
37
+ elsif other.empty?
38
+ other
39
+ else
40
+ WhereClause.new(
41
+ [ast.or(other.ast)],
42
+ binds + other.binds
43
+ )
44
+ end
45
+ end
46
+
47
+ def to_h(table_name = nil)
48
+ equalities = predicates.grep(Arel::Nodes::Equality)
49
+ if table_name
50
+ equalities = equalities.select do |node|
51
+ node.left.relation.name == table_name
52
+ end
53
+ end
54
+
55
+ binds = self.binds.map { |attr| [attr.name, attr.value] }.to_h
56
+
57
+ equalities.map { |node|
58
+ name = node.left.name
59
+ [name, binds.fetch(name.to_s) {
60
+ case node.right
61
+ when Array then node.right.map(&:val)
62
+ when Arel::Nodes::Casted, Arel::Nodes::Quoted
63
+ node.right.val
64
+ end
65
+ }]
66
+ }.to_h
67
+ end
68
+
69
+ def ast
70
+ Arel::Nodes::And.new(predicates_with_wrapped_sql_literals)
71
+ end
72
+
73
+ def ==(other)
74
+ other.is_a?(WhereClause) &&
75
+ predicates == other.predicates &&
76
+ binds == other.binds
77
+ end
78
+
79
+ def invert
80
+ WhereClause.new(inverted_predicates, binds)
81
+ end
82
+
83
+ def self.empty
84
+ @empty ||= new([], [])
85
+ end
86
+
87
+ protected
88
+
89
+ attr_reader :predicates
90
+
91
+ def referenced_columns
92
+ @referenced_columns ||= begin
93
+ equality_nodes = predicates.select { |n| equality_node?(n) }
94
+ Set.new(equality_nodes, &:left)
95
+ end
96
+ end
97
+
98
+ private
99
+
100
+ def predicates_unreferenced_by(other)
101
+ predicates.reject do |n|
102
+ equality_node?(n) && other.referenced_columns.include?(n.left)
103
+ end
104
+ end
105
+
106
+ def equality_node?(node)
107
+ node.respond_to?(:operator) && node.operator == :==
108
+ end
109
+
110
+ def non_conflicting_binds(other)
111
+ conflicts = referenced_columns & other.referenced_columns
112
+ conflicts.map! { |node| node.name.to_s }
113
+ binds.reject { |attr| conflicts.include?(attr.name) }
114
+ end
115
+
116
+ def inverted_predicates
117
+ predicates.map { |node| invert_predicate(node) }
118
+ end
119
+
120
+ def invert_predicate(node)
121
+ case node
122
+ when NilClass
123
+ raise ArgumentError, 'Invalid argument for .where.not(), got nil.'
124
+ when Arel::Nodes::In
125
+ Arel::Nodes::NotIn.new(node.left, node.right)
126
+ when Arel::Nodes::Equality
127
+ Arel::Nodes::NotEqual.new(node.left, node.right)
128
+ when String
129
+ Arel::Nodes::Not.new(Arel::Nodes::SqlLiteral.new(node))
130
+ else
131
+ Arel::Nodes::Not.new(node)
132
+ end
133
+ end
134
+
135
+ def predicates_except(columns)
136
+ predicates.reject do |node|
137
+ case node
138
+ 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
139
+ subrelation = (node.left.kind_of?(Arel::Attributes::Attribute) ? node.left : node.right)
140
+ columns.include?(subrelation.name.to_s)
141
+ end
142
+ end
143
+ end
144
+
145
+ def binds_except(columns)
146
+ binds.reject do |attr|
147
+ columns.include?(attr.name)
148
+ end
149
+ end
150
+
151
+ def predicates_with_wrapped_sql_literals
152
+ non_empty_predicates.map do |node|
153
+ if Arel::Nodes::Equality === node
154
+ node
155
+ else
156
+ wrap_sql_literal(node)
157
+ end
158
+ end
159
+ end
160
+
161
+ ARRAY_WITH_EMPTY_STRING = ['']
162
+ def non_empty_predicates
163
+ predicates - ARRAY_WITH_EMPTY_STRING
164
+ end
165
+
166
+ def wrap_sql_literal(node)
167
+ if ::String === node
168
+ node = Arel.sql(node)
169
+ end
170
+ Arel::Nodes::Grouping.new(node)
171
+ end
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,38 @@
1
+ module ActiveRecord
2
+ class Relation
3
+ class WhereClauseFactory # :nodoc:
4
+ def initialize(klass, predicate_builder)
5
+ @klass = klass
6
+ @predicate_builder = predicate_builder
7
+ end
8
+
9
+ def build(opts, other)
10
+ binds = []
11
+
12
+ case opts
13
+ when String, Array
14
+ parts = [klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))]
15
+ when Hash
16
+ attributes = predicate_builder.resolve_column_aliases(opts)
17
+ attributes = klass.send(:expand_hash_conditions_for_aggregates, attributes)
18
+ attributes.stringify_keys!
19
+
20
+ attributes, binds = predicate_builder.create_binds(attributes)
21
+
22
+ parts = predicate_builder.build_from_hash(attributes)
23
+ when Arel::Nodes::Node
24
+ parts = [opts]
25
+ binds = other
26
+ else
27
+ raise ArgumentError, "Unsupported argument type: #{opts} (#{opts.class})"
28
+ end
29
+
30
+ WhereClause.new(parts, binds)
31
+ end
32
+
33
+ protected
34
+
35
+ attr_reader :klass, :predicate_builder
36
+ end
37
+ end
38
+ end
@@ -1,7 +1,8 @@
1
1
  module ActiveRecord
2
2
  ###
3
- # This class encapsulates a Result returned from calling +exec_query+ on any
4
- # database connection adapter. For example:
3
+ # This class encapsulates a result returned from calling
4
+ # {#exec_query}[rdoc-ref:ConnectionAdapters::DatabaseStatements#exec_query]
5
+ # on any database connection adapter. For example:
5
6
  #
6
7
  # result = ActiveRecord::Base.connection.exec_query('SELECT id, title, body FROM posts')
7
8
  # result # => #<ActiveRecord::Result:0xdeadbeef>
@@ -74,14 +75,20 @@ module ActiveRecord
74
75
  hash_rows[idx]
75
76
  end
76
77
 
78
+ def first
79
+ return nil if @rows.empty?
80
+ Hash[@columns.zip(@rows.first)]
81
+ end
82
+
77
83
  def last
78
- hash_rows.last
84
+ return nil if @rows.empty?
85
+ Hash[@columns.zip(@rows.last)]
79
86
  end
80
87
 
81
88
  def cast_values(type_overrides = {}) # :nodoc:
82
89
  types = columns.map { |name| column_type(name, type_overrides) }
83
90
  result = rows.map do |values|
84
- types.zip(values).map { |type, value| type.type_cast_from_database(value) }
91
+ types.zip(values).map { |type, value| type.deserialize(value) }
85
92
  end
86
93
 
87
94
  columns.one? ? result.map!(&:first) : result
@@ -7,7 +7,7 @@ module ActiveRecord
7
7
  #
8
8
  # returns the connection handler local to the current thread.
9
9
  #
10
- # See the documentation of <tt>ActiveSupport::PerThreadRegistry</tt>
10
+ # See the documentation of ActiveSupport::PerThreadRegistry
11
11
  # for further details.
12
12
  class RuntimeRegistry # :nodoc:
13
13
  extend ActiveSupport::PerThreadRegistry
@@ -3,28 +3,34 @@ module ActiveRecord
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  module ClassMethods
6
- def quote_value(value, column) #:nodoc:
7
- connection.quote(value, column)
8
- end
9
-
10
- # Used to sanitize objects before they're used in an SQL SELECT statement. Delegates to <tt>connection.quote</tt>.
11
- def sanitize(object) #:nodoc:
6
+ # Used to sanitize objects before they're used in an SQL SELECT statement.
7
+ # Delegates to {connection.quote}[rdoc-ref:ConnectionAdapters::Quoting#quote].
8
+ def sanitize(object) # :nodoc:
12
9
  connection.quote(object)
13
10
  end
11
+ alias_method :quote_value, :sanitize
14
12
 
15
13
  protected
16
14
 
17
- # Accepts an array, hash, or string of SQL conditions and sanitizes
15
+ # Accepts an array or string of SQL conditions and sanitizes
18
16
  # them into a valid SQL fragment for a WHERE clause.
19
- # ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
20
- # { name: "foo'bar", group_id: 4 } returns "name='foo''bar' and group_id='4'"
21
- # "name='foo''bar' and group_id='4'" returns "name='foo''bar' and group_id='4'"
22
- def sanitize_sql_for_conditions(condition, table_name = self.table_name)
17
+ #
18
+ # sanitize_sql_for_conditions(["name=? and group_id=?", "foo'bar", 4])
19
+ # # => "name='foo''bar' and group_id=4"
20
+ #
21
+ # sanitize_sql_for_conditions(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4])
22
+ # # => "name='foo''bar' and group_id='4'"
23
+ #
24
+ # sanitize_sql_for_conditions(["name='%s' and group_id='%s'", "foo'bar", 4])
25
+ # # => "name='foo''bar' and group_id='4'"
26
+ #
27
+ # sanitize_sql_for_conditions("name='foo''bar' and group_id='4'")
28
+ # # => "name='foo''bar' and group_id='4'"
29
+ def sanitize_sql_for_conditions(condition)
23
30
  return nil if condition.blank?
24
31
 
25
32
  case condition
26
33
  when Array; sanitize_sql_array(condition)
27
- when Hash; sanitize_sql_hash_for_conditions(condition, table_name)
28
34
  else condition
29
35
  end
30
36
  end
@@ -33,7 +39,18 @@ module ActiveRecord
33
39
 
34
40
  # Accepts an array, hash, or string of SQL conditions and sanitizes
35
41
  # them into a valid SQL fragment for a SET clause.
36
- # { name: nil, group_id: 4 } returns "name = NULL , group_id='4'"
42
+ #
43
+ # sanitize_sql_for_assignment(["name=? and group_id=?", nil, 4])
44
+ # # => "name=NULL and group_id=4"
45
+ #
46
+ # sanitize_sql_for_assignment(["name=:name and group_id=:group_id", name: nil, group_id: 4])
47
+ # # => "name=NULL and group_id=4"
48
+ #
49
+ # Post.send(:sanitize_sql_for_assignment, { name: nil, group_id: 4 })
50
+ # # => "`posts`.`name` = NULL, `posts`.`group_id` = 4"
51
+ #
52
+ # sanitize_sql_for_assignment("name=NULL and group_id='4'")
53
+ # # => "name=NULL and group_id='4'"
37
54
  def sanitize_sql_for_assignment(assignments, default_table_name = self.table_name)
38
55
  case assignments
39
56
  when Array; sanitize_sql_array(assignments)
@@ -42,17 +59,37 @@ module ActiveRecord
42
59
  end
43
60
  end
44
61
 
62
+ # Accepts an array, or string of SQL conditions and sanitizes
63
+ # them into a valid SQL fragment for an ORDER clause.
64
+ #
65
+ # sanitize_sql_for_order(["field(id, ?)", [1,3,2]])
66
+ # # => "field(id, 1,3,2)"
67
+ #
68
+ # sanitize_sql_for_order("id ASC")
69
+ # # => "id ASC"
70
+ def sanitize_sql_for_order(condition)
71
+ if condition.is_a?(Array) && condition.first.to_s.include?('?')
72
+ sanitize_sql_array(condition)
73
+ else
74
+ condition
75
+ end
76
+ end
77
+
45
78
  # Accepts a hash of SQL conditions and replaces those attributes
46
- # that correspond to a +composed_of+ relationship with their expanded
47
- # aggregate attribute values.
79
+ # that correspond to a {#composed_of}[rdoc-ref:Aggregations::ClassMethods#composed_of]
80
+ # relationship with their expanded aggregate attribute values.
81
+ #
48
82
  # Given:
49
- # class Person < ActiveRecord::Base
50
- # composed_of :address, class_name: "Address",
51
- # mapping: [%w(address_street street), %w(address_city city)]
52
- # end
83
+ #
84
+ # class Person < ActiveRecord::Base
85
+ # composed_of :address, class_name: "Address",
86
+ # mapping: [%w(address_street street), %w(address_city city)]
87
+ # end
88
+ #
53
89
  # Then:
54
- # { address: Address.new("813 abc st.", "chicago") }
55
- # # => { address_street: "813 abc st.", address_city: "chicago" }
90
+ #
91
+ # { address: Address.new("813 abc st.", "chicago") }
92
+ # # => { address_street: "813 abc st.", address_city: "chicago" }
56
93
  def expand_hash_conditions_for_aggregates(attrs)
57
94
  expanded_attrs = {}
58
95
  attrs.each do |attr, value|
@@ -72,46 +109,42 @@ module ActiveRecord
72
109
  expanded_attrs
73
110
  end
74
111
 
75
- # Sanitizes a hash of attribute/value pairs into SQL conditions for a WHERE clause.
76
- # { name: "foo'bar", group_id: 4 }
77
- # # => "name='foo''bar' and group_id= 4"
78
- # { status: nil, group_id: [1,2,3] }
79
- # # => "status IS NULL and group_id IN (1,2,3)"
80
- # { age: 13..18 }
81
- # # => "age BETWEEN 13 AND 18"
82
- # { 'other_records.id' => 7 }
83
- # # => "`other_records`.`id` = 7"
84
- # { other_records: { id: 7 } }
85
- # # => "`other_records`.`id` = 7"
86
- # And for value objects on a composed_of relationship:
87
- # { address: Address.new("123 abc st.", "chicago") }
88
- # # => "address_street='123 abc st.' and address_city='chicago'"
89
- def sanitize_sql_hash_for_conditions(attrs, default_table_name = self.table_name)
90
- ActiveSupport::Deprecation.warn(<<-EOWARN)
91
- sanitize_sql_hash_for_conditions is deprecated, and will be removed in Rails 5.0
92
- EOWARN
93
- attrs = PredicateBuilder.resolve_column_aliases self, attrs
94
- attrs = expand_hash_conditions_for_aggregates(attrs)
95
-
96
- table = Arel::Table.new(table_name, arel_engine).alias(default_table_name)
97
- PredicateBuilder.build_from_hash(self, attrs, table).map { |b|
98
- connection.visitor.compile b
99
- }.join(' AND ')
100
- end
101
- alias_method :sanitize_sql_hash, :sanitize_sql_hash_for_conditions
102
-
103
112
  # Sanitizes a hash of attribute/value pairs into SQL conditions for a SET clause.
104
- # { status: nil, group_id: 1 }
105
- # # => "status = NULL , group_id = 1"
113
+ #
114
+ # sanitize_sql_hash_for_assignment({ status: nil, group_id: 1 }, "posts")
115
+ # # => "`posts`.`status` = NULL, `posts`.`group_id` = 1"
106
116
  def sanitize_sql_hash_for_assignment(attrs, table)
107
117
  c = connection
108
118
  attrs.map do |attr, value|
109
- "#{c.quote_table_name_for_assignment(table, attr)} = #{quote_bound_value(value, c, columns_hash[attr.to_s])}"
119
+ if value.is_a?(Base)
120
+ require "active_support/core_ext/string/filters"
121
+ ActiveSupport::Deprecation.warn(<<-WARNING.squish)
122
+ Passing `ActiveRecord::Base` objects to
123
+ `sanitize_sql_hash_for_assignment` (or methods which call it,
124
+ such as `update_all`) is deprecated. Please pass the id directly,
125
+ instead.
126
+ WARNING
127
+ else
128
+ value = type_for_attribute(attr.to_s).serialize(value)
129
+ end
130
+ "#{c.quote_table_name_for_assignment(table, attr)} = #{c.quote(value)}"
110
131
  end.join(', ')
111
132
  end
112
133
 
113
134
  # Sanitizes a +string+ so that it is safe to use within an SQL
114
- # LIKE statement. This method uses +escape_character+ to escape all occurrences of "\", "_" and "%"
135
+ # LIKE statement. This method uses +escape_character+ to escape all occurrences of "\", "_" and "%".
136
+ #
137
+ # sanitize_sql_like("100%")
138
+ # # => "100\\%"
139
+ #
140
+ # sanitize_sql_like("snake_cased_string")
141
+ # # => "snake\\_cased\\_string"
142
+ #
143
+ # sanitize_sql_like("100%", "!")
144
+ # # => "100!%"
145
+ #
146
+ # sanitize_sql_like("snake_cased_string", "!")
147
+ # # => "snake!_cased!_string"
115
148
  def sanitize_sql_like(string, escape_character = "\\")
116
149
  pattern = Regexp.union(escape_character, "%", "_")
117
150
  string.gsub(pattern) { |x| [escape_character, x].join }
@@ -119,7 +152,15 @@ sanitize_sql_hash_for_conditions is deprecated, and will be removed in Rails 5.0
119
152
 
120
153
  # Accepts an array of conditions. The array has each value
121
154
  # sanitized and interpolated into the SQL statement.
122
- # ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
155
+ #
156
+ # sanitize_sql_array(["name=? and group_id=?", "foo'bar", 4])
157
+ # # => "name='foo''bar' and group_id=4"
158
+ #
159
+ # sanitize_sql_array(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4])
160
+ # # => "name='foo''bar' and group_id=4"
161
+ #
162
+ # sanitize_sql_array(["name='%s' and group_id='%s'", "foo'bar", 4])
163
+ # # => "name='foo''bar' and group_id='4'"
123
164
  def sanitize_sql_array(ary)
124
165
  statement, *values = ary
125
166
  if values.first.is_a?(Hash) && statement =~ /:\w+/
@@ -133,7 +174,7 @@ sanitize_sql_hash_for_conditions is deprecated, and will be removed in Rails 5.0
133
174
  end
134
175
  end
135
176
 
136
- def replace_bind_variables(statement, values) #:nodoc:
177
+ def replace_bind_variables(statement, values) # :nodoc:
137
178
  raise_if_bind_arity_mismatch(statement, statement.count('?'), values.size)
138
179
  bound = values.dup
139
180
  c = connection
@@ -142,7 +183,7 @@ sanitize_sql_hash_for_conditions is deprecated, and will be removed in Rails 5.0
142
183
  end
143
184
  end
144
185
 
145
- def replace_bind_variable(value, c = connection) #:nodoc:
186
+ def replace_bind_variable(value, c = connection) # :nodoc:
146
187
  if ActiveRecord::Relation === value
147
188
  value.to_sql
148
189
  else
@@ -150,10 +191,10 @@ sanitize_sql_hash_for_conditions is deprecated, and will be removed in Rails 5.0
150
191
  end
151
192
  end
152
193
 
153
- def replace_named_bind_variables(statement, bind_vars) #:nodoc:
154
- statement.gsub(/(:?):([a-zA-Z]\w*)/) do
194
+ def replace_named_bind_variables(statement, bind_vars) # :nodoc:
195
+ statement.gsub(/(:?):([a-zA-Z]\w*)/) do |match|
155
196
  if $1 == ':' # skip postgresql casts
156
- $& # return the whole match
197
+ match # return the whole match
157
198
  elsif bind_vars.include?(match = $2.to_sym)
158
199
  replace_bind_variable(bind_vars[match])
159
200
  else
@@ -162,10 +203,8 @@ sanitize_sql_hash_for_conditions is deprecated, and will be removed in Rails 5.0
162
203
  end
163
204
  end
164
205
 
165
- def quote_bound_value(value, c = connection, column = nil) #:nodoc:
166
- if column
167
- c.quote(value, column)
168
- elsif value.respond_to?(:map) && !value.acts_like?(:string)
206
+ def quote_bound_value(value, c = connection) # :nodoc:
207
+ if value.respond_to?(:map) && !value.acts_like?(:string)
169
208
  if value.respond_to?(:empty?) && value.empty?
170
209
  c.quote(nil)
171
210
  else
@@ -176,7 +215,7 @@ sanitize_sql_hash_for_conditions is deprecated, and will be removed in Rails 5.0
176
215
  end
177
216
  end
178
217
 
179
- def raise_if_bind_arity_mismatch(statement, expected, provided) #:nodoc:
218
+ def raise_if_bind_arity_mismatch(statement, expected, provided) # :nodoc:
180
219
  unless expected == provided
181
220
  raise PreparedStatementInvalid, "wrong number of bind variables (#{provided} for #{expected}) in: #{statement}"
182
221
  end
@@ -184,8 +223,8 @@ sanitize_sql_hash_for_conditions is deprecated, and will be removed in Rails 5.0
184
223
  end
185
224
 
186
225
  # TODO: Deprecate this
187
- def quoted_id
188
- self.class.quote_value(id, column_for_attribute(self.class.primary_key))
226
+ def quoted_id # :nodoc:
227
+ self.class.quote_value(@attributes[self.class.primary_key].value_for_database)
189
228
  end
190
229
  end
191
230
  end