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,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ class Relation
5
+ module RecordFetchWarning
6
+ # When this module is prepended to ActiveRecord::Relation and
7
+ # +config.active_record.warn_on_records_fetched_greater_than+ is
8
+ # set to an integer, if the number of records a query returns is
9
+ # greater than the value of +warn_on_records_fetched_greater_than+,
10
+ # a warning is logged. This allows for the detection of queries that
11
+ # return a large number of records, which could cause memory bloat.
12
+ #
13
+ # In most cases, fetching large number of records can be performed
14
+ # efficiently using the ActiveRecord::Batches methods.
15
+ # See ActiveRecord::Batches for more information.
16
+ def exec_queries
17
+ QueryRegistry.reset
18
+
19
+ super.tap do
20
+ if logger && warn_on_records_fetched_greater_than
21
+ if @records.length > warn_on_records_fetched_greater_than
22
+ logger.warn "Query fetched #{@records.size} #{@klass} records: #{QueryRegistry.queries.join(";")}"
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ # :stopdoc:
29
+ ActiveSupport::Notifications.subscribe("sql.active_record") do |*, payload|
30
+ QueryRegistry.queries << payload[:sql]
31
+ end
32
+ # :startdoc:
33
+
34
+ class QueryRegistry # :nodoc:
35
+ extend ActiveSupport::PerThreadRegistry
36
+
37
+ attr_reader :queries
38
+
39
+ def initialize
40
+ @queries = []
41
+ end
42
+
43
+ def reset
44
+ @queries.clear
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ ActiveRecord::Relation.prepend ActiveRecord::Relation::RecordFetchWarning
@@ -1,180 +1,77 @@
1
- require 'active_support/core_ext/object/blank'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/hash/except"
4
+ require "active_support/core_ext/hash/slice"
5
+ require "active_record/relation/merger"
2
6
 
3
7
  module ActiveRecord
4
8
  module SpawnMethods
5
- def merge(r)
6
- return self unless r
7
- return to_a & r if r.is_a?(Array)
8
-
9
- merged_relation = clone
10
-
11
- r = r.with_default_scope if r.default_scoped? && r.klass != klass
12
-
13
- Relation::ASSOCIATION_METHODS.each do |method|
14
- value = r.send(:"#{method}_values")
15
-
16
- unless value.empty?
17
- if method == :includes
18
- merged_relation = merged_relation.includes(value)
19
- else
20
- merge_relation_method(merged_relation, method, value)
21
- end
22
- end
23
- end
24
-
25
- (Relation::MULTI_VALUE_METHODS - [:joins, :where, :order]).each do |method|
26
- value = r.send(:"#{method}_values")
27
- merge_relation_method(merged_relation, method, value) if value.present?
28
- end
29
-
30
- merge_joins(merged_relation, r)
31
-
32
- merged_wheres = @where_values + r.where_values
33
-
34
- unless @where_values.empty?
35
- # Remove duplicate ARel attributes. Last one wins.
36
- seen = Hash.new { |h,table| h[table] = {} }
37
- merged_wheres = merged_wheres.reverse.reject { |w|
38
- nuke = false
39
- if w.respond_to?(:operator) && w.operator == :== &&
40
- w.left.respond_to?(:relation)
41
- name = w.left.name
42
- table = w.left.relation.name
43
- nuke = seen[table][name]
44
- seen[table][name] = true
45
- end
46
- nuke
47
- }.reverse
48
- end
49
-
50
- merged_relation.where_values = merged_wheres
9
+ # This is overridden by Associations::CollectionProxy
10
+ def spawn #:nodoc:
11
+ @delegate_to_klass ? klass.all : clone
12
+ end
51
13
 
