activerecord 3.2.22.5 → 5.2.8

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 (275) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +657 -621
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +41 -46
  5. data/examples/performance.rb +55 -42
  6. data/examples/simple.rb +6 -5
  7. data/lib/active_record/aggregations.rb +264 -236
  8. data/lib/active_record/association_relation.rb +40 -0
  9. data/lib/active_record/associations/alias_tracker.rb +47 -42
  10. data/lib/active_record/associations/association.rb +127 -75
  11. data/lib/active_record/associations/association_scope.rb +126 -92
  12. data/lib/active_record/associations/belongs_to_association.rb +78 -27
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +9 -4
  14. data/lib/active_record/associations/builder/association.rb +117 -32
  15. data/lib/active_record/associations/builder/belongs_to.rb +135 -60
  16. data/lib/active_record/associations/builder/collection_association.rb +61 -54
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +120 -42
  18. data/lib/active_record/associations/builder/has_many.rb +10 -64
  19. data/lib/active_record/associations/builder/has_one.rb +19 -51
  20. data/lib/active_record/associations/builder/singular_association.rb +28 -18
  21. data/lib/active_record/associations/collection_association.rb +226 -293
  22. data/lib/active_record/associations/collection_proxy.rb +1067 -69
  23. data/lib/active_record/associations/foreign_association.rb +13 -0
  24. data/lib/active_record/associations/has_many_association.rb +83 -47
  25. data/lib/active_record/associations/has_many_through_association.rb +98 -65
  26. data/lib/active_record/associations/has_one_association.rb +57 -20
  27. data/lib/active_record/associations/has_one_through_association.rb +18 -9
  28. data/lib/active_record/associations/join_dependency/join_association.rb +48 -126
  29. data/lib/active_record/associations/join_dependency/join_base.rb +11 -12
  30. data/lib/active_record/associations/join_dependency/join_part.rb +35 -42
  31. data/lib/active_record/associations/join_dependency.rb +212 -164
  32. data/lib/active_record/associations/preloader/association.rb +95 -89
  33. data/lib/active_record/associations/preloader/through_association.rb +84 -44
  34. data/lib/active_record/associations/preloader.rb +123 -111
  35. data/lib/active_record/associations/singular_association.rb +33 -24
  36. data/lib/active_record/associations/through_association.rb +60 -26
  37. data/lib/active_record/associations.rb +1759 -1506
  38. data/lib/active_record/attribute_assignment.rb +60 -193
  39. data/lib/active_record/attribute_decorators.rb +90 -0
  40. data/lib/active_record/attribute_methods/before_type_cast.rb +55 -8
  41. data/lib/active_record/attribute_methods/dirty.rb +113 -74
  42. data/lib/active_record/attribute_methods/primary_key.rb +106 -77
  43. data/lib/active_record/attribute_methods/query.rb +8 -5
  44. data/lib/active_record/attribute_methods/read.rb +63 -114
  45. data/lib/active_record/attribute_methods/serialization.rb +60 -90
  46. data/lib/active_record/attribute_methods/time_zone_conversion.rb +69 -43
  47. data/lib/active_record/attribute_methods/write.rb +43 -45
  48. data/lib/active_record/attribute_methods.rb +366 -149
  49. data/lib/active_record/attributes.rb +266 -0
  50. data/lib/active_record/autosave_association.rb +312 -225
  51. data/lib/active_record/base.rb +114 -505
  52. data/lib/active_record/callbacks.rb +145 -67
  53. data/lib/active_record/coders/json.rb +15 -0
  54. data/lib/active_record/coders/yaml_column.rb +32 -23
  55. data/lib/active_record/collection_cache_key.rb +53 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +883 -284
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +16 -2
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +350 -200
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +82 -27
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +150 -65
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +23 -0
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +146 -0
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +477 -284
  64. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +95 -0
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1100 -310
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +283 -0
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +450 -118
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +657 -446
  69. data/lib/active_record/connection_adapters/column.rb +50 -255
  70. data/lib/active_record/connection_adapters/connection_specification.rb +287 -0
  71. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +33 -0
  72. data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
  73. data/lib/active_record/connection_adapters/mysql/database_statements.rb +140 -0
  74. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
  75. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -0
  76. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +73 -0
  77. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +87 -0
  78. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +80 -0
  79. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +148 -0
  80. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +35 -0
  81. data/lib/active_record/connection_adapters/mysql2_adapter.rb +59 -210
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +44 -0
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +163 -0
  84. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +92 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +56 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +50 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +23 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +21 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +71 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +41 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +65 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +97 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +111 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +23 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid.rb +34 -0
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +168 -0
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +43 -0
  110. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +65 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +206 -0
  112. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +774 -0
  114. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +39 -0
  115. data/lib/active_record/connection_adapters/postgresql/utils.rb +81 -0
  116. data/lib/active_record/connection_adapters/postgresql_adapter.rb +620 -1080
  117. data/lib/active_record/connection_adapters/schema_cache.rb +85 -36
  118. data/lib/active_record/connection_adapters/sql_type_metadata.rb +34 -0
  119. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
  120. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +67 -0
  121. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
  122. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  123. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  124. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +106 -0
  125. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +545 -27
  126. data/lib/active_record/connection_adapters/statement_pool.rb +34 -13
  127. data/lib/active_record/connection_handling.rb +145 -0
  128. data/lib/active_record/core.rb +559 -0
  129. data/lib/active_record/counter_cache.rb +200 -105
  130. data/lib/active_record/define_callbacks.rb +22 -0
  131. data/lib/active_record/dynamic_matchers.rb +107 -69
  132. data/lib/active_record/enum.rb +244 -0
  133. data/lib/active_record/errors.rb +245 -60
  134. data/lib/active_record/explain.rb +35 -71
  135. data/lib/active_record/explain_registry.rb +32 -0
  136. data/lib/active_record/explain_subscriber.rb +18 -9
  137. data/lib/active_record/fixture_set/file.rb +82 -0
  138. data/lib/active_record/fixtures.rb +418 -275
  139. data/lib/active_record/gem_version.rb +17 -0
  140. data/lib/active_record/inheritance.rb +209 -100
  141. data/lib/active_record/integration.rb +116 -21
  142. data/lib/active_record/internal_metadata.rb +45 -0
  143. data/lib/active_record/legacy_yaml_adapter.rb +48 -0
  144. data/lib/active_record/locale/en.yml +9 -1
  145. data/lib/active_record/locking/optimistic.rb +107 -94
  146. data/lib/active_record/locking/pessimistic.rb +20 -8
  147. data/lib/active_record/log_subscriber.rb +99 -34
  148. data/lib/active_record/migration/command_recorder.rb +199 -64
  149. data/lib/active_record/migration/compatibility.rb +217 -0
  150. data/lib/active_record/migration/join_table.rb +17 -0
  151. data/lib/active_record/migration.rb +893 -296
  152. data/lib/active_record/model_schema.rb +328 -175
  153. data/lib/active_record/nested_attributes.rb +338 -242
  154. data/lib/active_record/no_touching.rb +58 -0
  155. data/lib/active_record/null_relation.rb +68 -0
  156. data/lib/active_record/persistence.rb +557 -170
  157. data/lib/active_record/query_cache.rb +14 -43
  158. data/lib/active_record/querying.rb +36 -24
  159. data/lib/active_record/railtie.rb +147 -52
  160. data/lib/active_record/railties/console_sandbox.rb +5 -4
  161. data/lib/active_record/railties/controller_runtime.rb +13 -6
  162. data/lib/active_record/railties/databases.rake +206 -488
  163. data/lib/active_record/readonly_attributes.rb +4 -6
  164. data/lib/active_record/reflection.rb +734 -228
  165. data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
  166. data/lib/active_record/relation/batches.rb +249 -52
  167. data/lib/active_record/relation/calculations.rb +330 -284
  168. data/lib/active_record/relation/delegation.rb +135 -37
  169. data/lib/active_record/relation/finder_methods.rb +450 -287
  170. data/lib/active_record/relation/from_clause.rb +26 -0
  171. data/lib/active_record/relation/merger.rb +193 -0
  172. data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
  173. data/lib/active_record/relation/predicate_builder/association_query_value.rb +46 -0
  174. data/lib/active_record/relation/predicate_builder/base_handler.rb +19 -0
  175. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +20 -0
  176. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +56 -0
  177. data/lib/active_record/relation/predicate_builder/range_handler.rb +42 -0
  178. data/lib/active_record/relation/predicate_builder/relation_handler.rb +19 -0
  179. data/lib/active_record/relation/predicate_builder.rb +132 -43
  180. data/lib/active_record/relation/query_attribute.rb +45 -0
  181. data/lib/active_record/relation/query_methods.rb +1037 -221
  182. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  183. data/lib/active_record/relation/spawn_methods.rb +48 -151
  184. data/lib/active_record/relation/where_clause.rb +186 -0
  185. data/lib/active_record/relation/where_clause_factory.rb +34 -0
  186. data/lib/active_record/relation.rb +451 -359
  187. data/lib/active_record/result.rb +129 -20
  188. data/lib/active_record/runtime_registry.rb +24 -0
  189. data/lib/active_record/sanitization.rb +164 -136
  190. data/lib/active_record/schema.rb +31 -19
  191. data/lib/active_record/schema_dumper.rb +154 -107
  192. data/lib/active_record/schema_migration.rb +56 -0
  193. data/lib/active_record/scoping/default.rb +108 -98
  194. data/lib/active_record/scoping/named.rb +125 -112
  195. data/lib/active_record/scoping.rb +77 -123
  196. data/lib/active_record/secure_token.rb +40 -0
  197. data/lib/active_record/serialization.rb +10 -6
  198. data/lib/active_record/statement_cache.rb +121 -0
  199. data/lib/active_record/store.rb +175 -16
  200. data/lib/active_record/suppressor.rb +61 -0
  201. data/lib/active_record/table_metadata.rb +82 -0
  202. data/lib/active_record/tasks/database_tasks.rb +337 -0
  203. data/lib/active_record/tasks/mysql_database_tasks.rb +115 -0
  204. data/lib/active_record/tasks/postgresql_database_tasks.rb +143 -0
  205. data/lib/active_record/tasks/sqlite_database_tasks.rb +83 -0
  206. data/lib/active_record/timestamp.rb +80 -41
  207. data/lib/active_record/touch_later.rb +64 -0
  208. data/lib/active_record/transactions.rb +240 -119
  209. data/lib/active_record/translation.rb +2 -0
  210. data/lib/active_record/type/adapter_specific_registry.rb +136 -0
  211. data/lib/active_record/type/date.rb +9 -0
  212. data/lib/active_record/type/date_time.rb +9 -0
  213. data/lib/active_record/type/decimal_without_scale.rb +15 -0
  214. data/lib/active_record/type/hash_lookup_type_map.rb +25 -0
  215. data/lib/active_record/type/internal/timezone.rb +17 -0
  216. data/lib/active_record/type/json.rb +30 -0
  217. data/lib/active_record/type/serialized.rb +71 -0
  218. data/lib/active_record/type/text.rb +11 -0
  219. data/lib/active_record/type/time.rb +21 -0
  220. data/lib/active_record/type/type_map.rb +62 -0
  221. data/lib/active_record/type/unsigned_integer.rb +17 -0
  222. data/lib/active_record/type.rb +79 -0
  223. data/lib/active_record/type_caster/connection.rb +33 -0
  224. data/lib/active_record/type_caster/map.rb +23 -0
  225. data/lib/active_record/type_caster.rb +9 -0
  226. data/lib/active_record/validations/absence.rb +25 -0
  227. data/lib/active_record/validations/associated.rb +35 -18
  228. data/lib/active_record/validations/length.rb +26 -0
  229. data/lib/active_record/validations/presence.rb +68 -0
  230. data/lib/active_record/validations/uniqueness.rb +133 -75
  231. data/lib/active_record/validations.rb +53 -43
  232. data/lib/active_record/version.rb +7 -7
  233. data/lib/active_record.rb +89 -57
  234. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
  235. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  236. data/lib/rails/generators/active_record/migration/migration_generator.rb +61 -8
  237. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +24 -0
  238. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +46 -0
  239. data/lib/rails/generators/active_record/migration.rb +28 -8
  240. data/lib/rails/generators/active_record/model/model_generator.rb +23 -22
  241. data/lib/rails/generators/active_record/model/templates/model.rb.tt +13 -0
  242. data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +1 -1
  243. data/lib/rails/generators/active_record.rb +10 -16
  244. metadata +141 -62
  245. data/examples/associations.png +0 -0
  246. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
  247. data/lib/active_record/associations/join_helper.rb +0 -55
  248. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  249. data/lib/active_record/associations/preloader/collection_association.rb +0 -24
  250. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  251. data/lib/active_record/associations/preloader/has_many.rb +0 -17
  252. data/lib/active_record/associations/preloader/has_many_through.rb +0 -15
  253. data/lib/active_record/associations/preloader/has_one.rb +0 -23
  254. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  255. data/lib/active_record/associations/preloader/singular_association.rb +0 -21
  256. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  257. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  258. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -441
  259. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  260. data/lib/active_record/dynamic_finder_match.rb +0 -68
  261. data/lib/active_record/dynamic_scope_match.rb +0 -23
  262. data/lib/active_record/fixtures/file.rb +0 -65
  263. data/lib/active_record/identity_map.rb +0 -162
  264. data/lib/active_record/observer.rb +0 -121
  265. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  266. data/lib/active_record/serializers/xml_serializer.rb +0 -203
  267. data/lib/active_record/session_store.rb +0 -360
  268. data/lib/active_record/test_case.rb +0 -73
  269. data/lib/rails/generators/active_record/migration/templates/migration.rb +0 -34
  270. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -15
  271. data/lib/rails/generators/active_record/model/templates/model.rb +0 -12
  272. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  273. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  274. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  275. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ class Relation
