activerecord 3.2.22.5 → 4.2.11.3

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 (236) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +1632 -609
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +37 -41
  5. data/examples/performance.rb +31 -19
  6. data/examples/simple.rb +4 -4
  7. data/lib/active_record/aggregations.rb +56 -42
  8. data/lib/active_record/association_relation.rb +35 -0
  9. data/lib/active_record/associations/alias_tracker.rb +47 -36
  10. data/lib/active_record/associations/association.rb +73 -55
  11. data/lib/active_record/associations/association_scope.rb +143 -82
  12. data/lib/active_record/associations/belongs_to_association.rb +65 -25
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -2
  14. data/lib/active_record/associations/builder/association.rb +125 -31
  15. data/lib/active_record/associations/builder/belongs_to.rb +89 -61
  16. data/lib/active_record/associations/builder/collection_association.rb +69 -49
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +113 -42
  18. data/lib/active_record/associations/builder/has_many.rb +8 -64
  19. data/lib/active_record/associations/builder/has_one.rb +12 -51
  20. data/lib/active_record/associations/builder/singular_association.rb +23 -17
  21. data/lib/active_record/associations/collection_association.rb +251 -177
  22. data/lib/active_record/associations/collection_proxy.rb +963 -63
  23. data/lib/active_record/associations/foreign_association.rb +11 -0
  24. data/lib/active_record/associations/has_many_association.rb +113 -22
  25. data/lib/active_record/associations/has_many_through_association.rb +99 -39
  26. data/lib/active_record/associations/has_one_association.rb +43 -20
  27. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  28. data/lib/active_record/associations/join_dependency/join_association.rb +76 -107
  29. data/lib/active_record/associations/join_dependency/join_base.rb +7 -9
  30. data/lib/active_record/associations/join_dependency/join_part.rb +30 -37
  31. data/lib/active_record/associations/join_dependency.rb +230 -156
  32. data/lib/active_record/associations/preloader/association.rb +96 -55
  33. data/lib/active_record/associations/preloader/collection_association.rb +3 -3
  34. data/lib/active_record/associations/preloader/has_many_through.rb +7 -3
  35. data/lib/active_record/associations/preloader/has_one.rb +1 -1
  36. data/lib/active_record/associations/preloader/singular_association.rb +3 -3
  37. data/lib/active_record/associations/preloader/through_association.rb +62 -33
  38. data/lib/active_record/associations/preloader.rb +101 -79
  39. data/lib/active_record/associations/singular_association.rb +29 -13
  40. data/lib/active_record/associations/through_association.rb +30 -16
  41. data/lib/active_record/associations.rb +463 -345
  42. data/lib/active_record/attribute.rb +163 -0
  43. data/lib/active_record/attribute_assignment.rb +142 -151
  44. data/lib/active_record/attribute_decorators.rb +66 -0
  45. data/lib/active_record/attribute_methods/before_type_cast.rb +52 -7
  46. data/lib/active_record/attribute_methods/dirty.rb +137 -57
  47. data/lib/active_record/attribute_methods/primary_key.rb +50 -36
  48. data/lib/active_record/attribute_methods/query.rb +5 -4
  49. data/lib/active_record/attribute_methods/read.rb +73 -106
  50. data/lib/active_record/attribute_methods/serialization.rb +44 -94
  51. data/lib/active_record/attribute_methods/time_zone_conversion.rb +49 -45
  52. data/lib/active_record/attribute_methods/write.rb +57 -44
  53. data/lib/active_record/attribute_methods.rb +301 -141
  54. data/lib/active_record/attribute_set/builder.rb +106 -0
  55. data/lib/active_record/attribute_set.rb +81 -0
  56. data/lib/active_record/attributes.rb +147 -0
  57. data/lib/active_record/autosave_association.rb +246 -217
  58. data/lib/active_record/base.rb +70 -474
  59. data/lib/active_record/callbacks.rb +66 -28
  60. data/lib/active_record/coders/json.rb +13 -0
  61. data/lib/active_record/coders/yaml_column.rb +18 -21
  62. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +396 -219
  63. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  64. data/lib/active_record/connection_adapters/abstract/database_statements.rb +167 -164
  65. data/lib/active_record/connection_adapters/abstract/query_cache.rb +29 -24
  66. data/lib/active_record/connection_adapters/abstract/quoting.rb +74 -55
  67. data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
  68. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +125 -0
  69. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +261 -169
  70. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +707 -259
  72. data/lib/active_record/connection_adapters/abstract/transaction.rb +215 -0
  73. data/lib/active_record/connection_adapters/abstract_adapter.rb +298 -89
  74. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +466 -196
  75. data/lib/active_record/connection_adapters/column.rb +31 -245
  76. data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
  77. data/lib/active_record/connection_adapters/mysql2_adapter.rb +45 -57
  78. data/lib/active_record/connection_adapters/mysql_adapter.rb +180 -123
  79. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +93 -0
  80. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  81. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +232 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +100 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +19 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid.rb +36 -0
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +108 -0
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  110. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +596 -0
  112. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  113. data/lib/active_record/connection_adapters/postgresql_adapter.rb +430 -999
  114. data/lib/active_record/connection_adapters/schema_cache.rb +52 -27
  115. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +579 -22
  116. data/lib/active_record/connection_handling.rb +132 -0
  117. data/lib/active_record/core.rb +579 -0
  118. data/lib/active_record/counter_cache.rb +157 -105
  119. data/lib/active_record/dynamic_matchers.rb +119 -63
  120. data/lib/active_record/enum.rb +197 -0
  121. data/lib/active_record/errors.rb +94 -36
  122. data/lib/active_record/explain.rb +15 -63
  123. data/lib/active_record/explain_registry.rb +30 -0
  124. data/lib/active_record/explain_subscriber.rb +9 -5
  125. data/lib/active_record/fixture_set/file.rb +56 -0
  126. data/lib/active_record/fixtures.rb +302 -215
  127. data/lib/active_record/gem_version.rb +15 -0
  128. data/lib/active_record/inheritance.rb +143 -70
  129. data/lib/active_record/integration.rb +65 -12
  130. data/lib/active_record/legacy_yaml_adapter.rb +30 -0
  131. data/lib/active_record/locale/en.yml +8 -1
  132. data/lib/active_record/locking/optimistic.rb +73 -52
  133. data/lib/active_record/locking/pessimistic.rb +5 -5
  134. data/lib/active_record/log_subscriber.rb +24 -21
  135. data/lib/active_record/migration/command_recorder.rb +124 -32
  136. data/lib/active_record/migration/join_table.rb +15 -0
  137. data/lib/active_record/migration.rb +511 -213
  138. data/lib/active_record/model_schema.rb +91 -117
  139. data/lib/active_record/nested_attributes.rb +184 -130
  140. data/lib/active_record/no_touching.rb +52 -0
  141. data/lib/active_record/null_relation.rb +81 -0
  142. data/lib/active_record/persistence.rb +276 -117
  143. data/lib/active_record/query_cache.rb +19 -37
  144. data/lib/active_record/querying.rb +28 -18
  145. data/lib/active_record/railtie.rb +73 -40
  146. data/lib/active_record/railties/console_sandbox.rb +3 -4
  147. data/lib/active_record/railties/controller_runtime.rb +4 -3
  148. data/lib/active_record/railties/databases.rake +141 -416
  149. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  150. data/lib/active_record/readonly_attributes.rb +1 -4
  151. data/lib/active_record/reflection.rb +513 -154
  152. data/lib/active_record/relation/batches.rb +91 -43
  153. data/lib/active_record/relation/calculations.rb +199 -161
  154. data/lib/active_record/relation/delegation.rb +116 -25
  155. data/lib/active_record/relation/finder_methods.rb +362 -248
  156. data/lib/active_record/relation/merger.rb +193 -0
  157. data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
  158. data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
  159. data/lib/active_record/relation/predicate_builder.rb +135 -43
  160. data/lib/active_record/relation/query_methods.rb +928 -167
  161. data/lib/active_record/relation/spawn_methods.rb +48 -149
  162. data/lib/active_record/relation.rb +352 -207
  163. data/lib/active_record/result.rb +101 -10
  164. data/lib/active_record/runtime_registry.rb +22 -0
  165. data/lib/active_record/sanitization.rb +56 -59
  166. data/lib/active_record/schema.rb +19 -13
  167. data/lib/active_record/schema_dumper.rb +106 -63
  168. data/lib/active_record/schema_migration.rb +53 -0
  169. data/lib/active_record/scoping/default.rb +50 -57
  170. data/lib/active_record/scoping/named.rb +73 -109
  171. data/lib/active_record/scoping.rb +58 -123
  172. data/lib/active_record/serialization.rb +6 -2
  173. data/lib/active_record/serializers/xml_serializer.rb +12 -22
  174. data/lib/active_record/statement_cache.rb +111 -0
  175. data/lib/active_record/store.rb +168 -15
  176. data/lib/active_record/tasks/database_tasks.rb +299 -0
  177. data/lib/active_record/tasks/mysql_database_tasks.rb +159 -0
  178. data/lib/active_record/tasks/postgresql_database_tasks.rb +101 -0
  179. data/lib/active_record/tasks/sqlite_database_tasks.rb +55 -0
  180. data/lib/active_record/timestamp.rb +23 -16
  181. data/lib/active_record/transactions.rb +125 -79
  182. data/lib/active_record/type/big_integer.rb +13 -0
  183. data/lib/active_record/type/binary.rb +50 -0
  184. data/lib/active_record/type/boolean.rb +31 -0
  185. data/lib/active_record/type/date.rb +50 -0
  186. data/lib/active_record/type/date_time.rb +54 -0
  187. data/lib/active_record/type/decimal.rb +64 -0
  188. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  189. data/lib/active_record/type/decorator.rb +14 -0
  190. data/lib/active_record/type/float.rb +19 -0
  191. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  192. data/lib/active_record/type/integer.rb +59 -0
  193. data/lib/active_record/type/mutable.rb +16 -0
  194. data/lib/active_record/type/numeric.rb +36 -0
  195. data/lib/active_record/type/serialized.rb +62 -0
  196. data/lib/active_record/type/string.rb +40 -0
  197. data/lib/active_record/type/text.rb +11 -0
  198. data/lib/active_record/type/time.rb +26 -0
  199. data/lib/active_record/type/time_value.rb +38 -0
  200. data/lib/active_record/type/type_map.rb +64 -0
  201. data/lib/active_record/type/unsigned_integer.rb +15 -0
  202. data/lib/active_record/type/value.rb +110 -0
  203. data/lib/active_record/type.rb +23 -0
  204. data/lib/active_record/validations/associated.rb +24 -16
  205. data/lib/active_record/validations/presence.rb +67 -0
  206. data/lib/active_record/validations/uniqueness.rb +123 -64
  207. data/lib/active_record/validations.rb +36 -29
  208. data/lib/active_record/version.rb +5 -7
  209. data/lib/active_record.rb +66 -46
  210. data/lib/rails/generators/active_record/migration/migration_generator.rb +53 -8
  211. data/lib/rails/generators/active_record/{model/templates/migration.rb → migration/templates/create_table_migration.rb} +5 -1
  212. data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
  213. data/lib/rails/generators/active_record/migration.rb +11 -8
  214. data/lib/rails/generators/active_record/model/model_generator.rb +9 -4
  215. data/lib/rails/generators/active_record/model/templates/model.rb +4 -6
  216. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  217. data/lib/rails/generators/active_record.rb +3 -11
  218. metadata +101 -45
  219. data/examples/associations.png +0 -0
  220. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
  221. data/lib/active_record/associations/join_helper.rb +0 -55
  222. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  223. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  224. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  225. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  226. data/lib/active_record/dynamic_finder_match.rb +0 -68
  227. data/lib/active_record/dynamic_scope_match.rb +0 -23
  228. data/lib/active_record/fixtures/file.rb +0 -65
  229. data/lib/active_record/identity_map.rb +0 -162
  230. data/lib/active_record/observer.rb +0 -121
  231. data/lib/active_record/session_store.rb +0 -360
  232. data/lib/active_record/test_case.rb +0 -73
  233. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  234. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  235. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  236. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -0,0 +1,193 @@
