activerecord 3.2.19 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


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

Files changed (264) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1715 -604
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +40 -45
  5. data/examples/performance.rb +33 -22
  6. data/examples/simple.rb +3 -4
  7. data/lib/active_record/aggregations.rb +76 -51
  8. data/lib/active_record/association_relation.rb +35 -0
  9. data/lib/active_record/associations/alias_tracker.rb +54 -40
  10. data/lib/active_record/associations/association.rb +76 -56
  11. data/lib/active_record/associations/association_scope.rb +125 -93
  12. data/lib/active_record/associations/belongs_to_association.rb +57 -28
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -2
  14. data/lib/active_record/associations/builder/association.rb +120 -32
  15. data/lib/active_record/associations/builder/belongs_to.rb +115 -62
  16. data/lib/active_record/associations/builder/collection_association.rb +61 -53
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +117 -43
  18. data/lib/active_record/associations/builder/has_many.rb +9 -65
  19. data/lib/active_record/associations/builder/has_one.rb +18 -52
  20. data/lib/active_record/associations/builder/singular_association.rb +18 -19
  21. data/lib/active_record/associations/collection_association.rb +268 -186
  22. data/lib/active_record/associations/collection_proxy.rb +1003 -63
  23. data/lib/active_record/associations/foreign_association.rb +11 -0
  24. data/lib/active_record/associations/has_many_association.rb +81 -41
  25. data/lib/active_record/associations/has_many_through_association.rb +76 -55
  26. data/lib/active_record/associations/has_one_association.rb +51 -21
  27. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  28. data/lib/active_record/associations/join_dependency/join_association.rb +83 -108
  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 +239 -155
  32. data/lib/active_record/associations/preloader/association.rb +97 -62
  33. data/lib/active_record/associations/preloader/collection_association.rb +2 -8
  34. data/lib/active_record/associations/preloader/has_many_through.rb +7 -3
  35. data/lib/active_record/associations/preloader/has_one.rb +0 -8
  36. data/lib/active_record/associations/preloader/singular_association.rb +3 -3
  37. data/lib/active_record/associations/preloader/through_association.rb +75 -33
  38. data/lib/active_record/associations/preloader.rb +111 -79
  39. data/lib/active_record/associations/singular_association.rb +35 -13
  40. data/lib/active_record/associations/through_association.rb +41 -19
  41. data/lib/active_record/associations.rb +727 -501
  42. data/lib/active_record/attribute/user_provided_default.rb +28 -0
  43. data/lib/active_record/attribute.rb +213 -0
  44. data/lib/active_record/attribute_assignment.rb +32 -162
  45. data/lib/active_record/attribute_decorators.rb +67 -0
  46. data/lib/active_record/attribute_methods/before_type_cast.rb +52 -7
  47. data/lib/active_record/attribute_methods/dirty.rb +101 -61
  48. data/lib/active_record/attribute_methods/primary_key.rb +50 -36
  49. data/lib/active_record/attribute_methods/query.rb +7 -6
  50. data/lib/active_record/attribute_methods/read.rb +56 -117
  51. data/lib/active_record/attribute_methods/serialization.rb +43 -96
  52. data/lib/active_record/attribute_methods/time_zone_conversion.rb +93 -42
  53. data/lib/active_record/attribute_methods/write.rb +34 -45
  54. data/lib/active_record/attribute_methods.rb +333 -144
  55. data/lib/active_record/attribute_mutation_tracker.rb +70 -0
  56. data/lib/active_record/attribute_set/builder.rb +108 -0
  57. data/lib/active_record/attribute_set.rb +108 -0
  58. data/lib/active_record/attributes.rb +265 -0
  59. data/lib/active_record/autosave_association.rb +285 -223
  60. data/lib/active_record/base.rb +95 -490
  61. data/lib/active_record/callbacks.rb +95 -61
  62. data/lib/active_record/coders/json.rb +13 -0
  63. data/lib/active_record/coders/yaml_column.rb +28 -19
  64. data/lib/active_record/collection_cache_key.rb +40 -0
  65. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +724 -277
  66. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  67. data/lib/active_record/connection_adapters/abstract/database_statements.rb +199 -192
  68. data/lib/active_record/connection_adapters/abstract/query_cache.rb +31 -26
  69. data/lib/active_record/connection_adapters/abstract/quoting.rb +140 -57
  70. data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +147 -0
  72. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +419 -276
  73. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +105 -0
  74. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +963 -276
  75. data/lib/active_record/connection_adapters/abstract/transaction.rb +232 -0
  76. data/lib/active_record/connection_adapters/abstract_adapter.rb +397 -106
  77. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +643 -342
  78. data/lib/active_record/connection_adapters/column.rb +30 -259
  79. data/lib/active_record/connection_adapters/connection_specification.rb +263 -0
  80. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
  81. data/lib/active_record/connection_adapters/mysql/column.rb +50 -0
  82. data/lib/active_record/connection_adapters/mysql/database_statements.rb +125 -0
  83. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +70 -0
  84. data/lib/active_record/connection_adapters/mysql/quoting.rb +51 -0
  85. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +67 -0
  86. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +93 -0
  87. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +54 -0
  88. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +32 -0
  89. data/lib/active_record/connection_adapters/mysql2_adapter.rb +47 -196
  90. data/lib/active_record/connection_adapters/postgresql/column.rb +15 -0
  91. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +170 -0
  92. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +70 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +48 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +21 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +10 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +39 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +93 -0
  109. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -0
  110. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
  111. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  112. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  113. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  114. data/lib/active_record/connection_adapters/postgresql/oid.rb +31 -0
  115. data/lib/active_record/connection_adapters/postgresql/quoting.rb +116 -0
  116. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +49 -0
  117. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +180 -0
  118. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +47 -0
  119. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +682 -0
  120. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
  121. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  122. data/lib/active_record/connection_adapters/postgresql_adapter.rb +558 -1039
  123. data/lib/active_record/connection_adapters/schema_cache.rb +74 -36
  124. data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
  125. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +19 -0
  126. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +48 -0
  127. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  128. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +538 -24
  129. data/lib/active_record/connection_adapters/statement_pool.rb +31 -12
  130. data/lib/active_record/connection_handling.rb +155 -0
  131. data/lib/active_record/core.rb +561 -0
  132. data/lib/active_record/counter_cache.rb +146 -105
  133. data/lib/active_record/dynamic_matchers.rb +101 -64
  134. data/lib/active_record/enum.rb +234 -0
  135. data/lib/active_record/errors.rb +153 -56
  136. data/lib/active_record/explain.rb +15 -63
  137. data/lib/active_record/explain_registry.rb +30 -0
  138. data/lib/active_record/explain_subscriber.rb +10 -6
  139. data/lib/active_record/fixture_set/file.rb +77 -0
  140. data/lib/active_record/fixtures.rb +355 -232
  141. data/lib/active_record/gem_version.rb +15 -0
  142. data/lib/active_record/inheritance.rb +144 -79
  143. data/lib/active_record/integration.rb +66 -13
  144. data/lib/active_record/internal_metadata.rb +56 -0
  145. data/lib/active_record/legacy_yaml_adapter.rb +46 -0
  146. data/lib/active_record/locale/en.yml +9 -1
  147. data/lib/active_record/locking/optimistic.rb +77 -56
  148. data/lib/active_record/locking/pessimistic.rb +6 -6
  149. data/lib/active_record/log_subscriber.rb +53 -28
  150. data/lib/active_record/migration/command_recorder.rb +166 -33
  151. data/lib/active_record/migration/compatibility.rb +126 -0
  152. data/lib/active_record/migration/join_table.rb +15 -0
  153. data/lib/active_record/migration.rb +792 -264
  154. data/lib/active_record/model_schema.rb +192 -130
  155. data/lib/active_record/nested_attributes.rb +238 -145
  156. data/lib/active_record/no_touching.rb +52 -0
  157. data/lib/active_record/null_relation.rb +89 -0
  158. data/lib/active_record/persistence.rb +357 -157
  159. data/lib/active_record/query_cache.rb +22 -43
  160. data/lib/active_record/querying.rb +34 -23
  161. data/lib/active_record/railtie.rb +88 -48
  162. data/lib/active_record/railties/console_sandbox.rb +3 -4
  163. data/lib/active_record/railties/controller_runtime.rb +5 -4
  164. data/lib/active_record/railties/databases.rake +170 -422
  165. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  166. data/lib/active_record/readonly_attributes.rb +2 -5
  167. data/lib/active_record/reflection.rb +715 -189
  168. data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
  169. data/lib/active_record/relation/batches.rb +203 -50
  170. data/lib/active_record/relation/calculations.rb +203 -194
  171. data/lib/active_record/relation/delegation.rb +103 -25
  172. data/lib/active_record/relation/finder_methods.rb +457 -261
  173. data/lib/active_record/relation/from_clause.rb +32 -0
  174. data/lib/active_record/relation/merger.rb +167 -0
  175. data/lib/active_record/relation/predicate_builder/array_handler.rb +43 -0
  176. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +88 -0
  177. data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
  178. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
  179. data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
  180. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +57 -0
  181. data/lib/active_record/relation/predicate_builder/range_handler.rb +33 -0
  182. data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
  183. data/lib/active_record/relation/predicate_builder.rb +153 -48
  184. data/lib/active_record/relation/query_attribute.rb +19 -0
  185. data/lib/active_record/relation/query_methods.rb +1019 -194
  186. data/lib/active_record/relation/record_fetch_warning.rb +49 -0
  187. data/lib/active_record/relation/spawn_methods.rb +46 -150
  188. data/lib/active_record/relation/where_clause.rb +174 -0
  189. data/lib/active_record/relation/where_clause_factory.rb +38 -0
  190. data/lib/active_record/relation.rb +450 -245
  191. data/lib/active_record/result.rb +104 -12
  192. data/lib/active_record/runtime_registry.rb +22 -0
  193. data/lib/active_record/sanitization.rb +120 -94
  194. data/lib/active_record/schema.rb +28 -18
  195. data/lib/active_record/schema_dumper.rb +141 -74
  196. data/lib/active_record/schema_migration.rb +50 -0
  197. data/lib/active_record/scoping/default.rb +64 -57
  198. data/lib/active_record/scoping/named.rb +93 -108
  199. data/lib/active_record/scoping.rb +73 -121
  200. data/lib/active_record/secure_token.rb +38 -0
  201. data/lib/active_record/serialization.rb +7 -5
  202. data/lib/active_record/statement_cache.rb +113 -0
  203. data/lib/active_record/store.rb +173 -15
  204. data/lib/active_record/suppressor.rb +58 -0
  205. data/lib/active_record/table_metadata.rb +68 -0
  206. data/lib/active_record/tasks/database_tasks.rb +313 -0
  207. data/lib/active_record/tasks/mysql_database_tasks.rb +151 -0
  208. data/lib/active_record/tasks/postgresql_database_tasks.rb +110 -0
  209. data/lib/active_record/tasks/sqlite_database_tasks.rb +59 -0
  210. data/lib/active_record/timestamp.rb +42 -24
  211. data/lib/active_record/touch_later.rb +58 -0
  212. data/lib/active_record/transactions.rb +233 -105
  213. data/lib/active_record/type/adapter_specific_registry.rb +130 -0
  214. data/lib/active_record/type/date.rb +7 -0
  215. data/lib/active_record/type/date_time.rb +7 -0
  216. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  217. data/lib/active_record/type/internal/abstract_json.rb +29 -0
  218. data/lib/active_record/type/internal/timezone.rb +15 -0
  219. data/lib/active_record/type/serialized.rb +63 -0
  220. data/lib/active_record/type/time.rb +20 -0
  221. data/lib/active_record/type/type_map.rb +64 -0
  222. data/lib/active_record/type.rb +72 -0
  223. data/lib/active_record/type_caster/connection.rb +29 -0
  224. data/lib/active_record/type_caster/map.rb +19 -0
  225. data/lib/active_record/type_caster.rb +7 -0
  226. data/lib/active_record/validations/absence.rb +23 -0
  227. data/lib/active_record/validations/associated.rb +33 -18
  228. data/lib/active_record/validations/length.rb +24 -0
  229. data/lib/active_record/validations/presence.rb +66 -0
  230. data/lib/active_record/validations/uniqueness.rb +128 -68
  231. data/lib/active_record/validations.rb +48 -40
  232. data/lib/active_record/version.rb +5 -7
  233. data/lib/active_record.rb +71 -47
  234. data/lib/rails/generators/active_record/migration/migration_generator.rb +56 -8
  235. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +24 -0
  236. data/lib/rails/generators/active_record/migration/templates/migration.rb +28 -16
  237. data/lib/rails/generators/active_record/migration.rb +18 -8
  238. data/lib/rails/generators/active_record/model/model_generator.rb +38 -16
  239. data/lib/rails/generators/active_record/model/templates/application_record.rb +5 -0
  240. data/lib/rails/generators/active_record/model/templates/model.rb +7 -6
  241. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  242. data/lib/rails/generators/active_record.rb +3 -11
  243. metadata +188 -134
  244. data/examples/associations.png +0 -0
  245. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
  246. data/lib/active_record/associations/join_helper.rb +0 -55
  247. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  248. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  249. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  250. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -441
  251. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  252. data/lib/active_record/dynamic_finder_match.rb +0 -68
  253. data/lib/active_record/dynamic_scope_match.rb +0 -23
  254. data/lib/active_record/fixtures/file.rb +0 -65
  255. data/lib/active_record/identity_map.rb +0 -162
  256. data/lib/active_record/observer.rb +0 -121
  257. data/lib/active_record/serializers/xml_serializer.rb +0 -203
  258. data/lib/active_record/session_store.rb +0 -360
  259. data/lib/active_record/test_case.rb +0 -73
  260. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -15
  261. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  262. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  263. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  264. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -0,0 +1,49 @@