5
+ class FromClause # :nodoc:
6
+ attr_reader :value, :name
7
+
8
+ def initialize(value, name)
9
+ @value = value
10
+ @name = name
11
+ end
12
+
13
+ def merge(other)
14
+ self
15
+ end
16
+
17
+ def empty?
18
+ value.nil?
19
+ end
20
+
21
+ def self.empty
22
+ @empty ||= new(nil, nil)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,193 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/hash/keys"
4
+
5
+ module ActiveRecord
6
+ class Relation
7
+ class HashMerger # :nodoc:
8
+ attr_reader :relation, :hash
9
+
10
+ def initialize(relation, hash)
11
+ hash.assert_valid_keys(*Relation::VALUE_METHODS)
12
+
13
+ @relation = relation
14
+ @hash = hash
15
+ end
16
+
17
+ def merge #:nodoc:
18
+ Merger.new(relation, other).merge
19
+ end
20
+
21
+ # Applying values to a relation has some side effects. E.g.
22
+ # interpolation might take place for where values. So we should
23
+ # build a relation to merge in rather than directly merging
24
+ # the values.
25
+ def other
26
+ other = Relation.create(
27
+ relation.klass,
28
+ table: relation.table,
29
+ predicate_builder: relation.predicate_builder
30
+ )
31
+ hash.each { |k, v|
32
+ if k == :joins
33
+ if Hash === v
34
+ other.joins!(v)
35
+ else
36
+ other.joins!(*v)
37
+ end
38
+ elsif k == :select
39
+ other._select!(v)
40
+ else
41
+ other.send("#{k}!", v)
42
+ end
43
+ }
44
+ other
45
+ end
46
+ end
47
+
48
+ class Merger # :nodoc:
49
+ attr_reader :relation, :values, :other
50
+
51
+ def initialize(relation, other)
52
+ @relation = relation
53
+ @values = other.values
54
+ @other = other
55
+ end
56
+
57
+ NORMAL_VALUES = Relation::VALUE_METHODS -
58
+ Relation::CLAUSE_METHODS -
59
+ [:includes, :preload, :joins, :left_outer_joins, :order, :reverse_order, :lock, :create_with, :reordering] # :nodoc:
60
+
61
+ def normal_values
62
+ NORMAL_VALUES
63
+ end
64
+
65
+ def merge
66
+ normal_values.each do |name|
67
+ value = values[name]
68
+ # The unless clause is here mostly for performance reasons (since the `send` call might be moderately
69
+ # expensive), most of the time the value is going to be `nil` or `.blank?`, the only catch is that
70
+ # `false.blank?` returns `true`, so there needs to be an extra check so that explicit `false` values
71
+ # don't fall through the cracks.
72
+ unless value.nil? || (value.blank? && false != value)
73
+ if name == :select
74
+ relation._select!(*value)
75
+ else
76
+ relation.send("#{name}!", *value)
77
+ end
78
+ end
79
+ end
80
+
81
+ merge_multi_values
82
+ merge_single_values
83
+ merge_clauses
84
+ merge_preloads
85
+ merge_joins
86
+ merge_outer_joins
87
+
88
+ relation
89
+ end
90
+
91
+ private
92
+
93
+ def merge_preloads
94
+ return if other.preload_values.empty? && other.includes_values.empty?
95
+
96
+ if other.klass == relation.klass
97
+ relation.preload!(*other.preload_values) unless other.preload_values.empty?
98
+ relation.includes!(other.includes_values) unless other.includes_values.empty?
99
+ else
100
+ reflection = relation.klass.reflect_on_all_associations.find do |r|
101
+ r.class_name == other.klass.name
102
+ end || return
103
+
104
+ unless other.preload_values.empty?
105
+ relation.preload! reflection.name => other.preload_values
106
+ end
107
+
108
+ unless other.includes_values.empty?
109
+ relation.includes! reflection.name => other.includes_values
110
+ end
111
+ end
112
+ end
113
+
114
+ def merge_joins
115
+ return if other.joins_values.blank?
116
+
117
+ if other.klass == relation.klass
118
+ relation.joins!(*other.joins_values)
119
+ else
120
+ joins_dependency = other.joins_values.map do |join|
121
+ case join
122
+ when Hash, Symbol, Array
123
+ ActiveRecord::Associations::JoinDependency.new(
124
+ other.klass, other.table, join
125
+ )
126
+ else
127
+ join
128
+ end
129
+ end
130
+
131
+ relation.joins!(*joins_dependency)
132
+ end
133
+ end
134
+
135
+ def merge_outer_joins
136
+ return if other.left_outer_joins_values.blank?
137
+
138
+ if other.klass == relation.klass
139
+ relation.left_outer_joins!(*other.left_outer_joins_values)
140
+ else
141
+ joins_dependency = other.left_outer_joins_values.map do |join|
142
+ case join
143
+ when Hash, Symbol, Array
144
+ ActiveRecord::Associations::JoinDependency.new(
145
+ other.klass, other.table, join
146
+ )
147
+ else
148
+ join
149
+ end
150
+ end
151
+
152
+ relation.left_outer_joins!(*joins_dependency)
153
+ end
154
+ end
155
+
156
+ def merge_multi_values
157
+ if other.reordering_value
158
+ # override any order specified in the original relation
159
+ relation.reorder!(*other.order_values)
160
+ elsif other.order_values.any?
161
+ # merge in order_values from relation
162
+ relation.order!(*other.order_values)
163
+ end
164
+
165
+ extensions = other.extensions - relation.extensions
166
+ relation.extending!(*extensions) if extensions.any?
167
+ end
168
+
169
+ def merge_single_values
170
+ relation.lock_value ||= other.lock_value if other.lock_value
171
+
172
+ unless other.create_with_value.blank?
173
+ relation.create_with_value = (relation.create_with_value || {}).merge(other.create_with_value)
174
+ end
175
+ end
176
+
177
+ def merge_clauses
178
+ relation.from_clause = other.from_clause if replace_from_clause?
179
+
180
+ where_clause = relation.where_clause.merge(other.where_clause)
181
+ relation.where_clause = where_clause unless where_clause.empty?
182
+
183
+ having_clause = relation.having_clause.merge(other.having_clause)
184
+ relation.having_clause = having_clause unless having_clause.empty?
185
+ end
186
+
187
+ def replace_from_clause?
188
+ relation.from_clause.empty? && !other.from_clause.empty? &&
189
+ relation.klass.base_class == other.klass.base_class
190
+ end
191
+ end
192
+ end
193
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ class PredicateBuilder
5
+ class ArrayHandler # :nodoc:
6
+ def initialize(predicate_builder)
7
+ @predicate_builder = predicate_builder
8
+ end
9
+
10
+ def call(attribute, value)
11
+ return attribute.in([]) if value.empty?
12
+
13
+ values = value.map { |x| x.is_a?(Base) ? x.id : x }
14
+ nils, values = values.partition(&:nil?)
15
+ ranges, values = values.partition { |v| v.is_a?(Range) }
16
+
17
+ values_predicate =
18
+ case values.length
19
+ when 0 then NullPredicate
20
+ when 1 then predicate_builder.build(attribute, values.first)
21
+ else
22
+ values.map! do |v|
23
+ predicate_builder.build_bind_attribute(attribute.name, v)
24
+ end
25
+ values.empty? ? NullPredicate : attribute.in(values)
26
+ end
27
+
28
+ unless nils.empty?
29
+ values_predicate = values_predicate.or(predicate_builder.build(attribute, nil))
30
+ end
31
+
32
+ array_predicates = ranges.map { |range| predicate_builder.build(attribute, range) }
33
+ array_predicates.unshift(values_predicate)
34
+ array_predicates.inject(&:or)
35
+ end
36
+
37
+ protected
38
+
39
+ attr_reader :predicate_builder
40
+
41
+ module NullPredicate # :nodoc:
42
+ def self.or(other)
43
+ other
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ class PredicateBuilder
5
+ class AssociationQueryValue # :nodoc:
6
+ def initialize(associated_table, value)
7
+ @associated_table = associated_table
8
+ @value = value
9
+ end
10
+
11
+ def queries
12
+ [associated_table.association_join_foreign_key.to_s => ids]
13
+ end
14
+
15
+ # TODO Change this to private once we've dropped Ruby 2.2 support.
16
+ # Workaround for Ruby 2.2 "private attribute?" warning.
17
+ protected
18
+ attr_reader :associated_table, :value
19
+
20
+ private
21
+ def ids
22
+ case value
23
+ when Relation
24
+ value.select_values.empty? ? value.select(primary_key) : value
25
+ when Array
26
+ value.map { |v| convert_to_id(v) }
27
+ else
28
+ convert_to_id(value)
29
+ end
30
+ end
31
+
32
+ def primary_key
33
+ associated_table.association_join_primary_key
34
+ end
35
+
36
+ def convert_to_id(value)
37
+ case value
38
+ when Base
39
+ value._read_attribute(primary_key)
40
+ else
41
+ value
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ class PredicateBuilder
5
+ class BaseHandler # :nodoc:
6
+ def initialize(predicate_builder)
7
+ @predicate_builder = predicate_builder
8
+ end
9
+
10
+ def call(attribute, value)
11
+ predicate_builder.build(attribute, value.id)
12
+ end
13
+
14
+ protected
15
+
16
+ attr_reader :predicate_builder
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ class PredicateBuilder
5
+ class BasicObjectHandler # :nodoc:
6
+ def initialize(predicate_builder)
7
+ @predicate_builder = predicate_builder
8
+ end
9
+
10
+ def call(attribute, value)
11
+ bind = predicate_builder.build_bind_attribute(attribute.name, value)
12
+ attribute.eq(bind)
13
+ end
14
+
15
+ protected
16
+
17
+ attr_reader :predicate_builder
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ class PredicateBuilder
5
+ class PolymorphicArrayValue # :nodoc:
6
+ def initialize(associated_table, values)
7
+ @associated_table = associated_table
8
+ @values = values
9
+ end
10
+
11
+ def queries
12
+ type_to_ids_mapping.map do |type, ids|
13
+ {
14
+ associated_table.association_foreign_type.to_s => type,
15
+ associated_table.association_foreign_key.to_s => ids
16
+ }
17
+ end
18
+ end
19
+
20
+ # TODO Change this to private once we've dropped Ruby 2.2 support.
21
+ # Workaround for Ruby 2.2 "private attribute?" warning.
22
+ protected
23
+ attr_reader :associated_table, :values
24
+
25
+ private
26
+ def type_to_ids_mapping
27
+ default_hash = Hash.new { |hsh, key| hsh[key] = [] }
28
+ values.each_with_object(default_hash) do |value, hash|
29
+ hash[klass(value).polymorphic_name] << convert_to_id(value)
30
+ end
31
+ end
32
+
33
+ def primary_key(value)
34
+ associated_table.association_join_primary_key(klass(value))
35
+ end
36
+
37
+ def klass(value)
38
+ case value
39
+ when Base
40
+ value.class
41
+ when Relation
42
+ value.klass
43
+ end
44
+ end
45
+
46
+ def convert_to_id(value)
47
+ case value
48
+ when Base
49
+ value._read_attribute(primary_key(value))
50
+ when Relation
51
+ value.select(primary_key(value))
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ class PredicateBuilder
5
+ class RangeHandler # :nodoc:
6
+ class RangeWithBinds < Struct.new(:begin, :end)
7
+ def exclude_end?
8
+ false
9
+ end
10
+ end
11
+
12
+ def initialize(predicate_builder)
13
+ @predicate_builder = predicate_builder
14
+ end
15
+
16
+ def call(attribute, value)
17
+ begin_bind = predicate_builder.build_bind_attribute(attribute.name, value.begin)
18
+ end_bind = predicate_builder.build_bind_attribute(attribute.name, value.end)
19
+
20
+ if begin_bind.value.infinity?
21
+ if end_bind.value.infinity?
22
+ attribute.not_in([])
23
+ elsif value.exclude_end?
24
+ attribute.lt(end_bind)
25
+ else
26
+ attribute.lteq(end_bind)
27
+ end
28
+ elsif end_bind.value.infinity?
29
+ attribute.gteq(begin_bind)
30
+ elsif value.exclude_end?
31
+ attribute.gteq(begin_bind).and(attribute.lt(end_bind))
32
+ else
33
+ attribute.between(RangeWithBinds.new(begin_bind, end_bind))
34
+ end
35
+ end
36
+
37
+ protected
38
+
39
+ attr_reader :predicate_builder
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ class PredicateBuilder
5
+ class RelationHandler # :nodoc:
6
+ def call(attribute, value)
7
+ if value.eager_loading?
8
+ value = value.send(:apply_join_dependency)
9
+ end
10
+
11
+ if value.select_values.empty?
12
+ value = value.select(value.arel_attribute(value.klass.primary_key))
13
+ end
14
+
15
+ attribute.in(value.arel)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -1,63 +1,152 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  class PredicateBuilder # :nodoc:
3
- def self.build_from_hash(engine, attributes, default_table, allow_table_name = true)
4
- predicates = attributes.map do |column, value|
5
- table = default_table
5
+ delegate :resolve_column_aliases, to: :table
6
6
 
