activerecord 4.2.11.1 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (246) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +1282 -1195
  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 +8 -4
  8. data/lib/active_record/aggregations.rb +35 -24
  9. data/lib/active_record/association_relation.rb +3 -3
  10. data/lib/active_record/associations.rb +317 -209
  11. data/lib/active_record/associations/alias_tracker.rb +19 -16
  12. data/lib/active_record/associations/association.rb +11 -9
  13. data/lib/active_record/associations/association_scope.rb +73 -102
  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 +14 -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 +3 -10
  22. data/lib/active_record/associations/collection_association.rb +49 -41
  23. data/lib/active_record/associations/collection_proxy.rb +67 -27
  24. data/lib/active_record/associations/foreign_association.rb +1 -1
  25. data/lib/active_record/associations/has_many_association.rb +20 -71
  26. data/lib/active_record/associations/has_many_through_association.rb +8 -47
  27. data/lib/active_record/associations/has_one_association.rb +12 -5
  28. data/lib/active_record/associations/join_dependency.rb +29 -19
  29. data/lib/active_record/associations/join_dependency/join_association.rb +16 -10
  30. data/lib/active_record/associations/preloader.rb +14 -4
  31. data/lib/active_record/associations/preloader/association.rb +46 -52
  32. data/lib/active_record/associations/preloader/collection_association.rb +0 -6
  33. data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
  34. data/lib/active_record/associations/preloader/has_one.rb +0 -8
  35. data/lib/active_record/associations/preloader/through_association.rb +27 -14
  36. data/lib/active_record/associations/singular_association.rb +7 -1
  37. data/lib/active_record/associations/through_association.rb +11 -3
  38. data/lib/active_record/attribute.rb +68 -18
  39. data/lib/active_record/attribute/user_provided_default.rb +28 -0
  40. data/lib/active_record/attribute_assignment.rb +19 -140
  41. data/lib/active_record/attribute_decorators.rb +6 -5
  42. data/lib/active_record/attribute_methods.rb +76 -47
  43. data/lib/active_record/attribute_methods/before_type_cast.rb +1 -1
  44. data/lib/active_record/attribute_methods/dirty.rb +46 -86
  45. data/lib/active_record/attribute_methods/primary_key.rb +2 -2
  46. data/lib/active_record/attribute_methods/query.rb +2 -2
  47. data/lib/active_record/attribute_methods/read.rb +31 -59
  48. data/lib/active_record/attribute_methods/serialization.rb +13 -16
  49. data/lib/active_record/attribute_methods/time_zone_conversion.rb +61 -14
  50. data/lib/active_record/attribute_methods/write.rb +13 -37
  51. data/lib/active_record/attribute_mutation_tracker.rb +70 -0
  52. data/lib/active_record/attribute_set.rb +30 -3
  53. data/lib/active_record/attribute_set/builder.rb +6 -4
  54. data/lib/active_record/attributes.rb +199 -81
  55. data/lib/active_record/autosave_association.rb +49 -16
  56. data/lib/active_record/base.rb +32 -23
  57. data/lib/active_record/callbacks.rb +39 -43
  58. data/lib/active_record/coders/json.rb +1 -1
  59. data/lib/active_record/coders/yaml_column.rb +20 -8
  60. data/lib/active_record/collection_cache_key.rb +40 -0
  61. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +452 -182
  62. data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -3
  63. data/lib/active_record/connection_adapters/abstract/database_statements.rb +65 -61
  64. data/lib/active_record/connection_adapters/abstract/query_cache.rb +2 -2
  65. data/lib/active_record/connection_adapters/abstract/quoting.rb +74 -10
  66. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  67. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +61 -39
  68. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +236 -185
  69. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +72 -17
  70. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +380 -141
  71. data/lib/active_record/connection_adapters/abstract/transaction.rb +51 -34
  72. data/lib/active_record/connection_adapters/abstract_adapter.rb +141 -59
  73. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +401 -370
  74. data/lib/active_record/connection_adapters/column.rb +28 -43
  75. data/lib/active_record/connection_adapters/connection_specification.rb +15 -27
  76. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
  77. data/lib/active_record/connection_adapters/mysql/column.rb +50 -0
  78. data/lib/active_record/connection_adapters/mysql/database_statements.rb +125 -0
  79. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +70 -0
  80. data/lib/active_record/connection_adapters/mysql/quoting.rb +51 -0
  81. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +67 -0
  82. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +93 -0
  83. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +54 -0
  84. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +32 -0
  85. data/lib/active_record/connection_adapters/mysql2_adapter.rb +29 -166
  86. data/lib/active_record/connection_adapters/postgresql/column.rb +5 -10
  87. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +10 -72
  88. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid.rb +1 -6
  90. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +27 -57
  91. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +2 -2
  92. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +1 -1
  93. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -1
  94. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +7 -22
  95. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +3 -3
  96. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +1 -26
  97. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +2 -2
  98. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +0 -4
  99. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +4 -4
  100. data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +31 -17
  102. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +0 -4
  103. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +2 -2
  104. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +1 -1
  105. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +1 -1
  106. data/lib/active_record/connection_adapters/postgresql/quoting.rb +26 -18
  107. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +29 -10
  108. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -79
  109. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +47 -0
  110. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +234 -148
  111. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
  112. data/lib/active_record/connection_adapters/postgresql_adapter.rb +248 -160
  113. data/lib/active_record/connection_adapters/schema_cache.rb +36 -23
  114. data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
  115. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +19 -0
  116. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +48 -0
  117. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  118. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +149 -192
  119. data/lib/active_record/connection_adapters/statement_pool.rb +31 -12
  120. data/lib/active_record/connection_handling.rb +37 -14
  121. data/lib/active_record/core.rb +89 -107
  122. data/lib/active_record/counter_cache.rb +13 -24
  123. data/lib/active_record/dynamic_matchers.rb +1 -20
  124. data/lib/active_record/enum.rb +113 -76
  125. data/lib/active_record/errors.rb +87 -48
  126. data/lib/active_record/explain_registry.rb +1 -1
  127. data/lib/active_record/explain_subscriber.rb +1 -1
  128. data/lib/active_record/fixture_set/file.rb +26 -5
  129. data/lib/active_record/fixtures.rb +76 -40
  130. data/lib/active_record/gem_version.rb +4 -4
  131. data/lib/active_record/inheritance.rb +32 -40
  132. data/lib/active_record/integration.rb +4 -4
  133. data/lib/active_record/internal_metadata.rb +56 -0
  134. data/lib/active_record/legacy_yaml_adapter.rb +18 -2
  135. data/lib/active_record/locale/en.yml +3 -2
  136. data/lib/active_record/locking/optimistic.rb +15 -15
  137. data/lib/active_record/locking/pessimistic.rb +1 -1
  138. data/lib/active_record/log_subscriber.rb +43 -21
  139. data/lib/active_record/migration.rb +363 -133
  140. data/lib/active_record/migration/command_recorder.rb +59 -18
  141. data/lib/active_record/migration/compatibility.rb +126 -0
  142. data/lib/active_record/model_schema.rb +129 -41
  143. data/lib/active_record/nested_attributes.rb +58 -29
  144. data/lib/active_record/null_relation.rb +16 -8
  145. data/lib/active_record/persistence.rb +121 -80
  146. data/lib/active_record/query_cache.rb +15 -18
  147. data/lib/active_record/querying.rb +10 -9
  148. data/lib/active_record/railtie.rb +23 -16
  149. data/lib/active_record/railties/controller_runtime.rb +1 -1
  150. data/lib/active_record/railties/databases.rake +69 -46
  151. data/lib/active_record/readonly_attributes.rb +1 -1
  152. data/lib/active_record/reflection.rb +282 -115
  153. data/lib/active_record/relation.rb +176 -116
  154. data/lib/active_record/relation/batches.rb +139 -34
  155. data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
  156. data/lib/active_record/relation/calculations.rb +79 -108
  157. data/lib/active_record/relation/delegation.rb +7 -20
  158. data/lib/active_record/relation/finder_methods.rb +163 -81
  159. data/lib/active_record/relation/from_clause.rb +32 -0
  160. data/lib/active_record/relation/merger.rb +16 -42
  161. data/lib/active_record/relation/predicate_builder.rb +120 -107
  162. data/lib/active_record/relation/predicate_builder/array_handler.rb +11 -16
  163. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +88 -0
  164. data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
  165. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
  166. data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
  167. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +57 -0
  168. data/lib/active_record/relation/predicate_builder/range_handler.rb +33 -0
  169. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  170. data/lib/active_record/relation/query_attribute.rb +19 -0
  171. data/lib/active_record/relation/query_methods.rb +308 -244
  172. data/lib/active_record/relation/record_fetch_warning.rb +49 -0
  173. data/lib/active_record/relation/spawn_methods.rb +4 -7
  174. data/lib/active_record/relation/where_clause.rb +174 -0
  175. data/lib/active_record/relation/where_clause_factory.rb +38 -0
  176. data/lib/active_record/result.rb +4 -3
  177. data/lib/active_record/runtime_registry.rb +1 -1
  178. data/lib/active_record/sanitization.rb +95 -66
  179. data/lib/active_record/schema.rb +26 -22
  180. data/lib/active_record/schema_dumper.rb +62 -38
  181. data/lib/active_record/schema_migration.rb +11 -14
  182. data/lib/active_record/scoping.rb +32 -15
  183. data/lib/active_record/scoping/default.rb +23 -9
  184. data/lib/active_record/scoping/named.rb +49 -28
  185. data/lib/active_record/secure_token.rb +38 -0
  186. data/lib/active_record/serialization.rb +2 -4
  187. data/lib/active_record/statement_cache.rb +16 -14
  188. data/lib/active_record/store.rb +8 -3
  189. data/lib/active_record/suppressor.rb +58 -0
  190. data/lib/active_record/table_metadata.rb +68 -0
  191. data/lib/active_record/tasks/database_tasks.rb +57 -43
  192. data/lib/active_record/tasks/mysql_database_tasks.rb +6 -14
  193. data/lib/active_record/tasks/postgresql_database_tasks.rb +11 -2
  194. data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
  195. data/lib/active_record/timestamp.rb +20 -9
  196. data/lib/active_record/touch_later.rb +58 -0
  197. data/lib/active_record/transactions.rb +138 -56
  198. data/lib/active_record/type.rb +66 -17
  199. data/lib/active_record/type/adapter_specific_registry.rb +130 -0
  200. data/lib/active_record/type/date.rb +2 -45
  201. data/lib/active_record/type/date_time.rb +2 -49
  202. data/lib/active_record/type/internal/abstract_json.rb +29 -0
  203. data/lib/active_record/type/internal/timezone.rb +15 -0
  204. data/lib/active_record/type/serialized.rb +15 -14
  205. data/lib/active_record/type/time.rb +10 -16
  206. data/lib/active_record/type/type_map.rb +4 -4
  207. data/lib/active_record/type_caster.rb +7 -0
  208. data/lib/active_record/type_caster/connection.rb +29 -0
  209. data/lib/active_record/type_caster/map.rb +19 -0
  210. data/lib/active_record/validations.rb +33 -32
  211. data/lib/active_record/validations/absence.rb +23 -0
  212. data/lib/active_record/validations/associated.rb +10 -3
  213. data/lib/active_record/validations/length.rb +24 -0
  214. data/lib/active_record/validations/presence.rb +11 -12
  215. data/lib/active_record/validations/uniqueness.rb +30 -29
  216. data/lib/rails/generators/active_record/migration.rb +7 -0
  217. data/lib/rails/generators/active_record/migration/migration_generator.rb +7 -4
  218. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +8 -3
  219. data/lib/rails/generators/active_record/migration/templates/migration.rb +8 -1
  220. data/lib/rails/generators/active_record/model/model_generator.rb +32 -15
  221. data/lib/rails/generators/active_record/model/templates/application_record.rb +5 -0
  222. data/lib/rails/generators/active_record/model/templates/model.rb +3 -0
  223. metadata +59 -34
  224. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -498
  225. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  226. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +0 -11
  227. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  228. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
  229. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +0 -11
  230. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  231. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  232. data/lib/active_record/type/big_integer.rb +0 -13
  233. data/lib/active_record/type/binary.rb +0 -50
  234. data/lib/active_record/type/boolean.rb +0 -31
  235. data/lib/active_record/type/decimal.rb +0 -64
  236. data/lib/active_record/type/decimal_without_scale.rb +0 -11
  237. data/lib/active_record/type/decorator.rb +0 -14
  238. data/lib/active_record/type/float.rb +0 -19
  239. data/lib/active_record/type/integer.rb +0 -59
  240. data/lib/active_record/type/mutable.rb +0 -16
  241. data/lib/active_record/type/numeric.rb +0 -36
  242. data/lib/active_record/type/string.rb +0 -40
  243. data/lib/active_record/type/text.rb +0 -11
  244. data/lib/active_record/type/time_value.rb +0 -38
  245. data/lib/active_record/type/unsigned_integer.rb +0 -15
  246. data/lib/active_record/type/value.rb +0 -110