52
- (Relation::SINGLE_VALUE_METHODS - [:lock, :create_with, :reordering]).each do |method|
53
- value = r.send(:"#{method}_value")
54
- merged_relation.send(:"#{method}_value=", value) unless value.nil?
14
+ # Merges in the conditions from <tt>other</tt>, if <tt>other</tt> is an ActiveRecord::Relation.
15
+ # Returns an array representing the intersection of the resulting records with <tt>other</tt>, if <tt>other</tt> is an array.
16
+ #
17
+ # Post.where(published: true).joins(:comments).merge( Comment.where(spam: false) )
18
+ # # Performs a single join query with both where conditions.
19
+ #
20
+ # recent_posts = Post.order('created_at DESC').first(5)
21
+ # Post.where(published: true).merge(recent_posts)
22
+ # # Returns the intersection of all published posts with the 5 most recently created posts.
23
+ # # (This is just an example. You'd probably want to do this with a single query!)
24
+ #
25
+ # Procs will be evaluated by merge:
26
+ #
27
+ # Post.where(published: true).merge(-> { joins(:comments) })
28
+ # # => Post.where(published: true).joins(:comments)
29
+ #
30
+ # This is mainly intended for sharing common conditions between multiple associations.
31
+ def merge(other)
32
+ if other.is_a?(Array)
33
+ records & other
34
+ elsif other
35
+ spawn.merge!(other)
36
+ else
37
+ raise ArgumentError, "invalid argument: #{other.inspect}."
55
38
  end
39
+ end
56
40
 
57
- merged_relation.lock_value = r.lock_value unless merged_relation.lock_value
58
-
59
- merged_relation = merged_relation.create_with(r.create_with_value) unless r.create_with_value.empty?
60
-
61
- if (r.reordering_value)
62
- # override any order specified in the original relation
63
- merged_relation.reordering_value = true
64
- merged_relation.order_values = r.order_values
41
+ def merge!(other) # :nodoc:
42
+ if other.is_a?(Hash)
43
+ Relation::HashMerger.new(self, other).merge
44
+ elsif other.is_a?(Relation)
45
+ Relation::Merger.new(self, other).merge
46
+ elsif other.respond_to?(:to_proc)
47
+ instance_exec(&other)
65
48
  else
66
- # merge in order_values from r
67
- merged_relation.order_values += r.order_values
49
+ raise ArgumentError, "#{other.inspect} is not an ActiveRecord::Relation"
68
50
  end
69
-
70
- # Apply scope extension modules
71
- merged_relation.send :apply_modules, r.extensions
72
-
73
- merged_relation
74
51
  end
75
52
 
76
53
  # Removes from the query the condition(s) specified in +skips+.
77
54
  #
78
- # Example:
79
- #
80
55
  # Post.order('id asc').except(:order) # discards the order condition
81
56
  # Post.where('id > 10').order('id asc').except(:where) # discards the where condition but keeps the order
82
- #
83
57
  def except(*skips)
84
- result = self.class.new(@klass, table)
85
- result.default_scoped = default_scoped
86
-
87
- ((Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS) - skips).each do |method|
88
- result.send(:"#{method}_values=", send(:"#{method}_values"))
89
- end
90
-
91
- (Relation::SINGLE_VALUE_METHODS - skips).each do |method|
92
- result.send(:"#{method}_value=", send(:"#{method}_value"))
93
- end
94
-
95
- # Apply scope extension modules
96
- result.send(:apply_modules, extensions)
97
-
98
- result
58
+ relation_with values.except(*skips)
99
59
  end
100
60
 
101
61
  # Removes any condition from the query other than the one(s) specified in +onlies+.
102
62
  #
103
- # Example:
104
- #
105
63
  # Post.order('id asc').only(:where) # discards the order condition
106
64
  # Post.order('id asc').only(:where, :order) # uses the specified order
107
- #
108
65
  def only(*onlies)
