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,51 @@
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 |*args|
28
+ payload = args.last
29
+
30
+ QueryRegistry.queries << payload[:sql]
31
+ end
32
+ # :startdoc:
33
+
34
+ class QueryRegistry # :nodoc:
35
+ extend ActiveSupport::PerThreadRegistry
36
+
37
+ attr_accessor :queries
38
+
39
+ def initialize
40
+ reset
41
+ end
42
+
43
+ def reset
44
+ @queries = []
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ 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) )
@@ -33,7 +33,7 @@ module ActiveRecord
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,173 @@
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
+ 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
+ def non_empty_predicates
162
+ predicates - ['']
163
+ end
164
+
165
+ def wrap_sql_literal(node)
166
+ if ::String === node
167
+ node = Arel.sql(node)
168
+ end
169
+ Arel::Nodes::Grouping.new(node)
170
+ end
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,37 @@
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
+ else
26
+ raise ArgumentError, "Unsupported argument type: #{opts} (#{opts.class})"
27
+ end
28
+
29
+ WhereClause.new(parts, binds)
30
+ end
31
+
32
+ protected
33
+
34
+ attr_reader :klass, :predicate_builder
35
+ end
36
+ end
37
+ 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>
@@ -81,7 +82,7 @@ module ActiveRecord
81
82
  def cast_values(type_overrides = {}) # :nodoc:
82
83
  types = columns.map { |name| column_type(name, type_overrides) }
83
84
  result = rows.map do |values|
84
- types.zip(values).map { |type, value| type.type_cast_from_database(value) }
85
+ types.zip(values).map { |type, value| type.deserialize(value) }
85
86
  end
86
87
 
87
88
  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 a 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,32 @@ 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
+ value = type_for_attribute(attr.to_s).serialize(value)
120
+ "#{c.quote_table_name_for_assignment(table, attr)} = #{c.quote(value)}"
110
121
  end.join(', ')
111
122
  end
112
123
 
113
124
  # 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 "%"
125
+ # LIKE statement. This method uses +escape_character+ to escape all occurrences of "\", "_" and "%".
126
+ #
127
+ # sanitize_sql_like("100%")
128
+ # # => "100\\%"
129
+ #
130
+ # sanitize_sql_like("snake_cased_string")
131
+ # # => "snake\\_cased\\_string"
132
+ #
133
+ # sanitize_sql_like("100%", "!")
134
+ # # => "100!%"
135
+ #
136
+ # sanitize_sql_like("snake_cased_string", "!")
137
+ # # => "snake!_cased!_string"
115
138
  def sanitize_sql_like(string, escape_character = "\\")
116
139
  pattern = Regexp.union(escape_character, "%", "_")
117
140
  string.gsub(pattern) { |x| [escape_character, x].join }
@@ -119,7 +142,15 @@ sanitize_sql_hash_for_conditions is deprecated, and will be removed in Rails 5.0
119
142
 
120
143
  # Accepts an array of conditions. The array has each value
121
144
  # 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'"
145
+ #
146
+ # sanitize_sql_array(["name=? and group_id=?", "foo'bar", 4])
147
+ # # => "name='foo''bar' and group_id=4"
148
+ #
149
+ # sanitize_sql_array(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4])
150
+ # # => "name='foo''bar' and group_id=4"
151
+ #
152
+ # sanitize_sql_array(["name='%s' and group_id='%s'", "foo'bar", 4])
153
+ # # => "name='foo''bar' and group_id='4'"
123
154
  def sanitize_sql_array(ary)
124
155
  statement, *values = ary
125
156
  if values.first.is_a?(Hash) && statement =~ /:\w+/
@@ -133,7 +164,7 @@ sanitize_sql_hash_for_conditions is deprecated, and will be removed in Rails 5.0
133
164
  end
134
165
  end
135
166
 
136
- def replace_bind_variables(statement, values) #:nodoc:
167
+ def replace_bind_variables(statement, values) # :nodoc:
137
168
  raise_if_bind_arity_mismatch(statement, statement.count('?'), values.size)
138
169
  bound = values.dup
139
170
  c = connection
@@ -142,7 +173,7 @@ sanitize_sql_hash_for_conditions is deprecated, and will be removed in Rails 5.0
142
173
  end
143
174
  end
144
175
 
145
- def replace_bind_variable(value, c = connection) #:nodoc:
176
+ def replace_bind_variable(value, c = connection) # :nodoc:
146
177
  if ActiveRecord::Relation === value
147
178
  value.to_sql
148
179
  else
@@ -150,10 +181,10 @@ sanitize_sql_hash_for_conditions is deprecated, and will be removed in Rails 5.0
150
181
  end
151
182
  end
152
183
 
153
- def replace_named_bind_variables(statement, bind_vars) #:nodoc:
154
- statement.gsub(/(:?):([a-zA-Z]\w*)/) do
184
+ def replace_named_bind_variables(statement, bind_vars) # :nodoc:
185
+ statement.gsub(/(:?):([a-zA-Z]\w*)/) do |match|
155
186
  if $1 == ':' # skip postgresql casts
156
- $& # return the whole match
187
+ match # return the whole match
157
188
  elsif bind_vars.include?(match = $2.to_sym)
158
189
  replace_bind_variable(bind_vars[match])
159
190
  else
@@ -162,10 +193,8 @@ sanitize_sql_hash_for_conditions is deprecated, and will be removed in Rails 5.0
162
193
  end
163
194
  end
164
195
 
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)
196
+ def quote_bound_value(value, c = connection) # :nodoc:
197
+ if value.respond_to?(:map) && !value.acts_like?(:string)
169
198
  if value.respond_to?(:empty?) && value.empty?
170
199
  c.quote(nil)
171
200
  else
@@ -176,7 +205,7 @@ sanitize_sql_hash_for_conditions is deprecated, and will be removed in Rails 5.0
176
205
  end
177
206
  end
178
207
 
179
- def raise_if_bind_arity_mismatch(statement, expected, provided) #:nodoc:
208
+ def raise_if_bind_arity_mismatch(statement, expected, provided) # :nodoc:
180
209
  unless expected == provided
181
210
  raise PreparedStatementInvalid, "wrong number of bind variables (#{provided} for #{expected}) in: #{statement}"
182
211
  end
@@ -185,7 +214,7 @@ sanitize_sql_hash_for_conditions is deprecated, and will be removed in Rails 5.0
185
214
 
186
215
  # TODO: Deprecate this
187
216
  def quoted_id
188
- self.class.quote_value(id, column_for_attribute(self.class.primary_key))
217
+ self.class.quote_value(@attributes[self.class.primary_key].value_for_database)
189
218
  end
190
219
  end
191
220
  end