1
+ require 'active_support/core_ext/hash/keys'
2
+ require "set"
3
+
4
+ module ActiveRecord
5
+ class Relation
6
+ class HashMerger # :nodoc:
7
+ attr_reader :relation, :hash
8
+
9
+ def initialize(relation, hash)
10
+ hash.assert_valid_keys(*Relation::VALUE_METHODS)
11
+
12
+ @relation = relation
13
+ @hash = hash
14
+ end
15
+
16
+ def merge #:nodoc:
17
+ Merger.new(relation, other).merge
18
+ end
19
+
20
+ # Applying values to a relation has some side effects. E.g.
21
+ # interpolation might take place for where values. So we should
22
+ # build a relation to merge in rather than directly merging
23
+ # the values.
24
+ def other
25
+ other = Relation.create(relation.klass, relation.table)
26
+ hash.each { |k, v|
27
+ if k == :joins
28
+ if Hash === v
29
+ other.joins!(v)
30
+ else
31
+ other.joins!(*v)
32
+ end
33
+ elsif k == :select
34
+ other._select!(v)
35
+ else
36
+ other.send("#{k}!", v)
37
+ end
38
+ }
39
+ other
40
+ end
41
+ end
42
+
43
+ class Merger # :nodoc:
44
+ attr_reader :relation, :values, :other
45
+
46
+ def initialize(relation, other)
47
+ @relation = relation
48
+ @values = other.values
49
+ @other = other
50
+ end
51
+
52
+ NORMAL_VALUES = Relation::SINGLE_VALUE_METHODS +
53
+ Relation::MULTI_VALUE_METHODS -
54
+ [:includes, :preload, :joins, :where, :order, :bind, :reverse_order, :lock, :create_with, :reordering, :from] # :nodoc:
55
+
56
+
57
+ def normal_values
58
+ NORMAL_VALUES
59
+ end
60
+
61
+ def merge
62
+ normal_values.each do |name|
63
+ value = values[name]
64
+ # The unless clause is here mostly for performance reasons (since the `send` call might be moderately
65
+ # expensive), most of the time the value is going to be `nil` or `.blank?`, the only catch is that
66
+ # `false.blank?` returns `true`, so there needs to be an extra check so that explicit `false` values
67
+ # don't fall through the cracks.
68
+ unless value.nil? || (value.blank? && false != value)
69
+ if name == :select
70
+ relation._select!(*value)
71
+ else
72
+ relation.send("#{name}!", *value)
73
+ end
74
+ end
75
+ end
76
+
77
+ merge_multi_values
78
+ merge_single_values
79
+ merge_preloads
80
+ merge_joins
81
+
82
+ relation
83
+ end
84
+
85
+ private
86
+
87
+ def merge_preloads
88
+ return if other.preload_values.empty? && other.includes_values.empty?
89
+
90
+ if other.klass == relation.klass
91
+ relation.preload!(*other.preload_values) unless other.preload_values.empty?
92
+ relation.includes!(other.includes_values) unless other.includes_values.empty?
93
+ else
94
+ reflection = relation.klass.reflect_on_all_associations.find do |r|
95
+ r.class_name == other.klass.name
96
+ end || return
97
+
98
+ unless other.preload_values.empty?
99
+ relation.preload! reflection.name => other.preload_values
100
+ end
101
+
102
+ unless other.includes_values.empty?
103
+ relation.includes! reflection.name => other.includes_values
104
+ end
105
+ end
106
+ end
107
+
108
+ def merge_joins
109
+ return if other.joins_values.blank?
110
+
111
+ if other.klass == relation.klass
112
+ relation.joins!(*other.joins_values)
113
+ else
114
+ joins_dependency, rest = other.joins_values.partition do |join|
115
+ case join
116
+ when Hash, Symbol, Array
117
+ true
118
+ else
119
+ false
120
+ end
121
+ end
122
+
123
+ join_dependency = ActiveRecord::Associations::JoinDependency.new(other.klass,
124
+ joins_dependency,
125
+ [])
126
+ relation.joins! rest
127
+
128
+ @relation = relation.joins join_dependency
129
+ end
130
+ end
131
+
132
+ def merge_multi_values
133
+ lhs_wheres = relation.where_values
134
+ rhs_wheres = other.where_values
135
+
136
+ lhs_binds = relation.bind_values
137
+ rhs_binds = other.bind_values
138
+
139
+ removed, kept = partition_overwrites(lhs_wheres, rhs_wheres)
140
+
141
+ where_values = kept + rhs_wheres
142
+ bind_values = filter_binds(lhs_binds, removed) + rhs_binds
143
+
144
+ relation.where_values = where_values
145
+ relation.bind_values = bind_values
146
+
147
+ if other.reordering_value
148
+ # override any order specified in the original relation
149
+ relation.reorder! other.order_values
150
+ elsif other.order_values
151
+ # merge in order_values from relation
152
+ relation.order! other.order_values
153
+ end
154
+
155
+ relation.extend(*other.extending_values) unless other.extending_values.blank?
156
+ end
157
+
158
+ def merge_single_values
159
+ relation.from_value = other.from_value unless relation.from_value
160
+ relation.lock_value = other.lock_value unless relation.lock_value
161
+
162
+ unless other.create_with_value.blank?
163
+ relation.create_with_value = (relation.create_with_value || {}).merge(other.create_with_value)
164
+ end
165
+ end
166
+
167
+ def filter_binds(lhs_binds, removed_wheres)
168
+ return lhs_binds if removed_wheres.empty?
169
+
170
+ set = Set.new removed_wheres.map { |x| x.left.name.to_s }
171
+ lhs_binds.dup.delete_if { |col,_| set.include? col.name }
172
+ end
173
+
174
+ # Remove equalities from the existing relation with a LHS which is
175
+ # present in the relation being merged in.
176
+ # returns [things_to_remove, things_to_keep]
177
+ def partition_overwrites(lhs_wheres, rhs_wheres)
178
+ if lhs_wheres.empty? || rhs_wheres.empty?
179
+ return [[], lhs_wheres]
180
+ end
181
+
182
+ nodes = rhs_wheres.find_all do |w|
183
+ w.respond_to?(:operator) && w.operator == :==
184
+ end
185
+ seen = Set.new(nodes) { |node| node.left }
186
+
187
+ lhs_wheres.partition do |w|
188
+ w.respond_to?(:operator) && w.operator == :== && seen.include?(w.left)
189
+ end
190
+ end
191
+ end
192
+ end
193
+ end
@@ -0,0 +1,48 @@
1
+ require 'active_support/core_ext/string/filters'
2
+
3
+ module ActiveRecord
4
+ class PredicateBuilder
5
+ class ArrayHandler # :nodoc:
6
+ def call(attribute, value)
7
+ values = value.map { |x| x.is_a?(Base) ? x.id : x }
8
+ nils, values = values.partition(&:nil?)
9
+
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
+ return attribute.in([]) if values.empty? && nils.empty?
22
+
23
+ ranges, values = values.partition { |v| v.is_a?(Range) }
24
+
25
+ values_predicate =
26
+ case values.length
27
+ when 0 then NullPredicate
28
+ when 1 then attribute.eq(values.first)
29
+ else attribute.in(values)
30
+ end
31
+
32
+ unless nils.empty?
33
+ values_predicate = values_predicate.or(attribute.eq(nil))
34
+ end
35
+
36
+ array_predicates = ranges.map { |range| attribute.between(range) }
37
+ array_predicates.unshift(values_predicate)
38
+ array_predicates.inject { |composite, predicate| composite.or(predicate) }
39
+ end
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,13 @@
1
+ module ActiveRecord
2
+ class PredicateBuilder
3
+ class RelationHandler # :nodoc:
4
+ def call(attribute, value)
5
+ if value.select_values.empty?
6
+ value = value.select(value.klass.arel_table[value.klass.primary_key])
7
+ end
8
+
9
+ attribute.in(value.arel)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,63 +1,155 @@
1
1
  module ActiveRecord