109
- result = self.class.new(@klass, table)
110
- result.default_scoped = default_scoped
111
-
112
- ((Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS) & onlies).each do |method|
113
- result.send(:"#{method}_values=", send(:"#{method}_values"))
114
- end
115
-
116
- (Relation::SINGLE_VALUE_METHODS & onlies).each do |method|
117
- result.send(:"#{method}_value=", send(:"#{method}_value"))
118
- end
119
-
120
- # Apply scope extension modules
121
- result.send(:apply_modules, extensions)
122
-
123
- result
124
- end
125
-
126
- VALID_FIND_OPTIONS = [ :conditions, :include, :joins, :limit, :offset, :extend,
127
- :order, :select, :readonly, :group, :having, :from, :lock ]
128
-
129
- def apply_finder_options(options)
130
- relation = clone
131
- return relation unless options
132
-
133
- options.assert_valid_keys(VALID_FIND_OPTIONS)
134
- finders = options.dup
135
- finders.delete_if { |key, value| value.nil? && key != :limit }
136
-
137
- ([:joins, :select, :group, :order, :having, :limit, :offset, :from, :lock, :readonly] & finders.keys).each do |finder|
138
- relation = relation.send(finder, finders[finder])
139
- end
140
-
141
- relation = relation.where(finders[:conditions]) if options.has_key?(:conditions)
142
- relation = relation.includes(finders[:include]) if options.has_key?(:include)
143
- relation = relation.extending(finders[:extend]) if options.has_key?(:extend)
144
-
145
- relation
66
+ relation_with values.slice(*onlies)
146
67
  end
147
68
 
148
69
  private
149
70
 
150
- def merge_joins(relation, other)
151
- values = other.joins_values
152
- return if values.blank?
153
-
154
- if other.klass >= relation.klass
155
- relation.joins_values += values
156
- else
157
- joins_dependency, rest = values.partition do |join|
158
- case join
159
- when Hash, Symbol, Array
160
- true
161
- else
162
- false
163
- end
164
- end
165
-
166
- join_dependency = ActiveRecord::Associations::JoinDependency.new(
167
- other.klass,
168
- joins_dependency,
169
- []
170
- )
171
-
172
- relation.joins_values += join_dependency.join_associations + rest
173
- end
174
- end
175
-
176
- def merge_relation_method(relation, method, value)
177
- relation.send(:"#{method}_values=", relation.send(:"#{method}_values") + value)
71
+ def relation_with(values)
72
+ result = Relation.create(klass, values: values)
73
+ result.extend(*extending_values) if extending_values.any?
74
+ result
178
75
  end
179
76
  end
180
77
  end