1
+ module ActiveRecord
2
+ class Relation
3
+ module RecordFetchWarning
4
+ # When this module is prepended to ActiveRecord::Relation and
5
+ # `config.active_record.warn_on_records_fetched_greater_than` is
6
+ # set to an integer, if the number of records a query returns is
7
+ # greater than the value of `warn_on_records_fetched_greater_than`,
8
+ # a warning is logged. This allows for the detection of queries that
9
+ # return a large number of records, which could cause memory bloat.
10
+ #
11
+ # In most cases, fetching large number of records can be performed
12
+ # efficiently using the ActiveRecord::Batches methods.
13
+ # See active_record/lib/relation/batches.rb for more information.
14
+ def exec_queries
15
+ QueryRegistry.reset
16
+
17
+ super.tap do
18
+ if logger && warn_on_records_fetched_greater_than
19
+ if @records.length > warn_on_records_fetched_greater_than
20
+ logger.warn "Query fetched #{@records.size} #{@klass} records: #{QueryRegistry.queries.join(";")}"
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ # :stopdoc:
27
+ ActiveSupport::Notifications.subscribe("sql.active_record") do |*, payload|
28
+ QueryRegistry.queries << payload[:sql]
29
+ end
30
+ # :startdoc:
31
+
32
+ class QueryRegistry # :nodoc:
33
+ extend ActiveSupport::PerThreadRegistry
34
+
35
+ attr_reader :queries
36
+
37
+ def initialize
38
+ @queries = []
39
+ end
40
+
41
+ def reset
42
+ @queries.clear
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ ActiveRecord::Relation.prepend ActiveRecord::Relation::RecordFetchWarning
@@ -1,180 +1,76 @@
1
- require 'active_support/core_ext/object/blank'
1
+ require 'active_support/core_ext/hash/except'
2
+ require 'active_support/core_ext/hash/slice'
3
+ require 'active_record/relation/merger'
2
4
 