7
- if allow_table_name && value.is_a?(Hash)
8
- table = Arel::Table.new(column, engine)
7
+ def initialize(table)
8
+ @table = table
9
+ @handlers = []
9
10
 
10
- if value.empty?
11
- '1 = 2'
12
- else
13
- build_from_hash(engine, value, table, false)
14
- end
11
+ register_handler(BasicObject, BasicObjectHandler.new(self))
12
+ register_handler(Base, BaseHandler.new(self))
13
+ register_handler(Range, RangeHandler.new(self))
14
+ register_handler(Relation, RelationHandler.new)
15
+ register_handler(Array, ArrayHandler.new(self))
16
+ register_handler(Set, ArrayHandler.new(self))
17
+ end
18
+
19
+ def build_from_hash(attributes)
20
+ attributes = convert_dot_notation_to_hash(attributes)
21
+ expand_from_hash(attributes)
22
+ end
23
+
24
+ def self.references(attributes)
25
+ attributes.map do |key, value|
26
+ if value.is_a?(Hash)
27
+ key
15
28
  else
16
- column = column.to_s
29
+ key = key.to_s
30
+ key.split(".".freeze).first if key.include?(".".freeze)
31
+ end
32
+ end.compact
33
+ end
17
34
 
18
- if allow_table_name && column.include?('.')
19
- table_name, column = column.split('.', 2)
20
- table = Arel::Table.new(table_name, engine)
21
- end
35
+ # Define how a class is converted to Arel nodes when passed to +where+.
36
+ # The handler can be any object that responds to +call+, and will be used
37
+ # for any value that +===+ the class given. For example:
38
+ #
39
+ # MyCustomDateRange = Struct.new(:start, :end)
40
+ # handler = proc do |column, range|
41
+ # Arel::Nodes::Between.new(column,
42
+ # Arel::Nodes::And.new([range.start, range.end])
43
+ # )
44
+ # end
45
+ # ActiveRecord::PredicateBuilder.new("users").register_handler(MyCustomDateRange, handler)
46
+ def register_handler(klass, handler)
47
+ @handlers.unshift([klass, handler])
48
+ end
22
49
 
