activerecord 4.2.11.3 → 5.0.0.1

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 +1281 -1204
  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/aggregations.rb +35 -24
  8. data/lib/active_record/association_relation.rb +3 -3
  9. data/lib/active_record/associations/alias_tracker.rb +19 -16
  10. data/lib/active_record/associations/association.rb +11 -9
  11. data/lib/active_record/associations/association_scope.rb +73 -102
  12. data/lib/active_record/associations/belongs_to_association.rb +21 -32
  13. data/lib/active_record/associations/builder/association.rb +28 -34
  14. data/lib/active_record/associations/builder/belongs_to.rb +43 -18
  15. data/lib/active_record/associations/builder/collection_association.rb +7 -19
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +14 -11
  17. data/lib/active_record/associations/builder/has_many.rb +4 -4
  18. data/lib/active_record/associations/builder/has_one.rb +11 -6
  19. data/lib/active_record/associations/builder/singular_association.rb +3 -10
  20. data/lib/active_record/associations/collection_association.rb +49 -41
  21. data/lib/active_record/associations/collection_proxy.rb +67 -27
  22. data/lib/active_record/associations/foreign_association.rb +1 -1
  23. data/lib/active_record/associations/has_many_association.rb +20 -71
  24. data/lib/active_record/associations/has_many_through_association.rb +8 -47
  25. data/lib/active_record/associations/has_one_association.rb +12 -5
  26. data/lib/active_record/associations/join_dependency/join_association.rb +16 -10
  27. data/lib/active_record/associations/join_dependency.rb +29 -19
  28. data/lib/active_record/associations/preloader/association.rb +46 -52
  29. data/lib/active_record/associations/preloader/collection_association.rb +0 -6
  30. data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
  31. data/lib/active_record/associations/preloader/has_one.rb +0 -8
  32. data/lib/active_record/associations/preloader/through_association.rb +27 -14
  33. data/lib/active_record/associations/preloader.rb +14 -4
  34. data/lib/active_record/associations/singular_association.rb +7 -1
  35. data/lib/active_record/associations/through_association.rb +11 -3
  36. data/lib/active_record/associations.rb +317 -209
  37. data/lib/active_record/attribute/user_provided_default.rb +28 -0
  38. data/lib/active_record/attribute.rb +68 -18
  39. data/lib/active_record/attribute_assignment.rb +19 -140
  40. data/lib/active_record/attribute_decorators.rb +6 -5
  41. data/lib/active_record/attribute_methods/before_type_cast.rb +1 -1
  42. data/lib/active_record/attribute_methods/dirty.rb +46 -86
  43. data/lib/active_record/attribute_methods/primary_key.rb +2 -2
  44. data/lib/active_record/attribute_methods/query.rb +2 -2
  45. data/lib/active_record/attribute_methods/read.rb +31 -59
  46. data/lib/active_record/attribute_methods/serialization.rb +13 -16
  47. data/lib/active_record/attribute_methods/time_zone_conversion.rb +61 -14
  48. data/lib/active_record/attribute_methods/write.rb +13 -37
  49. data/lib/active_record/attribute_methods.rb +76 -47
  50. data/lib/active_record/attribute_mutation_tracker.rb +70 -0
  51. data/lib/active_record/attribute_set/builder.rb +6 -4
  52. data/lib/active_record/attribute_set.rb +30 -3
  53. data/lib/active_record/attributes.rb +199 -81
  54. data/lib/active_record/autosave_association.rb +49 -16
  55. data/lib/active_record/base.rb +32 -23
  56. data/lib/active_record/callbacks.rb +39 -43
  57. data/lib/active_record/coders/json.rb +1 -1
  58. data/lib/active_record/coders/yaml_column.rb +20 -8
  59. data/lib/active_record/collection_cache_key.rb +40 -0
  60. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +452 -182
  61. data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -3
  62. data/lib/active_record/connection_adapters/abstract/database_statements.rb +65 -61
  63. data/lib/active_record/connection_adapters/abstract/query_cache.rb +2 -2
  64. data/lib/active_record/connection_adapters/abstract/quoting.rb +74 -10
  65. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  66. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +61 -39
  67. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +236 -185
  68. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +72 -17
  69. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +380 -141
  70. data/lib/active_record/connection_adapters/abstract/transaction.rb +51 -34
  71. data/lib/active_record/connection_adapters/abstract_adapter.rb +141 -59
  72. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +401 -370
  73. data/lib/active_record/connection_adapters/column.rb +28 -43
  74. data/lib/active_record/connection_adapters/connection_specification.rb +15 -27
  75. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
  76. data/lib/active_record/connection_adapters/mysql/column.rb +50 -0
  77. data/lib/active_record/connection_adapters/mysql/database_statements.rb +125 -0
  78. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +70 -0
  79. data/lib/active_record/connection_adapters/mysql/quoting.rb +51 -0
  80. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +67 -0
  81. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +93 -0
  82. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +54 -0
  83. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +32 -0
  84. data/lib/active_record/connection_adapters/mysql2_adapter.rb +29 -166
  85. data/lib/active_record/connection_adapters/postgresql/column.rb +5 -10
  86. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +10 -72
  87. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +27 -57
  89. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +2 -2
  90. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +1 -1
  91. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -1
  92. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +7 -22
  93. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +3 -3
  94. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +1 -26
  95. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +2 -2
  96. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +0 -4
  97. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +4 -4
  98. data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +31 -17
  100. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +0 -4
  101. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +2 -2
  102. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +1 -1
  103. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +1 -1
  104. data/lib/active_record/connection_adapters/postgresql/oid.rb +1 -6
  105. data/lib/active_record/connection_adapters/postgresql/quoting.rb +26 -18
  106. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +29 -10
  107. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -79
  108. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +47 -0
  109. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +234 -148
  110. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
  111. data/lib/active_record/connection_adapters/postgresql_adapter.rb +248 -160
  112. data/lib/active_record/connection_adapters/schema_cache.rb +36 -23
  113. data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
  114. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +19 -0
  115. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +48 -0
  116. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  117. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +149 -192
  118. data/lib/active_record/connection_adapters/statement_pool.rb +31 -12
  119. data/lib/active_record/connection_handling.rb +37 -14
  120. data/lib/active_record/core.rb +89 -107
  121. data/lib/active_record/counter_cache.rb +13 -24
  122. data/lib/active_record/dynamic_matchers.rb +1 -20
  123. data/lib/active_record/enum.rb +113 -76
  124. data/lib/active_record/errors.rb +87 -48
  125. data/lib/active_record/explain_registry.rb +1 -1
  126. data/lib/active_record/explain_subscriber.rb +1 -1
  127. data/lib/active_record/fixture_set/file.rb +26 -5
  128. data/lib/active_record/fixtures.rb +76 -40
  129. data/lib/active_record/gem_version.rb +4 -4
  130. data/lib/active_record/inheritance.rb +32 -40
  131. data/lib/active_record/integration.rb +4 -4
  132. data/lib/active_record/internal_metadata.rb +56 -0
  133. data/lib/active_record/legacy_yaml_adapter.rb +18 -2
  134. data/lib/active_record/locale/en.yml +3 -2
  135. data/lib/active_record/locking/optimistic.rb +15 -15
  136. data/lib/active_record/locking/pessimistic.rb +1 -1
  137. data/lib/active_record/log_subscriber.rb +43 -21
  138. data/lib/active_record/migration/command_recorder.rb +59 -18
  139. data/lib/active_record/migration/compatibility.rb +126 -0
  140. data/lib/active_record/migration.rb +363 -133
  141. data/lib/active_record/model_schema.rb +129 -41
  142. data/lib/active_record/nested_attributes.rb +58 -29
  143. data/lib/active_record/null_relation.rb +16 -8
  144. data/lib/active_record/persistence.rb +121 -80
  145. data/lib/active_record/query_cache.rb +15 -18
  146. data/lib/active_record/querying.rb +10 -9
  147. data/lib/active_record/railtie.rb +23 -16
  148. data/lib/active_record/railties/controller_runtime.rb +1 -1
  149. data/lib/active_record/railties/databases.rake +69 -46
  150. data/lib/active_record/readonly_attributes.rb +1 -1
  151. data/lib/active_record/reflection.rb +282 -115
  152. data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
  153. data/lib/active_record/relation/batches.rb +139 -34
  154. data/lib/active_record/relation/calculations.rb +79 -108
  155. data/lib/active_record/relation/delegation.rb +7 -20
  156. data/lib/active_record/relation/finder_methods.rb +163 -81
  157. data/lib/active_record/relation/from_clause.rb +32 -0
  158. data/lib/active_record/relation/merger.rb +16 -42
  159. data/lib/active_record/relation/predicate_builder/array_handler.rb +11 -16
  160. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +88 -0
  161. data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
  162. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
  163. data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
  164. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +57 -0
  165. data/lib/active_record/relation/predicate_builder/range_handler.rb +33 -0
  166. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  167. data/lib/active_record/relation/predicate_builder.rb +120 -107
  168. data/lib/active_record/relation/query_attribute.rb +19 -0
  169. data/lib/active_record/relation/query_methods.rb +308 -244
  170. data/lib/active_record/relation/record_fetch_warning.rb +49 -0
  171. data/lib/active_record/relation/spawn_methods.rb +4 -7
  172. data/lib/active_record/relation/where_clause.rb +174 -0
  173. data/lib/active_record/relation/where_clause_factory.rb +38 -0
  174. data/lib/active_record/relation.rb +176 -116
  175. data/lib/active_record/result.rb +4 -3
  176. data/lib/active_record/runtime_registry.rb +1 -1
  177. data/lib/active_record/sanitization.rb +95 -66
  178. data/lib/active_record/schema.rb +26 -22
  179. data/lib/active_record/schema_dumper.rb +62 -38
  180. data/lib/active_record/schema_migration.rb +11 -14
  181. data/lib/active_record/scoping/default.rb +23 -9
  182. data/lib/active_record/scoping/named.rb +49 -28
  183. data/lib/active_record/scoping.rb +32 -15
  184. data/lib/active_record/secure_token.rb +38 -0
  185. data/lib/active_record/serialization.rb +2 -4
  186. data/lib/active_record/statement_cache.rb +16 -14
  187. data/lib/active_record/store.rb +8 -3
  188. data/lib/active_record/suppressor.rb +58 -0
  189. data/lib/active_record/table_metadata.rb +68 -0
  190. data/lib/active_record/tasks/database_tasks.rb +57 -43
  191. data/lib/active_record/tasks/mysql_database_tasks.rb +6 -14
  192. data/lib/active_record/tasks/postgresql_database_tasks.rb +11 -2
  193. data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
  194. data/lib/active_record/timestamp.rb +20 -9
  195. data/lib/active_record/touch_later.rb +58 -0
  196. data/lib/active_record/transactions.rb +138 -56
  197. data/lib/active_record/type/adapter_specific_registry.rb +130 -0
  198. data/lib/active_record/type/date.rb +2 -45
  199. data/lib/active_record/type/date_time.rb +2 -49
  200. data/lib/active_record/type/internal/abstract_json.rb +29 -0
  201. data/lib/active_record/type/internal/timezone.rb +15 -0
  202. data/lib/active_record/type/serialized.rb +15 -14
  203. data/lib/active_record/type/time.rb +10 -16
  204. data/lib/active_record/type/type_map.rb +4 -4
  205. data/lib/active_record/type.rb +66 -17
  206. data/lib/active_record/type_caster/connection.rb +29 -0
  207. data/lib/active_record/type_caster/map.rb +19 -0
  208. data/lib/active_record/type_caster.rb +7 -0
  209. data/lib/active_record/validations/absence.rb +23 -0
  210. data/lib/active_record/validations/associated.rb +10 -3
  211. data/lib/active_record/validations/length.rb +24 -0
  212. data/lib/active_record/validations/presence.rb +11 -12
  213. data/lib/active_record/validations/uniqueness.rb +30 -29
  214. data/lib/active_record/validations.rb +33 -32
  215. data/lib/active_record.rb +8 -4
  216. data/lib/rails/generators/active_record/migration/migration_generator.rb +7 -4
  217. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +8 -3
  218. data/lib/rails/generators/active_record/migration/templates/migration.rb +8 -1
  219. data/lib/rails/generators/active_record/migration.rb +7 -0
  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 +60 -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