3
5
  module ActiveRecord
4
6
  module SpawnMethods
5
- def merge(r)
6
- return self unless r
7
- return to_a & r if r.is_a?(Array)
8
7
 
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
8
+ # This is overridden by Associations::CollectionProxy
9
+ def spawn #:nodoc:
10
+ clone
11
+ end
51
12
 
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?
13
+ # Merges in the conditions from <tt>other</tt>, if <tt>other</tt> is an ActiveRecord::Relation.
14
+ # Returns an array representing the intersection of the resulting records with <tt>other</tt>, if <tt>other</tt> is an array.
15
+ #
16
+ # Post.where(published: true).joins(:comments).merge( Comment.where(spam: false) )
17
+ # # Performs a single join query with both where conditions.
18
+ #
19
+ # recent_posts = Post.order('created_at DESC').first(5)
20
+ # Post.where(published: true).merge(recent_posts)
21
+ # # Returns the intersection of all published posts with the 5 most recently created posts.
22
+ # # (This is just an example. You'd probably want to do this with a single query!)
23
+ #
24
+ # Procs will be evaluated by merge:
25
+ #
26
+ # Post.where(published: true).merge(-> { joins(:comments) })
27
+ # # => Post.where(published: true).joins(:comments)
28
+ #
29
+ # This is mainly intended for sharing common conditions between multiple associations.
30
+ def merge(other)
31
+ if other.is_a?(Array)
32
+ records & other
33
+ elsif other
34
+ spawn.merge!(other)
35
+ else
36
+ raise ArgumentError, "invalid argument: #{other.inspect}."
55
37
  end