23
- attribute = table[column]
50
+ def build(attribute, value)
51
+ if table.type(attribute.name).force_equality?(value)
52
+ bind = build_bind_attribute(attribute.name, value)
53
+ attribute.eq(bind)
54
+ else
55
+ handler_for(value).call(attribute, value)
56
+ end
57
+ end
24
58
 
25
- case value
26
- when ActiveRecord::Relation
27
- value = value.select(value.klass.arel_table[value.klass.primary_key]) if value.select_values.empty?
28
- attribute.in(value.arel.ast)
29
- when Array, ActiveRecord::Associations::CollectionProxy
30
- values = value.to_a.map {|x| x.is_a?(ActiveRecord::Base) ? x.id : x}
31
- ranges, values = values.partition {|v| v.is_a?(Range) || v.is_a?(Arel::Relation)}
59
+ def build_bind_attribute(column_name, value)
60
+ attr = Relation::QueryAttribute.new(column_name.to_s, value, table.type(column_name))
61
+ Arel::Nodes::BindParam.new(attr)
62
+ end
32
63
 
33
- array_predicates = ranges.map {|range| attribute.in(range)}
64
+ protected
34
65
 
35
- if values.include?(nil)
36
- values = values.compact
37
- if values.empty?
38
- array_predicates << attribute.eq(nil)
39
- else
40
- array_predicates << attribute.in(values.compact).or(attribute.eq(nil))
66
+ attr_reader :table
67
+
68
+ def expand_from_hash(attributes)
69
+ return ["1=0"] if attributes.empty?
70
+
71
+ attributes.flat_map do |key, value|
72
+ if value.is_a?(Hash) && !table.has_column?(key)
73
+ associated_predicate_builder(key).expand_from_hash(value)
74
+ elsif table.associated_with?(key)
75
+ # Find the foreign key when using queries such as:
76
+ # Post.where(author: author)
77
+ #
78
+ # For polymorphic relationships, find the foreign key and type:
79
+ # PriceEstimate.where(estimate_of: treasure)
80
+ associated_table = table.associated_table(key)
81
+ if associated_table.polymorphic_association?
82
+ case value.is_a?(Array) ? value.first : value
83
+ when Base, Relation
84
+ value = [value] unless value.is_a?(Array)
85
+ klass = PolymorphicArrayValue
41
86
  end