@@ -0,0 +1,186 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ class Relation
5
+ class WhereClause # :nodoc:
6
+ delegate :any?, :empty?, to: :predicates
7
+
8
+ def initialize(predicates)
9
+ @predicates = predicates
10
+ end
11
+
12
+ def +(other)
13
+ WhereClause.new(
14
+ predicates + other.predicates,
15
+ )
16
+ end
17
+
18
+ def -(other)
19
+ WhereClause.new(
20
+ predicates - other.predicates,
21
+ )
22
+ end
23
+
24
+ def merge(other)
25
+ WhereClause.new(
26
+ predicates_unreferenced_by(other) + other.predicates,
27
+ )
28
+ end
29
+
30
+ def except(*columns)
31
+ WhereClause.new(except_predicates(columns))
32
+ end
33
+
34
+ def or(other)
35
+ left = self - other
36
+ common = self - left
37
+ right = other - common
38
+
39
+ if left.empty? || right.empty?
40
+ common
41
+ else
42
+ or_clause = WhereClause.new(
43
+ [left.ast.or(right.ast)],
44
+ )
45
+ common + or_clause
46
+ end
47
+ end
48
+
49
+ def to_h(table_name = nil)
50
+ equalities = equalities(predicates)
51
+ if table_name
52
+ equalities = equalities.select do |node|
53
+ node.left.relation.name == table_name
54
+ end
55
+ end
56
+
57
+ equalities.map { |node|
58
+ name = node.left.name.to_s
59
+ value = extract_node_value(node.right)
60
+ [name, value]
61
+ }.to_h
62
+ end
63
+
64
+ def ast
65
+ Arel::Nodes::And.new(predicates_with_wrapped_sql_literals)
66
+ end
67
+
68
+ def ==(other)
69
+ other.is_a?(WhereClause) &&
70
+ predicates == other.predicates
71
+ end
72
+
73
+ def invert
74
+ WhereClause.new(inverted_predicates)
75
+ end
76
+
77
+ def self.empty
78
+ @empty ||= new([])
79
+ end
80
+
81
+ protected
82
+
83
+ attr_reader :predicates
84
+
85
+ def referenced_columns
86
+ @referenced_columns ||= begin
87
+ equality_nodes = predicates.select { |n| equality_node?(n) }
88
+ Set.new(equality_nodes, &:left)
89
+ end
90
+ end
91
+
92
+ private
93
+ def equalities(predicates)
94
+ equalities = []
95
+
96
+ predicates.each do |node|
97
+ case node
98
+ when Arel::Nodes::Equality
99
+ equalities << node
100
+ when Arel::Nodes::And
101
+ equalities.concat equalities(node.children)
102
+ end
103
+ end
104
+
105
+ equalities
106
+ end
107
+
108
+ def predicates_unreferenced_by(other)
109
+ predicates.reject do |n|
110
+ equality_node?(n) && other.referenced_columns.include?(n.left)
111
+ end
112
+ end
113
+
114
+ def equality_node?(node)
115
+ node.respond_to?(:operator) && node.operator == :==
116
+ end
117
+
118
+ def inverted_predicates
119
+ predicates.map { |node| invert_predicate(node) }
120
+ end
121
+
122
+ def invert_predicate(node)
123
+ case node
124
+ when NilClass
125
+ raise ArgumentError, "Invalid argument for .where.not(), got nil."
126
+ when Arel::Nodes::In
127
+ Arel::Nodes::NotIn.new(node.left, node.right)
128
+ when Arel::Nodes::Equality
129
+ Arel::Nodes::NotEqual.new(node.left, node.right)
130
+ when String
131
+ Arel::Nodes::Not.new(Arel::Nodes::SqlLiteral.new(node))
132
+ else
133
+ Arel::Nodes::Not.new(node)
134
+ end
135
+ end
136
+
137
+ def except_predicates(columns)
138
+ predicates.reject do |node|
139
+ case node
140
+ when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThan, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThan, Arel::Nodes::GreaterThanOrEqual
141
+ subrelation = (node.left.kind_of?(Arel::Attributes::Attribute) ? node.left : node.right)
142
+ columns.include?(subrelation.name.to_s)
143
+ end
144
+ end
145
+ end
146
+
147
+ def predicates_with_wrapped_sql_literals
148
+ non_empty_predicates.map do |node|
149
+ case node
150
+ when Arel::Nodes::SqlLiteral, ::String
151
+ wrap_sql_literal(node)
152
+ else node
153
+ end
154
+ end
155
+ end
156
+
157
+ ARRAY_WITH_EMPTY_STRING = [""]
158
+ def non_empty_predicates
159
+ predicates - ARRAY_WITH_EMPTY_STRING
160
+ end
161
+
162
+ def wrap_sql_literal(node)
163
+ if ::String === node
164
+ node = Arel.sql(node)
165
+ end
166
+ Arel::Nodes::Grouping.new(node)
167
+ end
168
+
169
+ def extract_node_value(node)
170
+ case node
171
+ when Array
172
+ node.map { |v| extract_node_value(v) }
173
+ when Arel::Nodes::Casted, Arel::Nodes::Quoted
174
+ node.val
175
+ when Arel::Nodes::BindParam
176
+ value = node.value
177
+ if value.respond_to?(:value_before_type_cast)
178
+ value.value_before_type_cast
179
+ else
180
+ value
181
+ end
182
+ end
183
+ end
184
+ end
185
+ end
186
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ class Relation
5
+ class WhereClauseFactory # :nodoc:
6
+ def initialize(klass, predicate_builder)
7
+ @klass = klass
8
+ @predicate_builder = predicate_builder
9
+ end
10
+
11
+ def build(opts, other)
12
+ case opts
13
+ when String, Array
14
+ parts = [klass.sanitize_sql(other.empty? ? opts : ([opts] + other))]
15
+ when Hash
16
+ attributes = predicate_builder.resolve_column_aliases(opts)
17
+ attributes.stringify_keys!
18
+
19
+ parts = predicate_builder.build_from_hash(attributes)
20
+ when Arel::Nodes::Node
21
+ parts = [opts]
22
+ else
23
+ raise ArgumentError, "Unsupported argument type: #{opts} (#{opts.class})"
24
+ end
25
+
26
+ WhereClause.new(parts)
27
+ end
28
+
29
+ protected
30
+
31
+ attr_reader :klass, :predicate_builder
32
+ end
33
+ end
34
+ end