activerecord 4.2.0 → 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 (249) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1537 -789
  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 +37 -23
  8. data/lib/active_record/association_relation.rb +16 -3
  9. data/lib/active_record/associations/alias_tracker.rb +19 -16
  10. data/lib/active_record/associations/association.rb +23 -9
  11. data/lib/active_record/associations/association_scope.rb +74 -102
  12. data/lib/active_record/associations/belongs_to_association.rb +26 -29
  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 +12 -20
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +22 -15
  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 +61 -33
  21. data/lib/active_record/associations/collection_proxy.rb +81 -35
  22. data/lib/active_record/associations/foreign_association.rb +11 -0
  23. data/lib/active_record/associations/has_many_association.rb +21 -57
  24. data/lib/active_record/associations/has_many_through_association.rb +15 -45
  25. data/lib/active_record/associations/has_one_association.rb +13 -5
  26. data/lib/active_record/associations/join_dependency/join_association.rb +20 -8
  27. data/lib/active_record/associations/join_dependency.rb +37 -21
  28. data/lib/active_record/associations/preloader/association.rb +51 -53
  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 +18 -8
  34. data/lib/active_record/associations/singular_association.rb +8 -8
  35. data/lib/active_record/associations/through_association.rb +22 -9
  36. data/lib/active_record/associations.rb +321 -212
  37. data/lib/active_record/attribute/user_provided_default.rb +28 -0
  38. data/lib/active_record/attribute.rb +79 -15
  39. data/lib/active_record/attribute_assignment.rb +20 -141
  40. data/lib/active_record/attribute_decorators.rb +6 -5
  41. data/lib/active_record/attribute_methods/before_type_cast.rb +6 -1
  42. data/lib/active_record/attribute_methods/dirty.rb +51 -81
  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 +65 -14
  48. data/lib/active_record/attribute_methods/write.rb +14 -38
  49. data/lib/active_record/attribute_methods.rb +70 -45
  50. data/lib/active_record/attribute_mutation_tracker.rb +70 -0
  51. data/lib/active_record/attribute_set/builder.rb +37 -15
  52. data/lib/active_record/attribute_set.rb +34 -3
  53. data/lib/active_record/attributes.rb +199 -73
  54. data/lib/active_record/autosave_association.rb +73 -25
  55. data/lib/active_record/base.rb +35 -27
  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 +457 -181
  61. data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -3
  62. data/lib/active_record/connection_adapters/abstract/database_statements.rb +83 -59
  63. data/lib/active_record/connection_adapters/abstract/query_cache.rb +3 -3
  64. data/lib/active_record/connection_adapters/abstract/quoting.rb +74 -9
  65. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -4
  66. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +61 -39
  67. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +246 -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 +438 -136
  70. data/lib/active_record/connection_adapters/abstract/transaction.rb +53 -40
  71. data/lib/active_record/connection_adapters/abstract_adapter.rb +166 -66
  72. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +429 -335
  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 +26 -177
  85. data/lib/active_record/connection_adapters/postgresql/column.rb +5 -10
  86. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +11 -73
  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 -56
  89. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +2 -2
  90. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +2 -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 -13
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +3 -1
  94. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +3 -3
  95. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +1 -26
  96. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +2 -2
  97. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +0 -4
  98. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +4 -4
  99. data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +31 -17
  101. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +17 -5
  102. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +2 -2
  103. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +1 -1
  104. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +1 -1
  105. data/lib/active_record/connection_adapters/postgresql/oid.rb +1 -6
  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 +248 -154
  111. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
  112. data/lib/active_record/connection_adapters/postgresql_adapter.rb +258 -170
  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 +150 -209
  119. data/lib/active_record/connection_adapters/statement_pool.rb +31 -12
  120. data/lib/active_record/connection_handling.rb +38 -15
  121. data/lib/active_record/core.rb +109 -114
  122. data/lib/active_record/counter_cache.rb +14 -25
  123. data/lib/active_record/dynamic_matchers.rb +1 -20
  124. data/lib/active_record/enum.rb +115 -79
  125. data/lib/active_record/errors.rb +88 -48
  126. data/lib/active_record/explain_registry.rb +1 -1
  127. data/lib/active_record/explain_subscriber.rb +2 -2
  128. data/lib/active_record/fixture_set/file.rb +26 -5
  129. data/lib/active_record/fixtures.rb +84 -46
  130. data/lib/active_record/gem_version.rb +2 -2
  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 +46 -0
  135. data/lib/active_record/locale/en.yml +3 -2
  136. data/lib/active_record/locking/optimistic.rb +27 -25
  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/command_recorder.rb +59 -18
  140. data/lib/active_record/migration/compatibility.rb +126 -0
  141. data/lib/active_record/migration.rb +372 -114
  142. data/lib/active_record/model_schema.rb +128 -38
  143. data/lib/active_record/nested_attributes.rb +71 -32
  144. data/lib/active_record/no_touching.rb +1 -1
  145. data/lib/active_record/null_relation.rb +16 -8
  146. data/lib/active_record/persistence.rb +124 -80
  147. data/lib/active_record/query_cache.rb +15 -18
  148. data/lib/active_record/querying.rb +10 -9
  149. data/lib/active_record/railtie.rb +28 -19
  150. data/lib/active_record/railties/controller_runtime.rb +1 -1
  151. data/lib/active_record/railties/databases.rake +67 -51
  152. data/lib/active_record/readonly_attributes.rb +1 -1
  153. data/lib/active_record/reflection.rb +318 -139
  154. data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
  155. data/lib/active_record/relation/batches.rb +139 -34
  156. data/lib/active_record/relation/calculations.rb +80 -102
  157. data/lib/active_record/relation/delegation.rb +7 -20
  158. data/lib/active_record/relation/finder_methods.rb +167 -97
  159. data/lib/active_record/relation/from_clause.rb +32 -0
  160. data/lib/active_record/relation/merger.rb +38 -41
  161. data/lib/active_record/relation/predicate_builder/array_handler.rb +12 -16
  162. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +88 -0
  163. data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
  164. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
  165. data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
  166. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +57 -0
  167. data/lib/active_record/relation/predicate_builder/range_handler.rb +33 -0
  168. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  169. data/lib/active_record/relation/predicate_builder.rb +124 -82
  170. data/lib/active_record/relation/query_attribute.rb +19 -0
  171. data/lib/active_record/relation/query_methods.rb +323 -257
  172. data/lib/active_record/relation/record_fetch_warning.rb +49 -0
  173. data/lib/active_record/relation/spawn_methods.rb +11 -10
  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/relation.rb +176 -115
  177. data/lib/active_record/result.rb +4 -3
  178. data/lib/active_record/runtime_registry.rb +1 -1
  179. data/lib/active_record/sanitization.rb +95 -66
  180. data/lib/active_record/schema.rb +26 -22
  181. data/lib/active_record/schema_dumper.rb +62 -38
  182. data/lib/active_record/schema_migration.rb +11 -17
  183. data/lib/active_record/scoping/default.rb +24 -9
  184. data/lib/active_record/scoping/named.rb +49 -28
  185. data/lib/active_record/scoping.rb +32 -15
  186. data/lib/active_record/secure_token.rb +38 -0
  187. data/lib/active_record/serialization.rb +2 -4
  188. data/lib/active_record/statement_cache.rb +16 -14
  189. data/lib/active_record/store.rb +8 -3
  190. data/lib/active_record/suppressor.rb +58 -0
  191. data/lib/active_record/table_metadata.rb +68 -0
  192. data/lib/active_record/tasks/database_tasks.rb +59 -42
  193. data/lib/active_record/tasks/mysql_database_tasks.rb +32 -26
  194. data/lib/active_record/tasks/postgresql_database_tasks.rb +29 -9
  195. data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
  196. data/lib/active_record/timestamp.rb +20 -9
  197. data/lib/active_record/touch_later.rb +58 -0
  198. data/lib/active_record/transactions.rb +159 -67
  199. data/lib/active_record/type/adapter_specific_registry.rb +130 -0
  200. data/lib/active_record/type/date.rb +2 -41
  201. data/lib/active_record/type/date_time.rb +2 -38
  202. data/lib/active_record/type/hash_lookup_type_map.rb +8 -2
  203. data/lib/active_record/type/internal/abstract_json.rb +29 -0
  204. data/lib/active_record/type/internal/timezone.rb +15 -0
  205. data/lib/active_record/type/serialized.rb +21 -14
  206. data/lib/active_record/type/time.rb +10 -16
  207. data/lib/active_record/type/type_map.rb +4 -4
  208. data/lib/active_record/type.rb +66 -17
  209. data/lib/active_record/type_caster/connection.rb +29 -0
  210. data/lib/active_record/type_caster/map.rb +19 -0
  211. data/lib/active_record/type_caster.rb +7 -0
  212. data/lib/active_record/validations/absence.rb +23 -0
  213. data/lib/active_record/validations/associated.rb +10 -3
  214. data/lib/active_record/validations/length.rb +24 -0
  215. data/lib/active_record/validations/presence.rb +11 -12
  216. data/lib/active_record/validations/uniqueness.rb +29 -18
  217. data/lib/active_record/validations.rb +33 -32
  218. data/lib/active_record.rb +9 -2
  219. data/lib/rails/generators/active_record/migration/migration_generator.rb +7 -4
  220. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +8 -6
  221. data/lib/rails/generators/active_record/migration/templates/migration.rb +8 -7
  222. data/lib/rails/generators/active_record/migration.rb +7 -0
  223. data/lib/rails/generators/active_record/model/model_generator.rb +32 -15
  224. data/lib/rails/generators/active_record/model/templates/application_record.rb +5 -0
  225. data/lib/rails/generators/active_record/model/templates/model.rb +3 -0
  226. metadata +60 -34
  227. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -491
  228. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  229. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +0 -11
  230. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  231. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
  232. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +0 -11
  233. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  234. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  235. data/lib/active_record/type/big_integer.rb +0 -13
  236. data/lib/active_record/type/binary.rb +0 -50
  237. data/lib/active_record/type/boolean.rb +0 -30
  238. data/lib/active_record/type/decimal.rb +0 -40
  239. data/lib/active_record/type/decimal_without_scale.rb +0 -11
  240. data/lib/active_record/type/decorator.rb +0 -14
  241. data/lib/active_record/type/float.rb +0 -19
  242. data/lib/active_record/type/integer.rb +0 -55
  243. data/lib/active_record/type/mutable.rb +0 -16
  244. data/lib/active_record/type/numeric.rb +0 -36
  245. data/lib/active_record/type/string.rb +0 -36
  246. data/lib/active_record/type/text.rb +0 -11
  247. data/lib/active_record/type/time_value.rb +0 -38
  248. data/lib/active_record/type/unsigned_integer.rb +0 -15
  249. data/lib/active_record/type/value.rb +0 -101