38
+ end
56
39
 
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
40
+ def merge!(other) # :nodoc:
41
+ if other.is_a?(Hash)
42
+ Relation::HashMerger.new(self, other).merge
43
+ elsif other.is_a?(Relation)
44
+ Relation::Merger.new(self, other).merge
45
+ elsif other.respond_to?(:to_proc)
46
+ instance_exec(&other)
65
47
  else
66
- # merge in order_values from r
67
- merged_relation.order_values += r.order_values
48
+ raise ArgumentError, "#{other.inspect} is not an ActiveRecord::Relation"
68
49
  end
69
-
70
- # Apply scope extension modules
71
- merged_relation.send :apply_modules, r.extensions
72
-
73
- merged_relation
74
50
  end
75
51
 
76
52
  # Removes from the query the condition(s) specified in +skips+.
77
53
  #
78
- # Example:
79
- #
80
54
  # Post.order('id asc').except(:order) # discards the order condition
81
55
  # Post.where('id > 10').order('id asc').except(:where) # discards the where condition but keeps the order
82
- #
83
56
  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
57
+ relation_with values.except(*skips)
99
58
  end
100
59
 
101
60
  # Removes any condition from the query other than the one(s) specified in +onlies+.
102
61
  #
103
- # Example:
104
- #
105
62
  # Post.order('id asc').only(:where) # discards the order condition