@@ -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
@@ -0,0 +1,88 @@
1
+ module ActiveRecord
2
+ class PredicateBuilder
3
+ class AssociationQueryHandler # :nodoc:
4
+ def self.value_for(table, column, value)
5
+ klass = if table.associated_table(column).polymorphic_association? && ::Array === value && value.first.is_a?(Base)
6
+ PolymorphicArrayValue
7
+ else
8
+ AssociationQueryValue
9
+ end
10
+
11
+ klass.new(table.associated_table(column), value)
12
+ end
13
+
14
+ def initialize(predicate_builder)
15
+ @predicate_builder = predicate_builder
16
+ end
17
+
18
+ def call(attribute, value)
19
+ queries = {}
20
+
21
+ table = value.associated_table
22
+ if value.base_class
23
+ queries[table.association_foreign_type.to_s] = value.base_class.name
24
+ end
25
+
26
+ queries[table.association_foreign_key.to_s] = value.ids
27
+ predicate_builder.build_from_hash(queries)
28
+ end
29
+
30
+ protected
31
+
32
+ attr_reader :predicate_builder
33
+ end
34
+
35
+ class AssociationQueryValue # :nodoc:
36
+ attr_reader :associated_table, :value
37
+
38
+ def initialize(associated_table, value)
39
+ @associated_table = associated_table
40
+ @value = value
41
+ end
42
+
43
+ def ids
44
+ case value
45
+ when Relation
46
+ value.select(primary_key)
47
+ when Array
48
+ value.map { |v| convert_to_id(v) }
49
+ else
50
+ convert_to_id(value)
51
+ end
52
+ end
53
+
54
+ def base_class
55
+ if associated_table.polymorphic_association?
56
+ @base_class ||= polymorphic_base_class_from_value
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def primary_key
63
+ associated_table.association_primary_key(base_class)
64
+ end
65
+
66
+ def polymorphic_base_class_from_value
67
+ case value
68
+ when Relation
69
+ value.klass.base_class
70
+ when Array
71
+ val = value.compact.first
72
+ val.class.base_class if val.is_a?(Base)
73
+ when Base
74
+ value.class.base_class
75
+ end
76
+ end
77
+
78
+ def convert_to_id(value)
79
+ case value
80
+ when Base
81
+ value._read_attribute(primary_key)
82
+ else
83
+ value
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,17 @@
1
+ module ActiveRecord
2
+ class PredicateBuilder
3
+ class BaseHandler # :nodoc:
4
+ def initialize(predicate_builder)
5
+ @predicate_builder = predicate_builder
6
+ end
7
+
8
+ def call(attribute, value)
9
+ predicate_builder.build(attribute, value.id)
10
+ end
11
+
12
+ protected
13
+
14
+ attr_reader :predicate_builder
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ module ActiveRecord
2
+ class PredicateBuilder
3
+ class BasicObjectHandler # :nodoc:
4
+ def initialize(predicate_builder)
5
+ @predicate_builder = predicate_builder
6
+ end
7
+
8
+ def call(attribute, value)
9
+ attribute.eq(value)
10
+ end
11
+
12
+ protected
13
+
14
+ attr_reader :predicate_builder
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,27 @@
1
+ module ActiveRecord
2
+ class PredicateBuilder
3
+ class ClassHandler # :nodoc:
4
+ def initialize(predicate_builder)
5
+ @predicate_builder = predicate_builder
6
+ end
7
+
8
+ def call(attribute, value)
9
+ print_deprecation_warning
10
+ predicate_builder.build(attribute, value.name)
11
+ end
12
+
13
+ protected
14
+
15
+ attr_reader :predicate_builder
16
+
17
+ private
18
+
19
+ def print_deprecation_warning
20
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
21
+ Passing a class as a value in an Active Record query is deprecated and
22
+ will be removed. Pass a string instead.
23
+ MSG
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,57 @@
1
+ module ActiveRecord
2
+ class PredicateBuilder
3
+ class PolymorphicArrayHandler # :nodoc:
4
+ def initialize(predicate_builder)
5
+ @predicate_builder = predicate_builder
6
+ end
7
+
8
+ def call(attribute, value)
9
+ table = value.associated_table
10
+ queries = value.type_to_ids_mapping.map do |type, ids|
11
+ { table.association_foreign_type.to_s => type, table.association_foreign_key.to_s => ids }
12
+ end
13
+
14
+ predicates = queries.map { |query| predicate_builder.build_from_hash(query) }
15
+
16
+ if predicates.size > 1
17
+ type_and_ids_predicates = predicates.map { |type_predicate, id_predicate| Arel::Nodes::Grouping.new(type_predicate.and(id_predicate)) }
18
+ type_and_ids_predicates.inject(&:or)
19
+ else
20
+ predicates.first
21
+ end
22
+ end
23
+
24
+ protected
25
+
26
+ attr_reader :predicate_builder
27
+ end
28
+
29
+ class PolymorphicArrayValue # :nodoc:
30
+ attr_reader :associated_table, :values
31
+
32
+ def initialize(associated_table, values)
33
+ @associated_table = associated_table
34
+ @values = values
35
+ end
36
+
37
+ def type_to_ids_mapping
38
+ default_hash = Hash.new { |hsh, key| hsh[key] = [] }
39
+ values.each_with_object(default_hash) { |value, hash| hash[base_class(value).name] << convert_to_id(value) }
40
+ end
41
+
42
+ private
43
+
44
+ def primary_key(value)
45
+ associated_table.association_primary_key(base_class(value))
46
+ end
47
+
48
+ def base_class(value)
49
+ value.class.base_class
50
+ end
51
+
52
+ def convert_to_id(value)
53
+ value._read_attribute(primary_key(value))
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,33 @@
1
+ module ActiveRecord
2
+ class PredicateBuilder
3
+ class RangeHandler # :nodoc:
4
+ RangeWithBinds = Struct.new(:begin, :end, :exclude_end?)
5
+
6
+ def initialize(predicate_builder)
7
+ @predicate_builder = predicate_builder
8
+ end
9
+
10
+ def call(attribute, value)
11
+ if value.begin.respond_to?(:infinite?) && value.begin.infinite?
12
+ if value.end.respond_to?(:infinite?) && value.end.infinite?
13
+ attribute.not_in([])
14
+ elsif value.exclude_end?
15
+ attribute.lt(value.end)
16
+ else
17
+ attribute.lteq(value.end)
18
+ end
19
+ elsif value.end.respond_to?(:infinite?) && value.end.infinite?
20
+ attribute.gteq(value.begin)
21
+ elsif value.exclude_end?
22
+ attribute.gteq(value.begin).and(attribute.lt(value.end))
23
+ else
24
+ attribute.between(value)
25
+ end
26
+ end
27
+
28
+ protected
29
+
30
+ attr_reader :predicate_builder
31
+ end
32
+ end
33
+ end
@@ -3,7 +3,7 @@ module ActiveRecord
3
3
  class RelationHandler # :nodoc:
4
4
  def call(attribute, value)
5
5
  if value.select_values.empty?
6
- value = value.select(value.klass.arel_table[value.klass.primary_key])
6
+ value = value.select(value.arel_attribute(value.klass.primary_key))
7
7
  end
8
8
 
9
9
  attribute.in(value.arel)
@@ -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
@@ -0,0 +1,19 @@
1
+ require 'active_record/attribute'
2
+
3
+ module ActiveRecord
4
+ class Relation
5
+ class QueryAttribute < Attribute # :nodoc:
6
+ def type_cast(value)
7
+ value
8
+ end
9
+
10
+ def value_for_database
11
+ @value_for_database ||= super
12
+ end
13
+
14
+ def with_cast_value(value)
15
+ QueryAttribute.new(name, value, type)
16
+ end
17
+ end
18
+ end
19
+ end