@@ -1,22 +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
- values = values.flatten
18
- end
19
-
20
12
  return attribute.in([]) if values.empty? && nils.empty?
21
13
 
22
14
  ranges, values = values.partition { |v| v.is_a?(Range) }
@@ -24,20 +16,24 @@ module ActiveRecord
24
16
  values_predicate =
25
17
  case values.length
26
18
  when 0 then NullPredicate
27
- when 1 then attribute.eq(values.first)
19
+ when 1 then predicate_builder.build(attribute, values.first)
28
20
  else attribute.in(values)
29
21
  end
30
22
 
31
23
  unless nils.empty?
32
- values_predicate = values_predicate.or(attribute.eq(nil))
24
+ values_predicate = values_predicate.or(predicate_builder.build(attribute, nil))
33
25
  end
34
26
 
35
- array_predicates = ranges.map { |range| attribute.between(range) }
27
+ array_predicates = ranges.map { |range| predicate_builder.build(attribute, range) }
36
28
  array_predicates.unshift(values_predicate)
37
29
  array_predicates.inject { |composite, predicate| composite.or(predicate) }
38
30
  end
39
31
 
40
- module NullPredicate
32
+ protected
33
+
34
+ attr_reader :predicate_builder
35
+
36
+ module NullPredicate # :nodoc:
41
37
  def self.or(other)