106
63
  # Post.order('id asc').only(:where, :order) # uses the specified order
107
- #
108
64
  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
65
+ relation_with values.slice(*onlies)
146
66
  end
147
67
 
148
68
  private
149
69
 
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)
70
+ def relation_with(values) # :nodoc:
71
+ result = Relation.create(klass, table, predicate_builder, values)
72
+ result.extend(*extending_values) if extending_values.any?
73
+ result
178
74
  end
179
75
  end
180
76
  end
@@ -0,0 +1,174 @@
1
+ module ActiveRecord
2
+ class Relation
3
+ class WhereClause # :nodoc:
4
+ attr_reader :binds
5
+
6
+ delegate :any?, :empty?, to: :predicates
7
+
8
+ def initialize(predicates, binds)
9
+ @predicates = predicates
10
+ @binds = binds
11
+ end
12
+
13
+ def +(other)
14
+ WhereClause.new(
15
+ predicates + other.predicates,
16
+ binds + other.binds,
17
+ )
18
+ end
19
+
20
+ def merge(other)
21
+ WhereClause.new(
22
+ predicates_unreferenced_by(other) + other.predicates,
23
+ non_conflicting_binds(other) + other.binds,
24
+ )
25
+ end
26
+
27
+ def except(*columns)
28
+ WhereClause.new(
29
+ predicates_except(columns),
30
+ binds_except(columns),
31
+ )
32
+ end
33
+
34
+ def or(other)
35
+ if empty?
36
+ self
37
+ elsif other.empty?
38
+ other
39
+ else
40
+ WhereClause.new(
41
+ [ast.or(other.ast)],
42
+ binds + other.binds
43
+ )
44
+ end
45
+ end
46
+
47
+ def to_h(table_name = nil)
48
+ equalities = predicates.grep(Arel::Nodes::Equality)
49
+ if table_name
50
+ equalities = equalities.select do |node|
51
+ node.left.relation.name == table_name
52
+ end
53
+ end
54
+
55
+ binds = self.binds.map { |attr| [attr.name, attr.value] }.to_h
56
+
57
+ equalities.map { |node|
58
+ name = node.left.name
59
+ [name, binds.fetch(name.to_s) {
60
+ case node.right
61
+ when Array then node.right.map(&:val)
62
+ when Arel::Nodes::Casted, Arel::Nodes::Quoted
63
+ node.right.val
64
+ end
65
+ }]
66
+ }.to_h
67
+ end
68
+
69
+ def ast
70
+ Arel::Nodes::And.new(predicates_with_wrapped_sql_literals)
71
+ end
72
+
73
+ def ==(other)
74
+ other.is_a?(WhereClause) &&
75
+ predicates == other.predicates &&
76
+ binds == other.binds
77
+ end
78
+
79
+ def invert
80
+ WhereClause.new(inverted_predicates, binds)
81
+ end
82
+
83
+ def self.empty
84
+ @empty ||= new([], [])
85
+ end
86
+
87
+ protected
88
+
89
+ attr_reader :predicates
90
+
91
+ def referenced_columns
92
+ @referenced_columns ||= begin
93
+ equality_nodes = predicates.select { |n| equality_node?(n) }
94
+ Set.new(equality_nodes, &:left)
95
+ end
96
+ end
97
+
98
+ private
99
+
100
+ def predicates_unreferenced_by(other)
101
+ predicates.reject do |n|
102
+ equality_node?(n) && other.referenced_columns.include?(n.left)
103
+ end
104
+ end
105
+
106
+ def equality_node?(node)
107
+ node.respond_to?(:operator) && node.operator == :==
108
+ end
109
+
110
+ def non_conflicting_binds(other)
111
+ conflicts = referenced_columns & other.referenced_columns
112
+ conflicts.map! { |node| node.name.to_s }
113
+ binds.reject { |attr| conflicts.include?(attr.name) }
114
+ end
115
+
116
+ def inverted_predicates
117
+ predicates.map { |node| invert_predicate(node) }
118
+ end
119
+
120
+ def invert_predicate(node)
121
+ case node
122
+ when NilClass
123
+ raise ArgumentError, 'Invalid argument for .where.not(), got nil.'
124
+ when Arel::Nodes::In
125
+ Arel::Nodes::NotIn.new(node.left, node.right)
126
+ when Arel::Nodes::Equality
127
+ Arel::Nodes::NotEqual.new(node.left, node.right)
128
+ when String
129
+ Arel::Nodes::Not.new(Arel::Nodes::SqlLiteral.new(node))
130
+ else
131
+ Arel::Nodes::Not.new(node)
132
+ end
133
+ end
134
+
135
+ def predicates_except(columns)
136
+ predicates.reject do |node|
137
+ case node
138
+ 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
139
+ subrelation = (node.left.kind_of?(Arel::Attributes::Attribute) ? node.left : node.right)
140
+ columns.include?(subrelation.name.to_s)
141
+ end
142
+ end
143
+ end
144
+
145
+ def binds_except(columns)
146
+ binds.reject do |attr|
147
+ columns.include?(attr.name)
148
+ end
149
+ end
150
+
151
+ def predicates_with_wrapped_sql_literals
152
+ non_empty_predicates.map do |node|
153
+ if Arel::Nodes::Equality === node
154
+ node
155
+ else
156
+ wrap_sql_literal(node)
157
+ end
158
+ end
159
+ end
160
+
161
+ ARRAY_WITH_EMPTY_STRING = ['']
162
+ def non_empty_predicates
163
+ predicates - ARRAY_WITH_EMPTY_STRING
164
+ end
165
+
166
+ def wrap_sql_literal(node)
167
+ if ::String === node
168
+ node = Arel.sql(node)
169
+ end
170
+ Arel::Nodes::Grouping.new(node)
171
+ end
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,38 @@
1
+ module ActiveRecord
2
+ class Relation
3
+ class WhereClauseFactory # :nodoc:
4
+ def initialize(klass, predicate_builder)
5
+ @klass = klass
6
+ @predicate_builder = predicate_builder
7
+ end
8
+
9
+ def build(opts, other)
10
+ binds = []
11
+
12
+ case opts
13
+ when String, Array
14
+ parts = [klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))]
15
+ when Hash
16
+ attributes = predicate_builder.resolve_column_aliases(opts)
17
+ attributes = klass.send(:expand_hash_conditions_for_aggregates, attributes)
18
+ attributes.stringify_keys!
19
+
20
+ attributes, binds = predicate_builder.create_binds(attributes)
21
+
22
+ parts = predicate_builder.build_from_hash(attributes)
23
+ when Arel::Nodes::Node
24
+ parts = [opts]
25
+ binds = other
26
+ else
27
+ raise ArgumentError, "Unsupported argument type: #{opts} (#{opts.class})"
28
+ end
29
+
30
+ WhereClause.new(parts, binds)
31
+ end
32
+
33
+ protected
34
+
35
+ attr_reader :klass, :predicate_builder
36
+ end
37
+ end
38
+ end