42
- else
43
- array_predicates << attribute.in(values)
44
87
  end
45
88
 
46
- array_predicates.inject {|composite, predicate| composite.or(predicate)}
47
- when Range, Arel::Relation
48
- attribute.in(value)
49
- when ActiveRecord::Base
50
- attribute.eq(value.id)
51
- when Class
52
- # FIXME: I think we need to deprecate this behavior
53
- attribute.eq(value.name)
89
+ klass ||= AssociationQueryValue
90
+ queries = klass.new(associated_table, value).queries.map do |query|
91
+ expand_from_hash(query).reduce(&:and)
92
+ end
93
+ queries.reduce(&:or)
94
+ elsif table.aggregated_with?(key)
95
+ mapping = table.reflect_on_aggregation(key).mapping
96
+ values = value.nil? ? [nil] : Array.wrap(value)
97
+ if mapping.length == 1 || values.empty?
98
+ column_name, aggr_attr = mapping.first
99
+ values = values.map do |object|
100
+ object.respond_to?(aggr_attr) ? object.public_send(aggr_attr) : object
101
+ end
102
+ build(table.arel_attribute(column_name), values)
103
+ else
104
+ queries = values.map do |object|
105
+ mapping.map do |field_attr, aggregate_attr|
106
+ build(table.arel_attribute(field_attr), object.try!(aggregate_attr))
107
+ end.reduce(&:and)
108
+ end
109
+ queries.reduce(&:or)
110
+ end
54
111
  else