42
38
  other
43
39
  end
@@ -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,82 +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
- hash = hash.dup
10
- hash.keys.grep(Symbol) do |key|
11
- if klass.attribute_alias? key
12
- hash[klass.attribute_alias(key)] = hash.delete key
13
- end
14
- end
15
- 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))
16
27
  end
17
28
 
18
- def self.build_from_hash(klass, attributes, default_table)
19
- queries = []
20
-
21
- attributes.each do |column, value|
22
- table = default_table
23
-
24
- if value.is_a?(Hash)
25
- if value.empty?
26
- queries << '1=0'
27
- else
28
- table = Arel::Table.new(column, default_table.engine)
29
- association = klass._reflect_on_association(column)
30
-
31
- value.each do |k, v|
32
- queries.concat expand(association && association.klass, table, k, v)
33
- end
34
- end
35
- else
36
- column = column.to_s
37
-
38
- if column.include?('.')
39
- table_name, column = column.split('.', 2)
40
- table = Arel::Table.new(table_name, default_table.engine)
41
- end
42
-
43
- queries.concat expand(klass, table, column, value)
44
- end
45
- end
46
-
47
- queries
29
+ def build_from_hash(attributes)
30
+ attributes = convert_dot_notation_to_hash(attributes)
31
+ expand_from_hash(attributes)
48
32
  end