@@ -0,0 +1,32 @@
1
+ module ActiveRecord
2
+ class Relation
3
+ class FromClause # :nodoc:
4
+ attr_reader :value, :name
5
+
6
+ def initialize(value, name)
7
+ @value = value
8
+ @name = name
9
+ end
10
+
11
+ def binds
12
+ if value.is_a?(Relation)
13
+ value.bound_attributes
14
+ else
15
+ []
16
+ end
17
+ end
18
+
19
+ def merge(other)
20
+ self
21
+ end
22
+
23
+ def empty?
24
+ value.nil?
25
+ end
26
+
27
+ def self.empty
28
+ @empty ||= new(nil, nil)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -1,5 +1,4 @@
1
1
  require 'active_support/core_ext/hash/keys'
2
- require "set"
3
2
 
4
3
  module ActiveRecord
5
4
  class Relation
@@ -22,7 +21,7 @@ module ActiveRecord
22
21
  # build a relation to merge in rather than directly merging
23
22
  # the values.
24
23
  def other
25
- other = Relation.create(relation.klass, relation.table)
24
+ other = Relation.create(relation.klass, relation.table, relation.predicate_builder)
26
25
  hash.each { |k, v|
27
26
  if k == :joins
28
27
  if Hash === v
@@ -49,10 +48,9 @@ module ActiveRecord
49
48
  @other = other
50
49
  end
51
50
 
52
- NORMAL_VALUES = Relation::SINGLE_VALUE_METHODS +
53
- Relation::MULTI_VALUE_METHODS -
54
- [:includes, :preload, :joins, :where, :order, :bind, :reverse_order, :lock, :create_with, :reordering, :from] # :nodoc:
55
-
51
+ NORMAL_VALUES = Relation::VALUE_METHODS -
52
+ Relation::CLAUSE_METHODS -
53
+ [:includes, :preload, :joins, :order, :reverse_order, :lock, :create_with, :reordering] # :nodoc:
56
54
 
57
55
  def normal_values
58
56
  NORMAL_VALUES
@@ -76,6 +74,7 @@ module ActiveRecord
76
74
 
77
75
  merge_multi_values
78
76
  merge_single_values
77
+ merge_clauses
79
78
  merge_preloads
80
79
  merge_joins
81
80
 
@@ -130,20 +129,6 @@ module ActiveRecord
130
129
  end
131
130
 
132
131
  def merge_multi_values
133
- lhs_wheres = relation.where_values
134
- rhs_wheres = other.where_values
135
-
136
- lhs_binds = relation.bind_values
137
- rhs_binds = other.bind_values
138
-
139
- removed, kept = partition_overwrites(lhs_wheres, rhs_wheres)
140
-
141
- where_values = kept + rhs_wheres
142
- bind_values = filter_binds(lhs_binds, removed) + rhs_binds
143
-
144
- relation.where_values = where_values
145
- relation.bind_values = bind_values
146
-
147
132
  if other.reordering_value
148
133
  # override any order specified in the original relation
149
134
  relation.reorder! other.order_values
@@ -156,36 +141,25 @@ module ActiveRecord
156
141
  end
157
142
 
158
143
  def merge_single_values
159
- relation.from_value = other.from_value unless relation.from_value
160
- relation.lock_value = other.lock_value unless relation.lock_value
144
+ if relation.from_clause.empty?
145
+ relation.from_clause = other.from_clause
146
+ end
147
+ relation.lock_value ||= other.lock_value
161
148
 
162
149
  unless other.create_with_value.blank?
163
150
  relation.create_with_value = (relation.create_with_value || {}).merge(other.create_with_value)
164
151
  end
165
152
  end
166
153
 
167
- def filter_binds(lhs_binds, removed_wheres)
168
- return lhs_binds if removed_wheres.empty?
169
-
170
- set = Set.new removed_wheres.map { |x| x.left.name.to_s }
171
- lhs_binds.dup.delete_if { |col,_| set.include? col.name }
154
+ CLAUSE_METHOD_NAMES = CLAUSE_METHODS.map do |name|
155
+ ["#{name}_clause", "#{name}_clause="]
172
156
  end
173
157
 
174
- # Remove equalities from the existing relation with a LHS which is
175
- # present in the relation being merged in.
176
- # returns [things_to_remove, things_to_keep]
177
- def partition_overwrites(lhs_wheres, rhs_wheres)
178
- if lhs_wheres.empty? || rhs_wheres.empty?
179
- return [[], lhs_wheres]
180
- end
181
-
182
- nodes = rhs_wheres.find_all do |w|
183
- w.respond_to?(:operator) && w.operator == :==
184
- end
185
- seen = Set.new(nodes) { |node| node.left }
186
-
187
- lhs_wheres.partition do |w|
188
- w.respond_to?(:operator) && w.operator == :== && seen.include?(w.left)
158
+ def merge_clauses
159
+ CLAUSE_METHOD_NAMES.each do |(reader, writer)|
160
+ clause = relation.send(reader)
161
+ other_clause = other.send(reader)
162
+ relation.send(writer, clause.merge(other_clause))
189
163
  end
190
164
  end
191
165
  end
@@ -1,91 +1,49 @@
1
1
  module ActiveRecord
2
2
  class PredicateBuilder # :nodoc:
3
- @handlers = []
4
-
5
- autoload :RelationHandler, 'active_record/relation/predicate_builder/relation_handler'
6
- autoload :ArrayHandler, 'active_record/relation/predicate_builder/array_handler'
7
-
8
- def self.resolve_column_aliases(klass, hash)
9
- # This method is a hot spot, so for now, use Hash[] to dup the hash.
10
- # https://bugs.ruby-lang.org/issues/7166
11
- hash = Hash[hash]
12
- hash.keys.grep(Symbol) do |key|
13
- if klass.attribute_alias? key
14
- hash[klass.attribute_alias(key)] = hash.delete key
15
- end
16
- end
17
- hash
3
+ require 'active_record/relation/predicate_builder/array_handler'
4
+ require 'active_record/relation/predicate_builder/association_query_handler'
5
+ require 'active_record/relation/predicate_builder/base_handler'
6
+ require 'active_record/relation/predicate_builder/basic_object_handler'
7
+ require 'active_record/relation/predicate_builder/class_handler'
8
+ require 'active_record/relation/predicate_builder/polymorphic_array_handler'
9
+ require 'active_record/relation/predicate_builder/range_handler'
10
+ require 'active_record/relation/predicate_builder/relation_handler'
11
+
12
+ delegate :resolve_column_aliases, to: :table
13
+
14
+ def initialize(table)
15
+ @table = table
16
+ @handlers = []
17
+
18
+ register_handler(BasicObject, BasicObjectHandler.new(self))
19
+ register_handler(Class, ClassHandler.new(self))
20
+ register_handler(Base, BaseHandler.new(self))
21
+ register_handler(Range, RangeHandler.new(self))
22
+ register_handler(RangeHandler::RangeWithBinds, RangeHandler.new(self))
23
+ register_handler(Relation, RelationHandler.new)
24
+ register_handler(Array, ArrayHandler.new(self))
25
+ register_handler(AssociationQueryValue, AssociationQueryHandler.new(self))
26
+ register_handler(PolymorphicArrayValue, PolymorphicArrayHandler.new(self))
18
27
  end
19
28
 
20
- def self.build_from_hash(klass, attributes, default_table)
21
- queries = []
22
-
23
- attributes.each do |column, value|
24
- table = default_table
25
-
26
- if value.is_a?(Hash)
27
- if value.empty?
28
- queries << '1=0'
29
- else
30
- table = Arel::Table.new(column, default_table.engine)
31
- association = klass._reflect_on_association(column)
32
-
33
- value.each do |k, v|
34
- queries.concat expand(association && association.klass, table, k, v)
35
- end
36
- end
37
- else
38
- column = column.to_s
39
-
40
- if column.include?('.')
41
- table_name, column = column.split('.', 2)
42
- table = Arel::Table.new(table_name, default_table.engine)
43
- end
44
-
45
- queries.concat expand(klass, table, column, value)
46
- end
47
- end
48
-
49
- queries
29
+ def build_from_hash(attributes)
30
+ attributes = convert_dot_notation_to_hash(attributes)
31
+ expand_from_hash(attributes)
50
32
  end
51
33
 
52
- def self.expand(klass, table, column, value)
53
- queries = []
34
+ def create_binds(attributes)
35
+ attributes = convert_dot_notation_to_hash(attributes)
36
+ create_binds_for_hash(attributes)
37
+ end
54
38
 
39
+ def expand(column, value)
55
40
  # Find the foreign key when using queries such as:
56
41
  # Post.where(author: author)
57
42
  #
58
43
  # For polymorphic relationships, find the foreign key and type:
59
44
  # PriceEstimate.where(estimate_of: treasure)
60
- if klass && reflection = klass._reflect_on_association(column)
61
- base_class = polymorphic_base_class_from_value(value)
62
-
63
- if reflection.polymorphic? && base_class
64
- queries << build(table[reflection.foreign_type], base_class)
65
- end
66
-
67
- column = reflection.foreign_key
68
-
69
- if base_class
70
- primary_key = reflection.association_primary_key(base_class)
71
- value = convert_value_to_association_ids(value, primary_key)
72
- end
73
- end
74
-
75
- queries << build(table[column], value)
76
- queries
77
- end
78
-
79
- def self.polymorphic_base_class_from_value(value)
80
- case value
81
- when Relation
82
- value.klass.base_class
83
- when Array
84
- val = value.compact.first
85
- val.class.base_class if val.is_a?(Base)
86
- when Base
87
- value.class.base_class
88
- end
45
+ value = AssociationQueryHandler.value_for(table, column, value) if table.associated_with?(column)
46
+ build(table.arel_attribute(column), value)
89
47
  end
90
48
 
91
49
  def self.references(attributes)
@@ -94,7 +52,7 @@ module ActiveRecord
94
52
  key
95
53
  else
96
54
  key = key.to_s
97
- key.split('.').first if key.include?('.')
55
+ key.split('.'.freeze).first if key.include?('.'.freeze)
98
56
  end
99
57
  end.compact
100
58
  end
@@ -109,47 +67,102 @@ module ActiveRecord
109
67
  # Arel::Nodes::And.new([range.start, range.end])
110
68
  # )
111
69
  # end
112
- # ActiveRecord::PredicateBuilder.register_handler(MyCustomDateRange, handler)
113
- def self.register_handler(klass, handler)
70
+ # ActiveRecord::PredicateBuilder.new("users").register_handler(MyCustomDateRange, handler)
71
+ def register_handler(klass, handler)
114
72
  @handlers.unshift([klass, handler])
115
73
  end
116
74
 
117
- BASIC_OBJECT_HANDLER = ->(attribute, value) { attribute.eq(value) } # :nodoc:
118
- register_handler(BasicObject, BASIC_OBJECT_HANDLER)
119
- # FIXME: I think we need to deprecate this behavior
120
- register_handler(Class, ->(attribute, value) { attribute.eq(value.name) })
121
- register_handler(Base, ->(attribute, value) { attribute.eq(value.id) })
122
- register_handler(Range, ->(attribute, value) { attribute.between(value) })
123
- register_handler(Relation, RelationHandler.new)
124
- register_handler(Array, ArrayHandler.new)
125
-
126
- def self.build(attribute, value)
75
+ def build(attribute, value)
127
76
  handler_for(value).call(attribute, value)
128
77
  end
129
- private_class_method :build
130
78
 
131
- def self.handler_for(object)
132
- @handlers.detect { |klass, _| klass === object }.last
79
+ protected
80
+
81
+ attr_reader :table
82
+
83
+ def expand_from_hash(attributes)
84
+ return ["1=0"] if attributes.empty?
85
+
86
+ attributes.flat_map do |key, value|
87
+ if value.is_a?(Hash)
88
+ associated_predicate_builder(key).expand_from_hash(value)
89
+ else
90
+ expand(key, value)
91
+ end
92
+ end
93
+ end
94
+
95
+
96
+ def create_binds_for_hash(attributes)
97
+ result = attributes.dup
98
+ binds = []
99
+
100
+ attributes.each do |column_name, value|
101
+ case value
102
+ when Hash
103
+ attrs, bvs = associated_predicate_builder(column_name).create_binds_for_hash(value)
104
+ result[column_name] = attrs
105
+ binds += bvs
106
+ when Relation
107
+ binds += value.bound_attributes
108
+ when Range
109
+ first = value.begin
110
+ last = value.end
111
+ unless first.respond_to?(:infinite?) && first.infinite?
112
+ binds << build_bind_param(column_name, first)
113
+ first = Arel::Nodes::BindParam.new
114
+ end
115
+ unless last.respond_to?(:infinite?) && last.infinite?
116
+ binds << build_bind_param(column_name, last)
117
+ last = Arel::Nodes::BindParam.new
118
+ end
119
+
120
+ result[column_name] = RangeHandler::RangeWithBinds.new(first, last, value.exclude_end?)
121
+ else
122
+ if can_be_bound?(column_name, value)
123
+ result[column_name] = Arel::Nodes::BindParam.new
124
+ binds << build_bind_param(column_name, value)
125
+ end
126
+ end
127
+ end
128
+
129
+ [result, binds]
130
+ end
131
+
132
+ private
133
+
134
+ def associated_predicate_builder(association_name)
135
+ self.class.new(table.associated_table(association_name))
133
136
  end
134
- private_class_method :handler_for
135
-
136
- def self.convert_value_to_association_ids(value, primary_key)
137
- case value
138
- when Relation
139
- value.select(primary_key)
140
- when Array
141
- value.map { |v| convert_value_to_association_ids(v, primary_key) }
142
- when Base
143
- value._read_attribute(primary_key)
144
- else
145
- value
137
+
138
+ def convert_dot_notation_to_hash(attributes)
139
+ dot_notation = attributes.select do |k, v|
140
+ k.include?(".".freeze) && !v.is_a?(Hash)
141
+ end
142
+
143
+ dot_notation.each_key do |key|
144
+ table_name, column_name = key.split(".".freeze)
145
+ value = attributes.delete(key)
146
+ attributes[table_name] ||= {}
147
+
148
+ attributes[table_name] = attributes[table_name].merge(column_name => value)
146
149
  end
150
+
151
+ attributes
147
152
  end
148
153
 
149
- def self.can_be_bound?(value) # :nodoc:
154
+ def handler_for(object)
155
+ @handlers.detect { |klass, _| klass === object }.last
156
+ end
157
+
158
+ def can_be_bound?(column_name, value)
150
159
  !value.nil? &&
151
- !value.is_a?(Hash) &&
152
- handler_for(value) == BASIC_OBJECT_HANDLER
160
+ handler_for(value).is_a?(BasicObjectHandler) &&
161
+ !table.associated_with?(column_name)
162
+ end
163
+
164
+ def build_bind_param(column_name, value)
165
+ Relation::QueryAttribute.new(column_name.to_s, value, table.type(column_name))
153
166
  end
154
167
  end
155
168
  end
@@ -1,23 +1,14 @@
1
- require 'active_support/core_ext/string/filters'
2
-
3
1
  module ActiveRecord
4
2
  class PredicateBuilder
5
3
  class ArrayHandler # :nodoc:
4
+ def initialize(predicate_builder)
5
+ @predicate_builder = predicate_builder
6
+ end
7
+
6
8
  def call(attribute, value)
7
9
  values = value.map { |x| x.is_a?(Base) ? x.id : x }
8
10
  nils, values = values.partition(&:nil?)
9
11
 
10
- if values.any? { |val| val.is_a?(Array) }
11
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
12
- Passing a nested array to Active Record finder methods is
13
- deprecated and will be removed. Flatten your array before using
14
- it for 'IN' conditions.
15
- MSG
16
-
17
- flat_values = values.flatten
18
- values = flat_values unless flat_values.include?(nil)
19
- end
20
-
21
12
  return attribute.in([]) if values.empty? && nils.empty?
22
13
 
23
14
  ranges, values = values.partition { |v| v.is_a?(Range) }
@@ -25,19 +16,23 @@ module ActiveRecord
25
16
  values_predicate =
26
17
  case values.length
27
18
  when 0 then NullPredicate
28
- when 1 then attribute.eq(values.first)
19
+ when 1 then predicate_builder.build(attribute, values.first)
29
20
  else attribute.in(values)
30
21
  end
31
22
 
32
23
  unless nils.empty?
33
- values_predicate = values_predicate.or(attribute.eq(nil))
24
+ values_predicate = values_predicate.or(predicate_builder.build(attribute, nil))
34
25
  end
35
26
 
36
- array_predicates = ranges.map { |range| attribute.between(range) }
27
+ array_predicates = ranges.map { |range| predicate_builder.build(attribute, range) }
37
28
  array_predicates.unshift(values_predicate)
38
29
  array_predicates.inject { |composite, predicate| composite.or(predicate) }
39
30
  end
40
31
 
32
+ protected
33
+
34
+ attr_reader :predicate_builder
35
+
41
36
  module NullPredicate # :nodoc:
42
37
  def self.or(other)
43
38
  other