activerecord 6.0.0 → 6.1.4
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1178 -600
- data/MIT-LICENSE +1 -1
- data/README.rdoc +4 -4
- data/lib/active_record/aggregations.rb +5 -6
- data/lib/active_record/association_relation.rb +30 -10
- data/lib/active_record/associations/alias_tracker.rb +19 -16
- data/lib/active_record/associations/association.rb +55 -29
- data/lib/active_record/associations/association_scope.rb +19 -15
- data/lib/active_record/associations/belongs_to_association.rb +23 -10
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +8 -3
- data/lib/active_record/associations/builder/association.rb +32 -5
- data/lib/active_record/associations/builder/belongs_to.rb +10 -7
- data/lib/active_record/associations/builder/collection_association.rb +5 -4
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +0 -3
- data/lib/active_record/associations/builder/has_many.rb +6 -2
- data/lib/active_record/associations/builder/has_one.rb +11 -14
- data/lib/active_record/associations/builder/singular_association.rb +1 -1
- data/lib/active_record/associations/collection_association.rb +25 -8
- data/lib/active_record/associations/collection_proxy.rb +14 -7
- data/lib/active_record/associations/foreign_association.rb +13 -0
- data/lib/active_record/associations/has_many_association.rb +24 -3
- data/lib/active_record/associations/has_many_through_association.rb +10 -4
- data/lib/active_record/associations/has_one_association.rb +15 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +39 -16
- data/lib/active_record/associations/join_dependency/join_part.rb +3 -3
- data/lib/active_record/associations/join_dependency.rb +77 -42
- data/lib/active_record/associations/preloader/association.rb +51 -25
- data/lib/active_record/associations/preloader/through_association.rb +2 -2
- data/lib/active_record/associations/preloader.rb +13 -8
- data/lib/active_record/associations/singular_association.rb +1 -1
- data/lib/active_record/associations/through_association.rb +1 -1
- data/lib/active_record/associations.rb +120 -13
- data/lib/active_record/attribute_assignment.rb +10 -9
- data/lib/active_record/attribute_methods/before_type_cast.rb +13 -10
- data/lib/active_record/attribute_methods/dirty.rb +3 -13
- data/lib/active_record/attribute_methods/primary_key.rb +6 -4
- data/lib/active_record/attribute_methods/query.rb +3 -6
- data/lib/active_record/attribute_methods/read.rb +8 -12
- data/lib/active_record/attribute_methods/serialization.rb +11 -6
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -15
- data/lib/active_record/attribute_methods/write.rb +12 -21
- data/lib/active_record/attribute_methods.rb +64 -54
- data/lib/active_record/attributes.rb +33 -9
- data/lib/active_record/autosave_association.rb +63 -44
- data/lib/active_record/base.rb +2 -14
- data/lib/active_record/callbacks.rb +153 -24
- data/lib/active_record/coders/yaml_column.rb +12 -3
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +202 -138
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +2 -44
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +87 -38
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +5 -10
- data/lib/active_record/connection_adapters/abstract/quoting.rb +34 -34
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +152 -116
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +141 -52
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +267 -105
- data/lib/active_record/connection_adapters/abstract/transaction.rb +94 -36
- data/lib/active_record/connection_adapters/abstract_adapter.rb +76 -79
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +149 -115
- data/lib/active_record/connection_adapters/column.rb +15 -1
- data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +35 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +1 -1
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +30 -36
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -2
- data/lib/active_record/connection_adapters/mysql/quoting.rb +18 -3
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +32 -7
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +8 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +5 -2
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +17 -13
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +10 -1
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +31 -13
- data/lib/active_record/connection_adapters/pool_config.rb +73 -0
- data/lib/active_record/connection_adapters/pool_manager.rb +47 -0
- data/lib/active_record/connection_adapters/postgresql/column.rb +24 -1
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +21 -56
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +0 -1
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -5
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +10 -2
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +0 -1
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
- data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +2 -3
- data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +2 -3
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +24 -6
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +11 -2
- data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +4 -4
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +7 -3
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +0 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +72 -54
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +8 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +0 -1
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +83 -65
- data/lib/active_record/connection_adapters/schema_cache.rb +106 -15
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +8 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +38 -12
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +1 -2
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +38 -5
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -57
- data/lib/active_record/connection_adapters/statement_pool.rb +0 -1
- data/lib/active_record/connection_adapters.rb +52 -0
- data/lib/active_record/connection_handling.rb +219 -81
- data/lib/active_record/core.rb +268 -71
- data/lib/active_record/counter_cache.rb +4 -1
- data/lib/active_record/database_configurations/connection_url_resolver.rb +98 -0
- data/lib/active_record/database_configurations/database_config.rb +52 -9
- data/lib/active_record/database_configurations/hash_config.rb +54 -8
- data/lib/active_record/database_configurations/url_config.rb +15 -41
- data/lib/active_record/database_configurations.rb +124 -85
- data/lib/active_record/delegated_type.rb +209 -0
- data/lib/active_record/destroy_association_async_job.rb +36 -0
- data/lib/active_record/dynamic_matchers.rb +2 -3
- data/lib/active_record/enum.rb +80 -38
- data/lib/active_record/errors.rb +47 -12
- data/lib/active_record/explain.rb +9 -5
- data/lib/active_record/explain_subscriber.rb +1 -1
- data/lib/active_record/fixture_set/file.rb +10 -17
- data/lib/active_record/fixture_set/model_metadata.rb +1 -2
- data/lib/active_record/fixture_set/render_context.rb +1 -1
- data/lib/active_record/fixture_set/table_row.rb +2 -3
- data/lib/active_record/fixture_set/table_rows.rb +0 -1
- data/lib/active_record/fixtures.rb +58 -12
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/inheritance.rb +40 -21
- data/lib/active_record/insert_all.rb +43 -10
- data/lib/active_record/integration.rb +3 -5
- data/lib/active_record/internal_metadata.rb +16 -7
- data/lib/active_record/legacy_yaml_adapter.rb +7 -3
- data/lib/active_record/locking/optimistic.rb +33 -18
- data/lib/active_record/locking/pessimistic.rb +6 -2
- data/lib/active_record/log_subscriber.rb +28 -9
- data/lib/active_record/middleware/database_selector/resolver/session.rb +3 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +14 -14
- data/lib/active_record/middleware/database_selector.rb +4 -2
- data/lib/active_record/migration/command_recorder.rb +53 -45
- data/lib/active_record/migration/compatibility.rb +71 -20
- data/lib/active_record/migration/join_table.rb +0 -1
- data/lib/active_record/migration.rb +115 -85
- data/lib/active_record/model_schema.rb +120 -15
- data/lib/active_record/nested_attributes.rb +2 -5
- data/lib/active_record/no_touching.rb +1 -1
- data/lib/active_record/null_relation.rb +0 -1
- data/lib/active_record/persistence.rb +50 -46
- data/lib/active_record/query_cache.rb +15 -5
- data/lib/active_record/querying.rb +12 -7
- data/lib/active_record/railtie.rb +65 -45
- data/lib/active_record/railties/console_sandbox.rb +2 -4
- data/lib/active_record/railties/databases.rake +280 -99
- data/lib/active_record/readonly_attributes.rb +4 -0
- data/lib/active_record/reflection.rb +77 -63
- data/lib/active_record/relation/batches/batch_enumerator.rb +25 -9
- data/lib/active_record/relation/batches.rb +38 -32
- data/lib/active_record/relation/calculations.rb +106 -45
- data/lib/active_record/relation/delegation.rb +9 -7
- data/lib/active_record/relation/finder_methods.rb +55 -17
- data/lib/active_record/relation/from_clause.rb +5 -1
- data/lib/active_record/relation/merger.rb +27 -26
- data/lib/active_record/relation/predicate_builder/array_handler.rb +8 -9
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +4 -5
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -6
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
- data/lib/active_record/relation/predicate_builder.rb +59 -40
- data/lib/active_record/relation/query_methods.rb +344 -181
- data/lib/active_record/relation/record_fetch_warning.rb +3 -3
- data/lib/active_record/relation/spawn_methods.rb +8 -8
- data/lib/active_record/relation/where_clause.rb +111 -62
- data/lib/active_record/relation.rb +116 -82
- data/lib/active_record/result.rb +41 -34
- data/lib/active_record/runtime_registry.rb +2 -2
- data/lib/active_record/sanitization.rb +6 -17
- data/lib/active_record/schema_dumper.rb +34 -4
- data/lib/active_record/schema_migration.rb +2 -8
- data/lib/active_record/scoping/default.rb +1 -4
- data/lib/active_record/scoping/named.rb +7 -18
- data/lib/active_record/scoping.rb +0 -1
- data/lib/active_record/secure_token.rb +16 -8
- data/lib/active_record/serialization.rb +5 -3
- data/lib/active_record/signed_id.rb +116 -0
- data/lib/active_record/statement_cache.rb +20 -4
- data/lib/active_record/store.rb +3 -3
- data/lib/active_record/suppressor.rb +2 -2
- data/lib/active_record/table_metadata.rb +42 -36
- data/lib/active_record/tasks/database_tasks.rb +140 -113
- data/lib/active_record/tasks/mysql_database_tasks.rb +34 -36
- data/lib/active_record/tasks/postgresql_database_tasks.rb +24 -27
- data/lib/active_record/tasks/sqlite_database_tasks.rb +13 -10
- data/lib/active_record/test_databases.rb +5 -4
- data/lib/active_record/test_fixtures.rb +79 -16
- data/lib/active_record/timestamp.rb +4 -7
- data/lib/active_record/touch_later.rb +20 -21
- data/lib/active_record/transactions.rb +26 -73
- data/lib/active_record/type/adapter_specific_registry.rb +2 -5
- data/lib/active_record/type/hash_lookup_type_map.rb +0 -1
- data/lib/active_record/type/serialized.rb +6 -3
- data/lib/active_record/type/time.rb +10 -0
- data/lib/active_record/type/type_map.rb +0 -1
- data/lib/active_record/type/unsigned_integer.rb +0 -1
- data/lib/active_record/type.rb +8 -2
- data/lib/active_record/type_caster/connection.rb +0 -1
- data/lib/active_record/type_caster/map.rb +8 -5
- data/lib/active_record/validations/associated.rb +1 -2
- data/lib/active_record/validations/numericality.rb +35 -0
- data/lib/active_record/validations/uniqueness.rb +24 -4
- data/lib/active_record/validations.rb +3 -3
- data/lib/active_record.rb +7 -13
- data/lib/arel/attributes/attribute.rb +4 -0
- data/lib/arel/collectors/bind.rb +5 -0
- data/lib/arel/collectors/composite.rb +8 -0
- data/lib/arel/collectors/sql_string.rb +7 -0
- data/lib/arel/collectors/substitute_binds.rb +7 -0
- data/lib/arel/nodes/binary.rb +82 -8
- data/lib/arel/nodes/bind_param.rb +8 -0
- data/lib/arel/nodes/casted.rb +21 -9
- data/lib/arel/nodes/equality.rb +6 -9
- data/lib/arel/nodes/grouping.rb +3 -0
- data/lib/arel/nodes/homogeneous_in.rb +76 -0
- data/lib/arel/nodes/in.rb +8 -1
- data/lib/arel/nodes/infix_operation.rb +13 -1
- data/lib/arel/nodes/join_source.rb +1 -1
- data/lib/arel/nodes/node.rb +7 -6
- data/lib/arel/nodes/ordering.rb +27 -0
- data/lib/arel/nodes/sql_literal.rb +3 -0
- data/lib/arel/nodes/table_alias.rb +7 -3
- data/lib/arel/nodes/unary.rb +0 -1
- data/lib/arel/nodes.rb +3 -1
- data/lib/arel/predications.rb +17 -24
- data/lib/arel/select_manager.rb +1 -2
- data/lib/arel/table.rb +13 -5
- data/lib/arel/visitors/dot.rb +14 -3
- data/lib/arel/visitors/mysql.rb +11 -1
- data/lib/arel/visitors/postgresql.rb +15 -5
- data/lib/arel/visitors/sqlite.rb +0 -1
- data/lib/arel/visitors/to_sql.rb +89 -79
- data/lib/arel/visitors/visitor.rb +0 -1
- data/lib/arel/visitors.rb +0 -7
- data/lib/arel.rb +15 -12
- data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
- data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -0
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +2 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -4
- data/lib/rails/generators/active_record/migration.rb +6 -2
- data/lib/rails/generators/active_record/model/model_generator.rb +38 -2
- data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
- metadata +27 -24
- data/lib/active_record/attribute_decorators.rb +0 -90
- data/lib/active_record/connection_adapters/connection_specification.rb +0 -297
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -29
- data/lib/active_record/define_callbacks.rb +0 -22
- data/lib/active_record/railties/collection_cache_association_loading.rb +0 -34
- data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -18
- data/lib/active_record/relation/where_clause_factory.rb +0 -33
- data/lib/arel/attributes.rb +0 -22
- data/lib/arel/visitors/depth_first.rb +0 -204
- data/lib/arel/visitors/ibm_db.rb +0 -34
- data/lib/arel/visitors/informix.rb +0 -62
- data/lib/arel/visitors/mssql.rb +0 -157
- data/lib/arel/visitors/oracle.rb +0 -159
- data/lib/arel/visitors/oracle12.rb +0 -66
- data/lib/arel/visitors/where_sql.rb +0 -23
data/MIT-LICENSE
CHANGED
data/README.rdoc
CHANGED
@@ -132,7 +132,7 @@ This would also define the following accessors: <tt>Product#name</tt> and
|
|
132
132
|
SQLite3[link:classes/ActiveRecord/ConnectionAdapters/SQLite3Adapter.html].
|
133
133
|
|
134
134
|
|
135
|
-
* Logging support for Log4r[https://github.com/colbygk/log4r] and Logger[
|
135
|
+
* Logging support for Log4r[https://github.com/colbygk/log4r] and Logger[https://ruby-doc.org/stdlib/libdoc/logger/rdoc/].
|
136
136
|
|
137
137
|
ActiveRecord::Base.logger = ActiveSupport::Logger.new(STDOUT)
|
138
138
|
ActiveRecord::Base.logger = Log4r::Logger.new('Application Log')
|
@@ -140,7 +140,7 @@ This would also define the following accessors: <tt>Product#name</tt> and
|
|
140
140
|
|
141
141
|
* Database agnostic schema management with Migrations.
|
142
142
|
|
143
|
-
class AddSystemSettings < ActiveRecord::Migration[
|
143
|
+
class AddSystemSettings < ActiveRecord::Migration[6.0]
|
144
144
|
def up
|
145
145
|
create_table :system_settings do |t|
|
146
146
|
t.string :name
|
@@ -194,7 +194,7 @@ The latest version of Active Record can be installed with RubyGems:
|
|
194
194
|
|
195
195
|
Source code can be downloaded as part of the Rails project on GitHub:
|
196
196
|
|
197
|
-
* https://github.com/rails/rails/tree/
|
197
|
+
* https://github.com/rails/rails/tree/main/activerecord
|
198
198
|
|
199
199
|
|
200
200
|
== License
|
@@ -216,4 +216,4 @@ Bug reports for the Ruby on Rails project can be filed here:
|
|
216
216
|
|
217
217
|
Feature requests should be discussed on the rails-core mailing list here:
|
218
218
|
|
219
|
-
* https://
|
219
|
+
* https://discuss.rubyonrails.org/c/rubyonrails-core
|
@@ -14,7 +14,6 @@ module ActiveRecord
|
|
14
14
|
end
|
15
15
|
|
16
16
|
private
|
17
|
-
|
18
17
|
def clear_aggregation_cache
|
19
18
|
@aggregation_cache.clear if persisted?
|
20
19
|
end
|
@@ -142,7 +141,7 @@ module ActiveRecord
|
|
142
141
|
# converted to an instance of value class if necessary.
|
143
142
|
#
|
144
143
|
# For example, the +NetworkResource+ model has +network_address+ and +cidr_range+ attributes that should be
|
145
|
-
# aggregated using the +NetAddr::CIDR+ value class (
|
144
|
+
# aggregated using the +NetAddr::CIDR+ value class (https://www.rubydoc.info/gems/netaddr/1.5.0/NetAddr/CIDR).
|
146
145
|
# The constructor for the value class is called +create+ and it expects a CIDR address string as a parameter.
|
147
146
|
# New values can be assigned to the value object using either another +NetAddr::CIDR+ object, a string
|
148
147
|
# or an array. The <tt>:constructor</tt> and <tt>:converter</tt> options can be used to meet
|
@@ -245,8 +244,8 @@ module ActiveRecord
|
|
245
244
|
private
|
246
245
|
def reader_method(name, class_name, mapping, allow_nil, constructor)
|
247
246
|
define_method(name) do
|
248
|
-
if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? { |key, _| !
|
249
|
-
attrs = mapping.collect { |key, _|
|
247
|
+
if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? { |key, _| !read_attribute(key).nil? })
|
248
|
+
attrs = mapping.collect { |key, _| read_attribute(key) }
|
250
249
|
object = constructor.respond_to?(:call) ?
|
251
250
|
constructor.call(*attrs) :
|
252
251
|
class_name.constantize.send(constructor, *attrs)
|
@@ -272,10 +271,10 @@ module ActiveRecord
|
|
272
271
|
end
|
273
272
|
|
274
273
|
if part.nil? && allow_nil
|
275
|
-
mapping.each { |key, _|
|
274
|
+
mapping.each { |key, _| write_attribute(key, nil) }
|
276
275
|
@aggregation_cache[name] = nil
|
277
276
|
else
|
278
|
-
mapping.each { |key, value|
|
277
|
+
mapping.each { |key, value| write_attribute(key, part.send(value)) }
|
279
278
|
@aggregation_cache[name] = part.freeze
|
280
279
|
end
|
281
280
|
end
|
@@ -1,8 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ActiveRecord
|
4
|
-
class AssociationRelation < Relation
|
5
|
-
def initialize(klass, association)
|
4
|
+
class AssociationRelation < Relation # :nodoc:
|
5
|
+
def initialize(klass, association, **)
|
6
6
|
super(klass)
|
7
7
|
@association = association
|
8
8
|
end
|
@@ -15,20 +15,40 @@ module ActiveRecord
|
|
15
15
|
other == records
|
16
16
|
end
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
18
|
+
%w(insert insert_all insert! insert_all! upsert upsert_all).each do |method|
|
19
|
+
class_eval <<~RUBY
|
20
|
+
def #{method}(attributes, **kwargs)
|
21
|
+
if @association.reflection.through_reflection?
|
22
|
+
raise ArgumentError, "Bulk insert or upsert is currently not supported for has_many through association"
|
23
|
+
end
|
22
24
|
|
23
|
-
|
24
|
-
|
25
|
+
scoping { klass.#{method}(attributes, **kwargs) }
|
26
|
+
end
|
27
|
+
RUBY
|
25
28
|
end
|
26
29
|
|
27
|
-
def
|
28
|
-
|
30
|
+
def build(attributes = nil, &block)
|
31
|
+
if attributes.is_a?(Array)
|
32
|
+
attributes.collect { |attr| build(attr, &block) }
|
33
|
+
else
|
34
|
+
block = current_scope_restoring_block(&block)
|
35
|
+
scoping { _new(attributes, &block) }
|
36
|
+
end
|
29
37
|
end
|
38
|
+
alias new build
|
30
39
|
|
31
40
|
private
|
41
|
+
def _new(attributes, &block)
|
42
|
+
@association.build(attributes, &block)
|
43
|
+
end
|
44
|
+
|
45
|
+
def _create(attributes, &block)
|
46
|
+
@association.create(attributes, &block)
|
47
|
+
end
|
48
|
+
|
49
|
+
def _create!(attributes, &block)
|
50
|
+
@association.create!(attributes, &block)
|
51
|
+
end
|
32
52
|
|
33
53
|
def exec_queries
|
34
54
|
super do |record|
|
@@ -6,9 +6,14 @@ module ActiveRecord
|
|
6
6
|
module Associations
|
7
7
|
# Keeps track of table aliases for ActiveRecord::Associations::JoinDependency
|
8
8
|
class AliasTracker # :nodoc:
|
9
|
-
def self.create(connection, initial_table, joins)
|
9
|
+
def self.create(connection, initial_table, joins, aliases = nil)
|
10
10
|
if joins.empty?
|
11
|
-
aliases
|
11
|
+
aliases ||= Hash.new(0)
|
12
|
+
elsif aliases
|
13
|
+
default_proc = aliases.default_proc || proc { 0 }
|
14
|
+
aliases.default_proc = proc { |h, k|
|
15
|
+
h[k] = initial_count_for(connection, k, joins) + default_proc.call(h, k)
|
16
|
+
}
|
12
17
|
else
|
13
18
|
aliases = Hash.new { |h, k|
|
14
19
|
h[k] = initial_count_for(connection, k, joins)
|
@@ -32,8 +37,6 @@ module ActiveRecord
|
|
32
37
|
).size
|
33
38
|
elsif join.is_a?(Arel::Nodes::Join)
|
34
39
|
join.left.name == name ? 1 : 0
|
35
|
-
elsif join.is_a?(Hash)
|
36
|
-
join[name]
|
37
40
|
else
|
38
41
|
raise ArgumentError, "joins list should be initialized by list of Arel::Nodes::Join"
|
39
42
|
end
|
@@ -48,31 +51,31 @@ module ActiveRecord
|
|
48
51
|
@connection = connection
|
49
52
|
end
|
50
53
|
|
51
|
-
def aliased_table_for(
|
52
|
-
|
54
|
+
def aliased_table_for(arel_table, table_name = nil)
|
55
|
+
table_name ||= arel_table.name
|
56
|
+
|
57
|
+
if aliases[table_name] == 0
|
53
58
|
# If it's zero, we can have our table_name
|
54
59
|
aliases[table_name] = 1
|
55
|
-
|
60
|
+
arel_table = arel_table.alias(table_name) if arel_table.name != table_name
|
56
61
|
else
|
57
62
|
# Otherwise, we need to use an alias
|
58
|
-
aliased_name = @connection.table_alias_for(
|
63
|
+
aliased_name = @connection.table_alias_for(yield)
|
59
64
|
|
60
65
|
# Update the count
|
61
|
-
aliases[aliased_name] += 1
|
66
|
+
count = aliases[aliased_name] += 1
|
62
67
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
aliased_name
|
67
|
-
end
|
68
|
-
Arel::Table.new(table_name, type_caster: type_caster).alias(table_alias)
|
68
|
+
aliased_name = "#{truncate(aliased_name)}_#{count}" if count > 1
|
69
|
+
|
70
|
+
arel_table = arel_table.alias(aliased_name)
|
69
71
|
end
|
72
|
+
|
73
|
+
arel_table
|
70
74
|
end
|
71
75
|
|
72
76
|
attr_reader :aliases
|
73
77
|
|
74
78
|
private
|
75
|
-
|
76
79
|
def truncate(name)
|
77
80
|
name.slice(0, @connection.table_alias_length - 2)
|
78
81
|
end
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "active_support/core_ext/array/wrap"
|
4
|
-
|
5
3
|
module ActiveRecord
|
6
4
|
module Associations
|
7
5
|
# = Active Record Associations
|
@@ -56,6 +54,10 @@ module ActiveRecord
|
|
56
54
|
@inversed = false
|
57
55
|
end
|
58
56
|
|
57
|
+
def reset_negative_cache # :nodoc:
|
58
|
+
reset if loaded? && target.nil?
|
59
|
+
end
|
60
|
+
|
59
61
|
# Reloads the \target and returns +self+ on success.
|
60
62
|
# The QueryCache is cleared if +force+ is true.
|
61
63
|
def reload(force = false)
|
@@ -95,7 +97,11 @@ module ActiveRecord
|
|
95
97
|
end
|
96
98
|
|
97
99
|
def scope
|
98
|
-
|
100
|
+
if (scope = klass.current_scope) && scope.try(:proxy_association) == self
|
101
|
+
scope.spawn
|
102
|
+
else
|
103
|
+
target_scope.merge!(association_scope)
|
104
|
+
end
|
99
105
|
end
|
100
106
|
|
101
107
|
def reset_scope
|
@@ -128,7 +134,15 @@ module ActiveRecord
|
|
128
134
|
self.target = record
|
129
135
|
@inversed = !!record
|
130
136
|
end
|
131
|
-
|
137
|
+
|
138
|
+
def inversed_from_queries(record)
|
139
|
+
if inversable?(record)
|
140
|
+
self.target = record
|
141
|
+
@inversed = true
|
142
|
+
else
|
143
|
+
@inversed = false
|
144
|
+
end
|
145
|
+
end
|
132
146
|
|
133
147
|
# Returns the class of the target. belongs_to polymorphic overrides this to look at the
|
134
148
|
# polymorphic_type field on the owner.
|
@@ -187,27 +201,36 @@ module ActiveRecord
|
|
187
201
|
set_inverse_instance(record)
|
188
202
|
end
|
189
203
|
|
190
|
-
def create(attributes =
|
204
|
+
def create(attributes = nil, &block)
|
191
205
|
_create_record(attributes, &block)
|
192
206
|
end
|
193
207
|
|
194
|
-
def create!(attributes =
|
208
|
+
def create!(attributes = nil, &block)
|
195
209
|
_create_record(attributes, true, &block)
|
196
210
|
end
|
197
211
|
|
198
212
|
private
|
199
213
|
def find_target
|
214
|
+
if strict_loading? && owner.validation_context.nil?
|
215
|
+
Base.strict_loading_violation!(owner: owner.class, reflection: reflection)
|
216
|
+
end
|
217
|
+
|
200
218
|
scope = self.scope
|
201
219
|
return scope.to_a if skip_statement_cache?(scope)
|
202
220
|
|
203
|
-
|
204
|
-
sc = reflection.association_scope_cache(conn, owner) do |params|
|
221
|
+
sc = reflection.association_scope_cache(klass, owner) do |params|
|
205
222
|
as = AssociationScope.create { params.bind }
|
206
223
|
target_scope.merge!(as.scope(self))
|
207
224
|
end
|
208
225
|
|
209
226
|
binds = AssociationScope.get_bind_values(owner, reflection.chain)
|
210
|
-
sc.execute(binds,
|
227
|
+
sc.execute(binds, klass.connection) { |record| set_inverse_instance(record) }
|
228
|
+
end
|
229
|
+
|
230
|
+
def strict_loading?
|
231
|
+
return reflection.strict_loading? if reflection.options.key?(:strict_loading)
|
232
|
+
|
233
|
+
owner.strict_loading?
|
211
234
|
end
|
212
235
|
|
213
236
|
# The scope for this association.
|
@@ -236,25 +259,6 @@ module ActiveRecord
|
|
236
259
|
!loaded? && (!owner.new_record? || foreign_key_present?) && klass
|
237
260
|
end
|
238
261
|
|
239
|
-
def creation_attributes
|
240
|
-
attributes = {}
|
241
|
-
|
242
|
-
if (reflection.has_one? || reflection.collection?) && !options[:through]
|
243
|
-
attributes[reflection.foreign_key] = owner[reflection.active_record_primary_key]
|
244
|
-
|
245
|
-
if reflection.type
|
246
|
-
attributes[reflection.type] = owner.class.polymorphic_name
|
247
|
-
end
|
248
|
-
end
|
249
|
-
|
250
|
-
attributes
|
251
|
-
end
|
252
|
-
|
253
|
-
# Sets the owner attributes on the given record
|
254
|
-
def set_owner_attributes(record)
|
255
|
-
creation_attributes.each { |key, value| record[key] = value }
|
256
|
-
end
|
257
|
-
|
258
262
|
# Returns true if there is a foreign key present on the owner which
|
259
263
|
# references the target. This is used to determine whether we can load
|
260
264
|
# the target if the owner is currently a new record (and therefore
|
@@ -302,7 +306,7 @@ module ActiveRecord
|
|
302
306
|
|
303
307
|
# Returns true if record contains the foreign_key
|
304
308
|
def foreign_key_for?(record)
|
305
|
-
record.
|
309
|
+
record._has_attribute?(reflection.foreign_key)
|
306
310
|
end
|
307
311
|
|
308
312
|
# This should be implemented to return the values of the relevant key(s) on the owner,
|
@@ -327,6 +331,28 @@ module ActiveRecord
|
|
327
331
|
klass.scope_attributes? ||
|
328
332
|
reflection.source_reflection.active_record.default_scopes.any?
|
329
333
|
end
|
334
|
+
|
335
|
+
def enqueue_destroy_association(options)
|
336
|
+
job_class = owner.class.destroy_association_async_job
|
337
|
+
|
338
|
+
if job_class
|
339
|
+
owner._after_commit_jobs.push([job_class, options])
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
def inversable?(record)
|
344
|
+
record &&
|
345
|
+
((!record.persisted? || !owner.persisted?) || matches_foreign_key?(record))
|
346
|
+
end
|
347
|
+
|
348
|
+
def matches_foreign_key?(record)
|
349
|
+
if foreign_key_for?(record)
|
350
|
+
record.read_attribute(reflection.foreign_key) == owner.id ||
|
351
|
+
(foreign_key_for?(owner) && owner.read_attribute(reflection.foreign_key) == record.id)
|
352
|
+
else
|
353
|
+
owner.read_attribute(reflection.foreign_key) == record.id
|
354
|
+
end
|
355
|
+
end
|
330
356
|
end
|
331
357
|
end
|
332
358
|
end
|
@@ -52,17 +52,16 @@ module ActiveRecord
|
|
52
52
|
attr_reader :value_transformation
|
53
53
|
|
54
54
|
def join(table, constraint)
|
55
|
-
|
55
|
+
Arel::Nodes::LeadingJoin.new(table, Arel::Nodes::On.new(constraint))
|
56
56
|
end
|
57
57
|
|
58
58
|
def last_chain_scope(scope, reflection, owner)
|
59
|
-
|
60
|
-
|
61
|
-
foreign_key = join_keys.foreign_key
|
59
|
+
primary_key = reflection.join_primary_key
|
60
|
+
foreign_key = reflection.join_foreign_key
|
62
61
|
|
63
62
|
table = reflection.aliased_table
|
64
63
|
value = transform_value(owner[foreign_key])
|
65
|
-
scope = apply_scope(scope, table,
|
64
|
+
scope = apply_scope(scope, table, primary_key, value)
|
66
65
|
|
67
66
|
if reflection.type
|
68
67
|
polymorphic_type = transform_value(owner.class.polymorphic_name)
|
@@ -77,13 +76,12 @@ module ActiveRecord
|
|
77
76
|
end
|
78
77
|
|
79
78
|
def next_chain_scope(scope, reflection, next_reflection)
|
80
|
-
|
81
|
-
|
82
|
-
foreign_key = join_keys.foreign_key
|
79
|
+
primary_key = reflection.join_primary_key
|
80
|
+
foreign_key = reflection.join_foreign_key
|
83
81
|
|
84
82
|
table = reflection.aliased_table
|
85
83
|
foreign_table = next_reflection.aliased_table
|
86
|
-
constraint = table[
|
84
|
+
constraint = table[primary_key].eq(foreign_table[foreign_key])
|
87
85
|
|
88
86
|
if reflection.type
|
89
87
|
value = transform_value(next_reflection.klass.polymorphic_name)
|
@@ -108,11 +106,9 @@ module ActiveRecord
|
|
108
106
|
name = reflection.name
|
109
107
|
chain = [Reflection::RuntimeReflection.new(reflection, association)]
|
110
108
|
reflection.chain.drop(1).each do |refl|
|
111
|
-
aliased_table = tracker.aliased_table_for(
|
112
|
-
refl.
|
113
|
-
|
114
|
-
refl.klass.type_caster
|
115
|
-
)
|
109
|
+
aliased_table = tracker.aliased_table_for(refl.klass.arel_table) do
|
110
|
+
refl.alias_candidate(name)
|
111
|
+
end
|
116
112
|
chain << ReflectionProxy.new(refl, aliased_table)
|
117
113
|
end
|
118
114
|
chain
|
@@ -134,10 +130,18 @@ module ActiveRecord
|
|
134
130
|
|
135
131
|
if scope_chain_item == chain_head.scope
|
136
132
|
scope.merge! item.except(:where, :includes, :unscope, :order)
|
133
|
+
elsif !item.references_values.empty?
|
134
|
+
scope.merge! item.only(:joins, :left_outer_joins)
|
135
|
+
|
136
|
+
associations = item.eager_load_values | item.includes_values
|
137
|
+
|
138
|
+
unless associations.empty?
|
139
|
+
scope.joins! item.construct_join_dependency(associations, Arel::Nodes::OuterJoin)
|
140
|
+
end
|
137
141
|
end
|
138
142
|
|
139
143
|
reflection.all_includes do
|
140
|
-
scope.
|
144
|
+
scope.includes_values |= item.includes_values
|
141
145
|
end
|
142
146
|
|
143
147
|
scope.unscope!(*item.unscope_values)
|
@@ -9,10 +9,21 @@ module ActiveRecord
|
|
9
9
|
|
10
10
|
case options[:dependent]
|
11
11
|
when :destroy
|
12
|
-
target.destroy
|
13
|
-
|
12
|
+
raise ActiveRecord::Rollback unless target.destroy
|
13
|
+
when :destroy_async
|
14
|
+
id = owner.public_send(reflection.foreign_key.to_sym)
|
15
|
+
primary_key_column = reflection.active_record_primary_key.to_sym
|
16
|
+
|
17
|
+
enqueue_destroy_association(
|
18
|
+
owner_model_name: owner.class.to_s,
|
19
|
+
owner_id: owner.id,
|
20
|
+
association_class: reflection.klass.to_s,
|
21
|
+
association_ids: [id],
|
22
|
+
association_primary_key_column: primary_key_column,
|
23
|
+
ensuring_owner_was_method: options.fetch(:ensuring_owner_was, nil)
|
24
|
+
)
|
14
25
|
else
|
15
|
-
target.
|
26
|
+
target.public_send(options[:dependent])
|
16
27
|
end
|
17
28
|
end
|
18
29
|
|
@@ -44,7 +55,7 @@ module ActiveRecord
|
|
44
55
|
|
45
56
|
def decrement_counters_before_last_save
|
46
57
|
if reflection.polymorphic?
|
47
|
-
model_was = owner.attribute_before_last_save(reflection.foreign_type)
|
58
|
+
model_was = owner.attribute_before_last_save(reflection.foreign_type)&.constantize
|
48
59
|
else
|
49
60
|
model_was = klass
|
50
61
|
end
|
@@ -68,7 +79,7 @@ module ActiveRecord
|
|
68
79
|
@updated = true
|
69
80
|
end
|
70
81
|
|
71
|
-
replace_keys(record)
|
82
|
+
replace_keys(record, force: true)
|
72
83
|
|
73
84
|
self.target = record
|
74
85
|
end
|
@@ -96,8 +107,12 @@ module ActiveRecord
|
|
96
107
|
reflection.counter_cache_column && owner.persisted?
|
97
108
|
end
|
98
109
|
|
99
|
-
def replace_keys(record)
|
100
|
-
|
110
|
+
def replace_keys(record, force: false)
|
111
|
+
target_key = record ? record._read_attribute(primary_key(record.class)) : nil
|
112
|
+
|
113
|
+
if force || owner[reflection.foreign_key] != target_key
|
114
|
+
owner[reflection.foreign_key] = target_key
|
115
|
+
end
|
101
116
|
end
|
102
117
|
|
103
118
|
def primary_key(klass)
|
@@ -108,11 +123,9 @@ module ActiveRecord
|
|
108
123
|
owner._read_attribute(reflection.foreign_key)
|
109
124
|
end
|
110
125
|
|
111
|
-
# NOTE - for now, we're only supporting inverse setting from belongs_to back onto
|
112
|
-
# has_one associations.
|
113
126
|
def invertible_for?(record)
|
114
127
|
inverse = inverse_reflection_for(record)
|
115
|
-
inverse && inverse.has_one?
|
128
|
+
inverse && (inverse.has_one? || ActiveRecord::Base.has_many_inversing)
|
116
129
|
end
|
117
130
|
|
118
131
|
def stale_state
|
@@ -6,7 +6,7 @@ module ActiveRecord
|
|
6
6
|
class BelongsToPolymorphicAssociation < BelongsToAssociation #:nodoc:
|
7
7
|
def klass
|
8
8
|
type = owner[reflection.foreign_type]
|
9
|
-
type.presence && type
|
9
|
+
type.presence && owner.class.polymorphic_class_for(type)
|
10
10
|
end
|
11
11
|
|
12
12
|
def target_changed?
|
@@ -14,9 +14,14 @@ module ActiveRecord
|
|
14
14
|
end
|
15
15
|
|
16
16
|
private
|
17
|
-
def replace_keys(record)
|
17
|
+
def replace_keys(record, force: false)
|
18
18
|
super
|
19
|
-
|
19
|
+
|
20
|
+
target_type = record ? record.class.polymorphic_name : nil
|
21
|
+
|
22
|
+
if force || owner[reflection.foreign_type] != target_type
|
23
|
+
owner[reflection.foreign_type] = target_type
|
24
|
+
end
|
20
25
|
end
|
21
26
|
|
22
27
|
def inverse_reflection_for(record)
|
@@ -18,7 +18,9 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
18
18
|
end
|
19
19
|
self.extensions = []
|
20
20
|
|
21
|
-
VALID_OPTIONS = [
|
21
|
+
VALID_OPTIONS = [
|
22
|
+
:class_name, :anonymous_class, :primary_key, :foreign_key, :dependent, :validate, :inverse_of, :strict_loading
|
23
|
+
].freeze # :nodoc:
|
22
24
|
|
23
25
|
def self.build(model, name, scope, options, &block)
|
24
26
|
if model.dangerous_attribute_method?(name)
|
@@ -72,8 +74,9 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
72
74
|
|
73
75
|
def self.define_callbacks(model, reflection)
|
74
76
|
if dependent = reflection.options[:dependent]
|
75
|
-
check_dependent_options(dependent)
|
77
|
+
check_dependent_options(dependent, model)
|
76
78
|
add_destroy_callbacks(model, reflection)
|
79
|
+
add_after_commit_jobs_callback(model, dependent)
|
77
80
|
end
|
78
81
|
|
79
82
|
Association.extensions.each do |extension|
|
@@ -118,7 +121,11 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
118
121
|
raise NotImplementedError
|
119
122
|
end
|
120
123
|
|
121
|
-
def self.check_dependent_options(dependent)
|
124
|
+
def self.check_dependent_options(dependent, model)
|
125
|
+
if dependent == :destroy_async && !model.destroy_association_async_job
|
126
|
+
err_message = "ActiveJob is required to use destroy_async on associations"
|
127
|
+
raise ActiveRecord::ActiveJobRequiredError, err_message
|
128
|
+
end
|
122
129
|
unless valid_dependent_options.include? dependent
|
123
130
|
raise ArgumentError, "The :dependent option must be one of #{valid_dependent_options}, but is :#{dependent}"
|
124
131
|
end
|
@@ -126,11 +133,31 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
126
133
|
|
127
134
|
def self.add_destroy_callbacks(model, reflection)
|
128
135
|
name = reflection.name
|
129
|
-
model.before_destroy
|
136
|
+
model.before_destroy(->(o) { o.association(name).handle_dependency })
|
137
|
+
end
|
138
|
+
|
139
|
+
def self.add_after_commit_jobs_callback(model, dependent)
|
140
|
+
if dependent == :destroy_async
|
141
|
+
mixin = model.generated_association_methods
|
142
|
+
|
143
|
+
unless mixin.method_defined?(:_after_commit_jobs)
|
144
|
+
model.after_commit(-> do
|
145
|
+
_after_commit_jobs.each do |job_class, job_arguments|
|
146
|
+
job_class.perform_later(**job_arguments)
|
147
|
+
end
|
148
|
+
end)
|
149
|
+
|
150
|
+
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
151
|
+
def _after_commit_jobs
|
152
|
+
@_after_commit_jobs ||= []
|
153
|
+
end
|
154
|
+
CODE
|
155
|
+
end
|
156
|
+
end
|
130
157
|
end
|
131
158
|
|
132
159
|
private_class_method :build_scope, :macro, :valid_options, :validate_options, :define_extensions,
|
133
160
|
:define_callbacks, :define_accessors, :define_readers, :define_writers, :define_validations,
|
134
|
-
:valid_dependent_options, :check_dependent_options, :add_destroy_callbacks
|
161
|
+
:valid_dependent_options, :check_dependent_options, :add_destroy_callbacks, :add_after_commit_jobs_callback
|
135
162
|
end
|
136
163
|
end
|
@@ -7,11 +7,14 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def self.valid_options(options)
|
10
|
-
super + [:polymorphic, :
|
10
|
+
valid = super + [:polymorphic, :counter_cache, :optional, :default]
|
11
|
+
valid += [:foreign_type] if options[:polymorphic]
|
12
|
+
valid += [:ensuring_owner_was] if options[:dependent] == :destroy_async
|
13
|
+
valid
|
11
14
|
end
|
12
15
|
|
13
16
|
def self.valid_dependent_options
|
14
|
-
[:destroy, :delete]
|
17
|
+
[:destroy, :delete, :destroy_async]
|
15
18
|
end
|
16
19
|
|
17
20
|
def self.define_callbacks(model, reflection)
|
@@ -55,19 +58,19 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
55
58
|
|
56
59
|
if old_record
|
57
60
|
if touch != true
|
58
|
-
old_record.
|
61
|
+
old_record.public_send(touch_method, touch)
|
59
62
|
else
|
60
|
-
old_record.
|
63
|
+
old_record.public_send(touch_method)
|
61
64
|
end
|
62
65
|
end
|
63
66
|
end
|
64
67
|
|
65
|
-
record = o.
|
68
|
+
record = o.public_send name
|
66
69
|
if record && record.persisted?
|
67
70
|
if touch != true
|
68
|
-
record.
|
71
|
+
record.public_send(touch_method, touch)
|
69
72
|
else
|
70
|
-
record.
|
73
|
+
record.public_send(touch_method)
|
71
74
|
end
|
72
75
|
end
|
73
76
|
end
|
@@ -7,8 +7,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
7
7
|
CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove]
|
8
8
|
|
9
9
|
def self.valid_options(options)
|
10
|
-
super + [:
|
11
|
-
:after_add, :before_remove, :after_remove, :extend]
|
10
|
+
super + [:before_add, :after_add, :before_remove, :after_remove, :extend]
|
12
11
|
end
|
13
12
|
|
14
13
|
def self.define_callbacks(model, reflection)
|
@@ -31,8 +30,10 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
31
30
|
def self.define_callback(model, callback_name, name, options)
|
32
31
|
full_callback_name = "#{callback_name}_for_#{name}"
|
33
32
|
|
34
|
-
|
35
|
-
|
33
|
+
unless model.method_defined?(full_callback_name)
|
34
|
+
model.class_attribute(full_callback_name, instance_accessor: false, instance_predicate: false)
|
35
|
+
end
|
36
|
+
|
36
37
|
callbacks = Array(options[callback_name.to_sym]).map do |callback|
|
37
38
|
case callback
|
38
39
|
when Symbol
|