49
33
 
50
- def self.expand(klass, table, column, value)
51
- queries = []
34
+ def create_binds(attributes)
35
+ attributes = convert_dot_notation_to_hash(attributes)
36
+ create_binds_for_hash(attributes)
37
+ end
52
38
 
39
+ def expand(column, value)
53
40
  # Find the foreign key when using queries such as:
54
41
  # Post.where(author: author)
55
42
  #
56
43
  # For polymorphic relationships, find the foreign key and type:
57
44
  # PriceEstimate.where(estimate_of: treasure)
58
- if klass && reflection = klass._reflect_on_association(column)
59
- if reflection.polymorphic? && base_class = polymorphic_base_class_from_value(value)
60
- queries << build(table[reflection.foreign_type], base_class)
61
- end
62
-
63
- column = reflection.foreign_key
64
- end
65
-
66
- queries << build(table[column], value)
67
- queries
68
- end
69
-
70
- def self.polymorphic_base_class_from_value(value)
71
- case value
72
- when Relation
73
- value.klass.base_class
74
- when Array
75
- val = value.compact.first
76
- val.class.base_class if val.is_a?(Base)
77
- when Base
78
- value.class.base_class
79
- end
45
+ value = AssociationQueryHandler.value_for(table, column, value) if table.associated_with?(column)
46
+ build(table.arel_attribute(column), value)
80
47
  end
81
48
 
82
49
  def self.references(attributes)
@@ -85,7 +52,7 @@ module ActiveRecord
85
52
  key
86
53
  else
87
54
  key = key.to_s
88
- key.split('.').first if key.include?('.')
55
+ key.split('.'.freeze).first if key.include?('.'.freeze)
89
56
  end
90
57
  end.compact
91
58
  end
@@ -100,27 +67,102 @@ module ActiveRecord
100
67
  # Arel::Nodes::And.new([range.start, range.end])
101
68
  # )
102
69
  # end
103
- # ActiveRecord::PredicateBuilder.register_handler(MyCustomDateRange, handler)
104
- def self.register_handler(klass, handler)
70
+ # ActiveRecord::PredicateBuilder.new("users").register_handler(MyCustomDateRange, handler)
71
+ def register_handler(klass, handler)
105
72
  @handlers.unshift([klass, handler])
106
73
  end
107
74
 
108
- register_handler(BasicObject, ->(attribute, value) { attribute.eq(value) })
109
- # FIXME: I think we need to deprecate this behavior
110
- register_handler(Class, ->(attribute, value) { attribute.eq(value.name) })
111
- register_handler(Base, ->(attribute, value) { attribute.eq(value.id) })
112
- register_handler(Range, ->(attribute, value) { attribute.between(value) })
113
- register_handler(Relation, RelationHandler.new)
114
- register_handler(Array, ArrayHandler.new)
115
-
116
- def self.build(attribute, value)
75
+ def build(attribute, value)
117
76
  handler_for(value).call(attribute, value)
118
77
  end
119
- private_class_method :build
120
78
 
121
- def self.handler_for(object)
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))
136
+ end
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)
149
+ end
150
+
151
+ attributes
152
+ end
153
+
154
+ def handler_for(object)
122
155
  @handlers.detect { |klass, _| klass === object }.last
123
156
  end
124
- private_class_method :handler_for
157
+
158
+ def can_be_bound?(column_name, value)
159
+ !value.nil? &&
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))
166
+ end
125
167
  end
126
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