55
- attribute.eq(value)
112
+ build(table.arel_attribute(key), value)
56
113
  end
57
114
  end
58
115
  end
59
116
 
60
- predicates.flatten
61
- end
117
+ private
118
+
119
+ def associated_predicate_builder(association_name)
120
+ self.class.new(table.associated_table(association_name))
121
+ end
122
+
123
+ def convert_dot_notation_to_hash(attributes)
124
+ dot_notation = attributes.select do |k, v|
125
+ k.include?(".".freeze) && !v.is_a?(Hash)
126
+ end
127
+
128
+ dot_notation.each_key do |key|
129
+ table_name, column_name = key.split(".".freeze)
130
+ value = attributes.delete(key)
131
+ attributes[table_name] ||= {}
132
+
133
+ attributes[table_name] = attributes[table_name].merge(column_name => value)
134
+ end
135
+
136
+ attributes
137
+ end
138
+
139
+ def handler_for(object)
140
+ @handlers.detect { |klass, _| klass === object }.last
141
+ end
62
142
  end
63
143
  end
144
+
145
+ require "active_record/relation/predicate_builder/array_handler"
146
+ require "active_record/relation/predicate_builder/base_handler"
147
+ require "active_record/relation/predicate_builder/basic_object_handler"
148
+ require "active_record/relation/predicate_builder/range_handler"
149
+ require "active_record/relation/predicate_builder/relation_handler"
150
+
151
+ require "active_record/relation/predicate_builder/association_query_value"
152
+ require "active_record/relation/predicate_builder/polymorphic_array_value"