2
2
  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
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
18
+ end
6
19
 
7
- if allow_table_name && value.is_a?(Hash)
8
- table = Arel::Table.new(column, engine)
20
+ def self.build_from_hash(klass, attributes, default_table)
21
+ queries = []
9
22
 
23
+ attributes.each do |column, value|
24
+ table = default_table
25
+
26
+ if value.is_a?(Hash)
10
27
  if value.empty?
11
- '1 = 2'
28
+ queries << '1=0'
12
29
  else
13
- build_from_hash(engine, value, table, false)
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
14
36
  end
15
37
  else
16
38
  column = column.to_s
17
39
 
18
- if allow_table_name && column.include?('.')
40
+ if column.include?('.')
19
41
  table_name, column = column.split('.', 2)
20
- table = Arel::Table.new(table_name, engine)
42
+ table = Arel::Table.new(table_name, default_table.engine)
21
43
  end
22
44
 
23
- attribute = table[column]
24
-
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)}
32
-
33
- array_predicates = ranges.map {|range| attribute.in(range)}
34
-
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))
41
- end
42
- else
43
- array_predicates << attribute.in(values)
44
- end
45
+ queries.concat expand(klass, table, column, value)
46
+ end
47
+ end
45
48
 
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)
54
- else
55
- attribute.eq(value)
56
- end
49
+ queries
50
+ end
51
+
52
+ def self.expand(klass, table, column, value)
53
+ queries = []
54
+
55
+ # Find the foreign key when using queries such as:
56
+ # Post.where(author: author)
57
+ #
58
+ # For polymorphic relationships, find the foreign key and type:
59
+ # 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)
57
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
89
+ end
90
+
91
+ def self.references(attributes)
92
+ attributes.map do |key, value|
93
+ if value.is_a?(Hash)
94
+ key
95
+ else
96
+ key = key.to_s
97
+ key.split('.').first if key.include?('.')
98
+ end
99
+ end.compact
100
+ end
101
+
102
+ # Define how a class is converted to Arel nodes when passed to +where+.
103
+ # The handler can be any object that responds to +call+, and will be used
104
+ # for any value that +===+ the class given. For example:
105
+ #
106
+ # MyCustomDateRange = Struct.new(:start, :end)
107
+ # handler = proc do |column, range|
108
+ # Arel::Nodes::Between.new(column,
109
+ # Arel::Nodes::And.new([range.start, range.end])
110
+ # )
111
+ # end
112
+ # ActiveRecord::PredicateBuilder.register_handler(MyCustomDateRange, handler)
113
+ def self.register_handler(klass, handler)
114
+ @handlers.unshift([klass, handler])
115
+ end
116
+
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)
127
+ handler_for(value).call(attribute, value)
128
+ end
129
+ private_class_method :build
130
+
131
+ def self.handler_for(object)
132
+ @handlers.detect { |klass, _| klass === object }.last
133
+ 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
58
146
  end
147
+ end
59
148
 
60
- predicates.flatten
149
+ def self.can_be_bound?(value) # :nodoc:
150
+ !value.nil? &&
151
+ !value.is_a?(Hash) &&
152
+ handler_for(value) == BASIC_OBJECT_HANDLER
61
153
  end
62
154
  end
63
155
  end