activerecord 3.1.10 → 4.2.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (237) hide show
  1. checksums.yaml +6 -6
  2. data/CHANGELOG.md +1837 -338
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +39 -43
  5. data/examples/performance.rb +51 -20
  6. data/examples/simple.rb +4 -4
  7. data/lib/active_record/aggregations.rb +57 -43
  8. data/lib/active_record/association_relation.rb +35 -0
  9. data/lib/active_record/associations/alias_tracker.rb +47 -39
  10. data/lib/active_record/associations/association.rb +71 -85
  11. data/lib/active_record/associations/association_scope.rb +138 -89
  12. data/lib/active_record/associations/belongs_to_association.rb +65 -25
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +9 -3
  14. data/lib/active_record/associations/builder/association.rb +125 -29
  15. data/lib/active_record/associations/builder/belongs_to.rb +91 -60
  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 -52
  20. data/lib/active_record/associations/builder/singular_association.rb +22 -29
  21. data/lib/active_record/associations/collection_association.rb +294 -187
  22. data/lib/active_record/associations/collection_proxy.rb +961 -94
  23. data/lib/active_record/associations/foreign_association.rb +11 -0
  24. data/lib/active_record/associations/has_many_association.rb +118 -23
  25. data/lib/active_record/associations/has_many_through_association.rb +115 -45
  26. data/lib/active_record/associations/has_one_association.rb +57 -24
  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 -102
  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 +61 -32
  38. data/lib/active_record/associations/preloader.rb +113 -87
  39. data/lib/active_record/associations/singular_association.rb +29 -13
  40. data/lib/active_record/associations/through_association.rb +37 -19
  41. data/lib/active_record/associations.rb +505 -371
  42. data/lib/active_record/attribute.rb +163 -0
  43. data/lib/active_record/attribute_assignment.rb +212 -0
  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 +141 -51
  47. data/lib/active_record/attribute_methods/primary_key.rb +87 -36
  48. data/lib/active_record/attribute_methods/query.rb +5 -4
  49. data/lib/active_record/attribute_methods/read.rb +74 -117
  50. data/lib/active_record/attribute_methods/serialization.rb +70 -0
  51. data/lib/active_record/attribute_methods/time_zone_conversion.rb +49 -47
  52. data/lib/active_record/attribute_methods/write.rb +60 -21
  53. data/lib/active_record/attribute_methods.rb +409 -48
  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 +279 -232
  58. data/lib/active_record/base.rb +84 -1969
  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 +422 -243
  63. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  64. data/lib/active_record/connection_adapters/abstract/database_statements.rb +170 -194
  65. data/lib/active_record/connection_adapters/abstract/query_cache.rb +32 -19
  66. data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -57
  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 +273 -170
  70. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +731 -254
  72. data/lib/active_record/connection_adapters/abstract/transaction.rb +215 -0
  73. data/lib/active_record/connection_adapters/abstract_adapter.rb +339 -95
  74. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +946 -0
  75. data/lib/active_record/connection_adapters/column.rb +33 -221
  76. data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
  77. data/lib/active_record/connection_adapters/mysql2_adapter.rb +140 -602
  78. data/lib/active_record/connection_adapters/mysql_adapter.rb +254 -756
  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 +445 -902
  114. data/lib/active_record/connection_adapters/schema_cache.rb +94 -0
  115. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +578 -25
  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 +159 -102
  119. data/lib/active_record/dynamic_matchers.rb +140 -0
  120. data/lib/active_record/enum.rb +197 -0
  121. data/lib/active_record/errors.rb +102 -34
  122. data/lib/active_record/explain.rb +38 -0
  123. data/lib/active_record/explain_registry.rb +30 -0
  124. data/lib/active_record/explain_subscriber.rb +29 -0
  125. data/lib/active_record/fixture_set/file.rb +56 -0
  126. data/lib/active_record/fixtures.rb +318 -260
  127. data/lib/active_record/gem_version.rb +15 -0
  128. data/lib/active_record/inheritance.rb +247 -0
  129. data/lib/active_record/integration.rb +113 -0
  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 +80 -52
  133. data/lib/active_record/locking/pessimistic.rb +27 -5
  134. data/lib/active_record/log_subscriber.rb +25 -18
  135. data/lib/active_record/migration/command_recorder.rb +130 -38
  136. data/lib/active_record/migration/join_table.rb +15 -0
  137. data/lib/active_record/migration.rb +532 -201
  138. data/lib/active_record/model_schema.rb +342 -0
  139. data/lib/active_record/nested_attributes.rb +229 -139
  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 +304 -99
  143. data/lib/active_record/query_cache.rb +25 -43
  144. data/lib/active_record/querying.rb +68 -0
  145. data/lib/active_record/railtie.rb +86 -45
  146. data/lib/active_record/railties/console_sandbox.rb +3 -4
  147. data/lib/active_record/railties/controller_runtime.rb +7 -4
  148. data/lib/active_record/railties/databases.rake +198 -377
  149. data/lib/active_record/railties/jdbcmysql_error.rb +2 -2
  150. data/lib/active_record/readonly_attributes.rb +23 -0
  151. data/lib/active_record/reflection.rb +516 -165
  152. data/lib/active_record/relation/batches.rb +96 -45
  153. data/lib/active_record/relation/calculations.rb +221 -144
  154. data/lib/active_record/relation/delegation.rb +140 -0
  155. data/lib/active_record/relation/finder_methods.rb +362 -243
  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 -41
  160. data/lib/active_record/relation/query_methods.rb +982 -155
  161. data/lib/active_record/relation/spawn_methods.rb +50 -110
  162. data/lib/active_record/relation.rb +371 -180
  163. data/lib/active_record/result.rb +109 -12
  164. data/lib/active_record/runtime_registry.rb +22 -0
  165. data/lib/active_record/sanitization.rb +191 -0
  166. data/lib/active_record/schema.rb +19 -13
  167. data/lib/active_record/schema_dumper.rb +111 -61
  168. data/lib/active_record/schema_migration.rb +53 -0
  169. data/lib/active_record/scoping/default.rb +135 -0
  170. data/lib/active_record/scoping/named.rb +164 -0
  171. data/lib/active_record/scoping.rb +87 -0
  172. data/lib/active_record/serialization.rb +7 -45
  173. data/lib/active_record/serializers/xml_serializer.rb +14 -65
  174. data/lib/active_record/statement_cache.rb +111 -0
  175. data/lib/active_record/store.rb +205 -0
  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 +35 -14
  181. data/lib/active_record/transactions.rb +141 -74
  182. data/lib/active_record/translation.rb +22 -0
  183. data/lib/active_record/type/big_integer.rb +13 -0
  184. data/lib/active_record/type/binary.rb +50 -0
  185. data/lib/active_record/type/boolean.rb +31 -0
  186. data/lib/active_record/type/date.rb +50 -0
  187. data/lib/active_record/type/date_time.rb +54 -0
  188. data/lib/active_record/type/decimal.rb +64 -0
  189. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  190. data/lib/active_record/type/decorator.rb +14 -0
  191. data/lib/active_record/type/float.rb +19 -0
  192. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  193. data/lib/active_record/type/integer.rb +59 -0
  194. data/lib/active_record/type/mutable.rb +16 -0
  195. data/lib/active_record/type/numeric.rb +36 -0
  196. data/lib/active_record/type/serialized.rb +62 -0
  197. data/lib/active_record/type/string.rb +40 -0
  198. data/lib/active_record/type/text.rb +11 -0
  199. data/lib/active_record/type/time.rb +26 -0
  200. data/lib/active_record/type/time_value.rb +38 -0
  201. data/lib/active_record/type/type_map.rb +64 -0
  202. data/lib/active_record/type/unsigned_integer.rb +15 -0
  203. data/lib/active_record/type/value.rb +110 -0
  204. data/lib/active_record/type.rb +23 -0
  205. data/lib/active_record/validations/associated.rb +27 -18
  206. data/lib/active_record/validations/presence.rb +67 -0
  207. data/lib/active_record/validations/uniqueness.rb +125 -66
  208. data/lib/active_record/validations.rb +37 -30
  209. data/lib/active_record/version.rb +5 -7
  210. data/lib/active_record.rb +80 -25
  211. data/lib/rails/generators/active_record/migration/migration_generator.rb +54 -9
  212. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
  213. data/lib/rails/generators/active_record/migration/templates/migration.rb +25 -11
  214. data/lib/rails/generators/active_record/migration.rb +11 -8
  215. data/lib/rails/generators/active_record/model/model_generator.rb +17 -4
  216. data/lib/rails/generators/active_record/model/templates/model.rb +5 -2
  217. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  218. data/lib/rails/generators/active_record.rb +3 -11
  219. metadata +132 -53
  220. data/examples/associations.png +0 -0
  221. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -62
  222. data/lib/active_record/associations/join_helper.rb +0 -55
  223. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  224. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -135
  225. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -556
  226. data/lib/active_record/dynamic_finder_match.rb +0 -56
  227. data/lib/active_record/dynamic_scope_match.rb +0 -23
  228. data/lib/active_record/identity_map.rb +0 -163
  229. data/lib/active_record/named_scope.rb +0 -200
  230. data/lib/active_record/observer.rb +0 -121
  231. data/lib/active_record/session_store.rb +0 -358
  232. data/lib/active_record/test_case.rb +0 -69
  233. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -17
  234. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  235. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  236. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  237. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -16
@@ -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,61 +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.to_sym]
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|
31
- x.is_a?(ActiveRecord::Base) ? x.id : x
32
- }
33
-
34
- if values.include?(nil)
35
- values = values.compact
36
- if values.empty?
37
- attribute.eq nil
38
- else
39
- attribute.in(values.compact).or attribute.eq(nil)
40
- end
41
- else
42
- attribute.in(values)
43
- end
45
+ queries.concat expand(klass, table, column, value)
46
+ end
47
+ end
44
48
 
45
- when Range, Arel::Relation
46
- attribute.in(value)
47
- when ActiveRecord::Base
48
- attribute.eq(value.id)
49
- when Class
50
- # FIXME: I think we need to deprecate this behavior
51
- attribute.eq(value.name)
52
- else
53
- attribute.eq(value)
54
- 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)
55
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
56
146
  end
147
+ end
57
148
 
58
- 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
59
153
  end
60
154
  end
61
155
  end