activerecord 8.0.3 → 8.1.0.beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +427 -522
- data/README.rdoc +1 -1
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/association.rb +1 -1
- data/lib/active_record/associations/builder/association.rb +16 -5
- data/lib/active_record/associations/builder/belongs_to.rb +17 -4
- data/lib/active_record/associations/builder/collection_association.rb +7 -3
- data/lib/active_record/associations/builder/has_one.rb +1 -1
- data/lib/active_record/associations/builder/singular_association.rb +33 -5
- data/lib/active_record/associations/collection_proxy.rb +22 -4
- data/lib/active_record/associations/deprecation.rb +88 -0
- data/lib/active_record/associations/errors.rb +3 -0
- data/lib/active_record/associations/join_dependency.rb +2 -0
- data/lib/active_record/associations/preloader/branch.rb +1 -0
- data/lib/active_record/associations.rb +159 -21
- data/lib/active_record/attribute_methods/serialization.rb +16 -3
- data/lib/active_record/attribute_methods.rb +1 -1
- data/lib/active_record/attributes.rb +3 -0
- data/lib/active_record/autosave_association.rb +1 -1
- data/lib/active_record/base.rb +2 -3
- data/lib/active_record/coders/json.rb +14 -5
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +1 -3
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +15 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -12
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +382 -51
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +26 -30
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +25 -18
- data/lib/active_record/connection_adapters/abstract/quoting.rb +15 -24
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +7 -2
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +26 -34
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +85 -22
- data/lib/active_record/connection_adapters/abstract/transaction.rb +16 -3
- data/lib/active_record/connection_adapters/abstract_adapter.rb +66 -14
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +43 -11
- data/lib/active_record/connection_adapters/column.rb +17 -4
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +42 -5
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +26 -4
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +27 -22
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +18 -23
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +8 -21
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +65 -30
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +74 -38
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +10 -5
- data/lib/active_record/connection_adapters/schema_cache.rb +2 -2
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +37 -25
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +4 -13
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +54 -30
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +4 -3
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -1
- data/lib/active_record/connection_adapters.rb +1 -0
- data/lib/active_record/core.rb +5 -4
- data/lib/active_record/counter_cache.rb +33 -8
- data/lib/active_record/database_configurations/database_config.rb +5 -1
- data/lib/active_record/database_configurations/hash_config.rb +50 -9
- data/lib/active_record/database_configurations/url_config.rb +13 -3
- data/lib/active_record/database_configurations.rb +7 -3
- data/lib/active_record/delegated_type.rb +1 -1
- data/lib/active_record/dynamic_matchers.rb +54 -69
- data/lib/active_record/encryption/encryptable_record.rb +4 -4
- data/lib/active_record/encryption/encrypted_attribute_type.rb +1 -1
- data/lib/active_record/encryption/scheme.rb +1 -1
- data/lib/active_record/enum.rb +24 -8
- data/lib/active_record/errors.rb +23 -7
- data/lib/active_record/explain_registry.rb +0 -1
- data/lib/active_record/filter_attribute_handler.rb +73 -0
- data/lib/active_record/fixtures.rb +2 -2
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +1 -1
- data/lib/active_record/insert_all.rb +12 -7
- data/lib/active_record/locking/optimistic.rb +7 -0
- data/lib/active_record/locking/pessimistic.rb +5 -0
- data/lib/active_record/log_subscriber.rb +1 -5
- data/lib/active_record/middleware/shard_selector.rb +34 -17
- data/lib/active_record/migration/command_recorder.rb +14 -1
- data/lib/active_record/migration/compatibility.rb +34 -24
- data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
- data/lib/active_record/migration.rb +26 -16
- data/lib/active_record/model_schema.rb +10 -7
- data/lib/active_record/nested_attributes.rb +2 -0
- data/lib/active_record/persistence.rb +34 -3
- data/lib/active_record/query_cache.rb +22 -15
- data/lib/active_record/query_logs.rb +3 -7
- data/lib/active_record/railtie.rb +32 -3
- data/lib/active_record/railties/databases.rake +16 -4
- data/lib/active_record/railties/job_checkpoints.rb +15 -0
- data/lib/active_record/railties/job_runtime.rb +10 -11
- data/lib/active_record/reflection.rb +42 -3
- data/lib/active_record/relation/batches.rb +26 -12
- data/lib/active_record/relation/calculations.rb +20 -9
- data/lib/active_record/relation/delegation.rb +0 -1
- data/lib/active_record/relation/finder_methods.rb +27 -11
- data/lib/active_record/relation/merger.rb +2 -2
- data/lib/active_record/relation/predicate_builder.rb +2 -2
- data/lib/active_record/relation/query_attribute.rb +3 -1
- data/lib/active_record/relation/query_methods.rb +39 -29
- data/lib/active_record/relation/where_clause.rb +1 -10
- data/lib/active_record/relation.rb +25 -13
- data/lib/active_record/result.rb +44 -21
- data/lib/active_record/sanitization.rb +2 -0
- data/lib/active_record/schema_dumper.rb +12 -10
- data/lib/active_record/scoping.rb +0 -1
- data/lib/active_record/signed_id.rb +43 -15
- data/lib/active_record/statement_cache.rb +13 -9
- data/lib/active_record/store.rb +44 -19
- data/lib/active_record/tasks/abstract_tasks.rb +76 -0
- data/lib/active_record/tasks/database_tasks.rb +2 -21
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -40
- data/lib/active_record/tasks/postgresql_database_tasks.rb +5 -39
- data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -26
- data/lib/active_record/test_databases.rb +10 -2
- data/lib/active_record/test_fixtures.rb +27 -2
- data/lib/active_record/testing/query_assertions.rb +8 -2
- data/lib/active_record/timestamp.rb +4 -2
- data/lib/active_record/transaction.rb +2 -5
- data/lib/active_record/transactions.rb +32 -10
- data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
- data/lib/active_record/type/internal/timezone.rb +7 -0
- data/lib/active_record/type/json.rb +15 -2
- data/lib/active_record/type/serialized.rb +11 -4
- data/lib/active_record/type/type_map.rb +1 -1
- data/lib/active_record/type_caster/connection.rb +2 -1
- data/lib/active_record/validations/associated.rb +1 -1
- data/lib/active_record.rb +65 -3
- data/lib/arel/alias_predication.rb +2 -0
- data/lib/arel/crud.rb +6 -11
- data/lib/arel/nodes/count.rb +2 -2
- data/lib/arel/nodes/function.rb +4 -10
- data/lib/arel/nodes/named_function.rb +2 -2
- data/lib/arel/nodes/node.rb +1 -1
- data/lib/arel/nodes.rb +0 -2
- data/lib/arel/select_manager.rb +7 -2
- data/lib/arel/visitors/dot.rb +0 -3
- data/lib/arel/visitors/postgresql.rb +55 -0
- data/lib/arel/visitors/sqlite.rb +55 -8
- data/lib/arel/visitors/to_sql.rb +3 -21
- data/lib/arel.rb +3 -1
- metadata +13 -9
- data/lib/active_record/normalization.rb +0 -163
@@ -181,7 +181,7 @@ module ActiveRecord
|
|
181
181
|
end
|
182
182
|
|
183
183
|
case exception
|
184
|
-
when ::Trilogy::ConnectionClosed, ::Trilogy::EOFError
|
184
|
+
when ::Trilogy::ConnectionClosed, ::Trilogy::EOFError, ::Trilogy::SSLError
|
185
185
|
return ConnectionFailed.new(message, connection_pool: @pool)
|
186
186
|
when ::Trilogy::Error
|
187
187
|
if exception.is_a?(SystemCallError) || exception.message.include?("TRILOGY_INVALID_SEQUENCE_ID")
|
@@ -84,6 +84,7 @@ module ActiveRecord
|
|
84
84
|
autoload_at "active_record/connection_adapters/abstract/schema_definitions" do
|
85
85
|
autoload :IndexDefinition
|
86
86
|
autoload :ColumnDefinition
|
87
|
+
autoload :ColumnMethods
|
87
88
|
autoload :ChangeColumnDefinition
|
88
89
|
autoload :ChangeColumnDefaultDefinition
|
89
90
|
autoload :ForeignKeyDefinition
|
data/lib/active_record/core.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "active_support/core_ext/enumerable"
|
4
|
-
require "active_support/core_ext/module/delegation"
|
5
4
|
require "active_support/parameter_filter"
|
6
5
|
require "concurrent/map"
|
7
6
|
|
@@ -112,7 +111,7 @@ module ActiveRecord
|
|
112
111
|
# Post.attributes_for_inspect = [:id, :title]
|
113
112
|
# Post.first.inspect #=> "#<Post id: 1, title: "Hello, World!">"
|
114
113
|
#
|
115
|
-
# When set to
|
114
|
+
# When set to +:all+ inspect will list all the record's attributes:
|
116
115
|
#
|
117
116
|
# Post.attributes_for_inspect = :all
|
118
117
|
# Post.first.inspect #=> "#<Post id: 1, title: "Hello, World!", published_at: "2023-10-23 14:28:11 +0000">"
|
@@ -358,6 +357,8 @@ module ActiveRecord
|
|
358
357
|
def filter_attributes=(filter_attributes)
|
359
358
|
@inspection_filter = nil
|
360
359
|
@filter_attributes = filter_attributes
|
360
|
+
|
361
|
+
FilterAttributeHandler.sensitive_attribute_was_declared(self, filter_attributes)
|
361
362
|
end
|
362
363
|
|
363
364
|
def inspection_filter # :nodoc:
|
@@ -451,7 +452,7 @@ module ActiveRecord
|
|
451
452
|
where(wheres).limit(1)
|
452
453
|
}
|
453
454
|
|
454
|
-
statement.execute(values.flatten, connection
|
455
|
+
statement.execute(values.flatten, connection).then do |r|
|
455
456
|
r.first
|
456
457
|
rescue TypeError
|
457
458
|
raise ActiveRecord::StatementInvalid
|
@@ -641,7 +642,7 @@ module ActiveRecord
|
|
641
642
|
def hash
|
642
643
|
id = self.id
|
643
644
|
|
644
|
-
if primary_key_values_present?
|
645
|
+
if self.class.composite_primary_key? ? primary_key_values_present? : id
|
645
646
|
self.class.hash ^ id.hash
|
646
647
|
else
|
647
648
|
super
|
@@ -17,7 +17,7 @@ module ActiveRecord
|
|
17
17
|
#
|
18
18
|
# ==== Parameters
|
19
19
|
#
|
20
|
-
# * +id+ - The id of the object you wish to reset a counter on.
|
20
|
+
# * +id+ - The id of the object you wish to reset a counter on or an array of ids.
|
21
21
|
# * +counters+ - One or more association counters to reset. Association name or counter name can be given.
|
22
22
|
# * <tt>:touch</tt> - Touch timestamp columns when updating.
|
23
23
|
# Pass +true+ to touch +updated_at+ and/or +updated_on+. Pass a symbol to
|
@@ -28,13 +28,25 @@ module ActiveRecord
|
|
28
28
|
# # For the Post with id #1, reset the comments_count
|
29
29
|
# Post.reset_counters(1, :comments)
|
30
30
|
#
|
31
|
+
# # For posts with ids #1 and #2, reset the comments_count
|
32
|
+
# Post.reset_counters([1, 2], :comments)
|
33
|
+
#
|
31
34
|
# # Like above, but also touch the updated_at and/or updated_on
|
32
35
|
# # attributes.
|
33
36
|
# Post.reset_counters(1, :comments, touch: true)
|
34
37
|
def reset_counters(id, *counters, touch: nil)
|
35
|
-
|
38
|
+
ids = if composite_primary_key?
|
39
|
+
if id.first.is_a?(Array)
|
40
|
+
id
|
41
|
+
else
|
42
|
+
[id]
|
43
|
+
end
|
44
|
+
else
|
45
|
+
Array(id)
|
46
|
+
end
|
47
|
+
|
48
|
+
updates = Hash.new { |h, k| h[k] = {} }
|
36
49
|
|
37
|
-
updates = {}
|
38
50
|
counters.each do |counter_association|
|
39
51
|
has_many_association = _reflect_on_association(counter_association)
|
40
52
|
unless has_many_association
|
@@ -48,14 +60,22 @@ module ActiveRecord
|
|
48
60
|
has_many_association = has_many_association.through_reflection
|
49
61
|
end
|
50
62
|
|
63
|
+
counter_association = counter_association.to_sym
|
51
64
|
foreign_key = has_many_association.foreign_key.to_s
|
52
65
|
child_class = has_many_association.klass
|
53
66
|
reflection = child_class._reflections.values.find { |e| e.belongs_to? && e.foreign_key.to_s == foreign_key && e.options[:counter_cache].present? }
|
54
67
|
counter_name = reflection.counter_cache_column
|
55
68
|
|
56
|
-
|
57
|
-
|
58
|
-
|
69
|
+
counts =
|
70
|
+
unscoped
|
71
|
+
.joins(counter_association)
|
72
|
+
.where(primary_key => ids)
|
73
|
+
.group(primary_key)
|
74
|
+
.count(:all)
|
75
|
+
|
76
|
+
ids.each do |id|
|
77
|
+
updates[id].merge!(counter_name => counts[id] || 0)
|
78
|
+
end
|
59
79
|
end
|
60
80
|
|
61
81
|
if touch
|
@@ -63,10 +83,15 @@ module ActiveRecord
|
|
63
83
|
names = Array.wrap(names)
|
64
84
|
options = names.extract_options!
|
65
85
|
touch_updates = touch_attributes_with_time(*names, **options)
|
66
|
-
|
86
|
+
|
87
|
+
updates.each_value do |record_updates|
|
88
|
+
record_updates.merge!(touch_updates)
|
89
|
+
end
|
67
90
|
end
|
68
91
|
|
69
|
-
|
92
|
+
updates.each do |id, record_updates|
|
93
|
+
unscoped.where(primary_key => [id]).update_all(record_updates)
|
94
|
+
end
|
70
95
|
|
71
96
|
true
|
72
97
|
end
|
@@ -38,6 +38,7 @@ module ActiveRecord
|
|
38
38
|
def initialize(env_name, name, configuration_hash)
|
39
39
|
super(env_name, name)
|
40
40
|
@configuration_hash = configuration_hash.symbolize_keys.freeze
|
41
|
+
validate_configuration!
|
41
42
|
end
|
42
43
|
|
43
44
|
# Determines whether a database configuration is for a replica / readonly
|
@@ -69,16 +70,32 @@ module ActiveRecord
|
|
69
70
|
@configuration_hash = configuration_hash.merge(database: database).freeze
|
70
71
|
end
|
71
72
|
|
72
|
-
def
|
73
|
-
(configuration_hash[:pool] || 5).to_i
|
73
|
+
def max_connections
|
74
|
+
(configuration_hash[:max_connections] || configuration_hash[:pool] || 5).to_i
|
74
75
|
end
|
75
76
|
|
77
|
+
def min_connections
|
78
|
+
(configuration_hash[:min_connections] || 0).to_i
|
79
|
+
end
|
80
|
+
|
81
|
+
alias :pool :max_connections
|
82
|
+
deprecate pool: :max_connections, deprecator: ActiveRecord.deprecator
|
83
|
+
|
76
84
|
def min_threads
|
77
85
|
(configuration_hash[:min_threads] || 0).to_i
|
78
86
|
end
|
79
87
|
|
80
88
|
def max_threads
|
81
|
-
(configuration_hash[:max_threads] ||
|
89
|
+
(configuration_hash[:max_threads] || max_connections).to_i
|
90
|
+
end
|
91
|
+
|
92
|
+
def max_age
|
93
|
+
v = configuration_hash[:max_age]&.to_i
|
94
|
+
if v && v > 0
|
95
|
+
v
|
96
|
+
else
|
97
|
+
Float::INFINITY
|
98
|
+
end
|
82
99
|
end
|
83
100
|
|
84
101
|
def query_cache
|
@@ -93,10 +110,8 @@ module ActiveRecord
|
|
93
110
|
(configuration_hash[:checkout_timeout] || 5).to_f
|
94
111
|
end
|
95
112
|
|
96
|
-
|
97
|
-
|
98
|
-
def reaping_frequency
|
99
|
-
configuration_hash.fetch(:reaping_frequency, 60)&.to_f
|
113
|
+
def reaping_frequency # :nodoc:
|
114
|
+
configuration_hash.fetch(:reaping_frequency, default_reaping_frequency)&.to_f
|
100
115
|
end
|
101
116
|
|
102
117
|
def idle_timeout
|
@@ -104,6 +119,11 @@ module ActiveRecord
|
|
104
119
|
timeout if timeout > 0
|
105
120
|
end
|
106
121
|
|
122
|
+
def keepalive
|
123
|
+
keepalive = (configuration_hash[:keepalive] || 600).to_f
|
124
|
+
keepalive if keepalive > 0
|
125
|
+
end
|
126
|
+
|
107
127
|
def adapter
|
108
128
|
configuration_hash[:adapter]&.to_s
|
109
129
|
end
|
@@ -159,8 +179,8 @@ module ActiveRecord
|
|
159
179
|
end
|
160
180
|
|
161
181
|
def schema_format # :nodoc:
|
162
|
-
format = configuration_hash
|
163
|
-
raise "Invalid schema format" unless [
|
182
|
+
format = configuration_hash.fetch(:schema_format, ActiveRecord.schema_format).to_sym
|
183
|
+
raise "Invalid schema format" unless [:ruby, :sql].include?(format)
|
164
184
|
format
|
165
185
|
end
|
166
186
|
|
@@ -181,6 +201,27 @@ module ActiveRecord
|
|
181
201
|
"structure.sql"
|
182
202
|
end
|
183
203
|
end
|
204
|
+
|
205
|
+
def default_reaping_frequency
|
206
|
+
# Reap every 20 seconds by default, but run more often as necessary to
|
207
|
+
# meet other configured timeouts.
|
208
|
+
[20, idle_timeout, max_age, keepalive].compact.min
|
209
|
+
end
|
210
|
+
|
211
|
+
def validate_configuration!
|
212
|
+
if configuration_hash[:pool] && configuration_hash[:max_connections]
|
213
|
+
pool_val = configuration_hash[:pool].to_i
|
214
|
+
max_conn_val = configuration_hash[:max_connections].to_i
|
215
|
+
|
216
|
+
if pool_val != max_conn_val
|
217
|
+
raise "Ambiguous configuration: 'pool' (#{pool_val}) and 'max_connections' (#{max_conn_val}) are set to different values. Prefer just 'max_connections'."
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
if configuration_hash[:pool] && configuration_hash[:min_connections]
|
222
|
+
raise "Ambiguous configuration: when setting 'min_connections', use 'max_connections' instead of 'pool'."
|
223
|
+
end
|
224
|
+
end
|
184
225
|
end
|
185
226
|
end
|
186
227
|
end
|
@@ -47,9 +47,8 @@ module ActiveRecord
|
|
47
47
|
@configuration_hash[:schema_dump] = false
|
48
48
|
end
|
49
49
|
|
50
|
-
|
51
|
-
|
52
|
-
end
|
50
|
+
query_cache = parse_query_cache
|
51
|
+
@configuration_hash[:query_cache] = query_cache unless query_cache.nil?
|
53
52
|
|
54
53
|
to_boolean!(@configuration_hash, :replica)
|
55
54
|
to_boolean!(@configuration_hash, :database_tasks)
|
@@ -58,6 +57,17 @@ module ActiveRecord
|
|
58
57
|
end
|
59
58
|
|
60
59
|
private
|
60
|
+
def parse_query_cache
|
61
|
+
case value = @configuration_hash[:query_cache]
|
62
|
+
when /\A\d+\z/
|
63
|
+
value.to_i
|
64
|
+
when "false"
|
65
|
+
false
|
66
|
+
else
|
67
|
+
value
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
61
71
|
def to_boolean!(configuration_hash, key)
|
62
72
|
if configuration_hash[key].is_a?(String)
|
63
73
|
configuration_hash[key] = configuration_hash[key] != "false"
|
@@ -36,9 +36,11 @@ module ActiveRecord
|
|
36
36
|
# to respond to `sharded?`. To implement this define the following in an
|
37
37
|
# initializer:
|
38
38
|
#
|
39
|
-
#
|
40
|
-
#
|
41
|
-
#
|
39
|
+
# ActiveSupport.on_load(:active_record_database_configurations) do
|
40
|
+
# ActiveRecord::DatabaseConfigurations.register_db_config_handler do |env_name, name, url, config|
|
41
|
+
# next unless config.key?(:vitess)
|
42
|
+
# VitessConfig.new(env_name, name, config)
|
43
|
+
# end
|
42
44
|
# end
|
43
45
|
#
|
44
46
|
# Note: applications must handle the condition in which custom config should be
|
@@ -306,4 +308,6 @@ module ActiveRecord
|
|
306
308
|
url
|
307
309
|
end
|
308
310
|
end
|
311
|
+
|
312
|
+
ActiveSupport.run_load_hooks(:active_record_database_configurations, DatabaseConfigurations)
|
309
313
|
end
|
@@ -229,7 +229,7 @@ module ActiveRecord
|
|
229
229
|
# @entry.message_uuid # => returns entryable_uuid, when entryable_type == "Message", otherwise nil
|
230
230
|
# @entry.comment_uuid # => returns entryable_uuid, when entryable_type == "Comment", otherwise nil
|
231
231
|
def delegated_type(role, types:, **options)
|
232
|
-
belongs_to role, options.delete(:scope), **options
|
232
|
+
belongs_to role, options.delete(:scope), **options, polymorphic: true
|
233
233
|
define_delegated_type_methods role, types: types, options: options
|
234
234
|
end
|
235
235
|
|
@@ -7,16 +7,18 @@ module ActiveRecord
|
|
7
7
|
if self == Base
|
8
8
|
super
|
9
9
|
else
|
10
|
-
|
11
|
-
|
10
|
+
super || begin
|
11
|
+
match = Method.match(name)
|
12
|
+
match && match.valid?(self, name)
|
13
|
+
end
|
12
14
|
end
|
13
15
|
end
|
14
16
|
|
15
17
|
def method_missing(name, ...)
|
16
|
-
match = Method.match(
|
18
|
+
match = Method.match(name)
|
17
19
|
|
18
|
-
if match && match.valid?
|
19
|
-
match.define
|
20
|
+
if match && match.valid?(self, name)
|
21
|
+
match.define(self, name)
|
20
22
|
send(name, ...)
|
21
23
|
else
|
22
24
|
super
|
@@ -24,97 +26,80 @@ module ActiveRecord
|
|
24
26
|
end
|
25
27
|
|
26
28
|
class Method
|
27
|
-
@matchers = []
|
28
|
-
|
29
29
|
class << self
|
30
|
-
|
31
|
-
|
32
|
-
def match(model, name)
|
33
|
-
klass = matchers.find { |k| k.pattern.match?(name) }
|
34
|
-
klass.new(model, name) if klass
|
30
|
+
def match(name)
|
31
|
+
FindBy.match?(name) || FindByBang.match?(name)
|
35
32
|
end
|
36
33
|
|
37
|
-
def
|
38
|
-
|
34
|
+
def valid?(model, name)
|
35
|
+
attribute_names(model, name.to_s).all? { |name| model.columns_hash[name] || model.reflect_on_aggregation(name.to_sym) }
|
39
36
|
end
|
40
37
|
|
41
|
-
def
|
42
|
-
|
38
|
+
def define(model, name)
|
39
|
+
model.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
40
|
+
def self.#{name}(#{signature(model, name)})
|
41
|
+
#{body(model, name)}
|
42
|
+
end
|
43
|
+
CODE
|
43
44
|
end
|
44
45
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
46
|
+
private
|
47
|
+
def make_pattern(prefix, suffix)
|
48
|
+
/\A#{prefix}_([_a-zA-Z]\w*)#{suffix}\Z/
|
49
|
+
end
|
49
50
|
|
50
|
-
|
51
|
+
def attribute_names(model, name)
|
52
|
+
attribute_names = name.match(pattern)[1].split("_and_")
|
53
|
+
attribute_names.map! { |name| model.attribute_aliases[name] || name }
|
54
|
+
end
|
51
55
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
@attribute_names = @name.match(self.class.pattern)[1].split("_and_")
|
56
|
-
@attribute_names.map! { |name| @model.attribute_aliases[name] || name }
|
57
|
-
end
|
56
|
+
def body(model, method_name)
|
57
|
+
"#{finder}(#{attributes_hash(model, method_name)})"
|
58
|
+
end
|
58
59
|
|
59
|
-
|
60
|
-
|
61
|
-
|
60
|
+
# The parameters in the signature may have reserved Ruby words, in order
|
61
|
+
# to prevent errors, we start each param name with `_`.
|
62
|
+
def signature(model, method_name)
|
63
|
+
attribute_names(model, method_name.to_s).map { |name| "_#{name}" }.join(", ")
|
64
|
+
end
|
62
65
|
|
63
|
-
|
64
|
-
|
65
|
-
def
|
66
|
-
#{
|
66
|
+
# Given that the parameters starts with `_`, the finder needs to use the
|
67
|
+
# same parameter name.
|
68
|
+
def attributes_hash(model, method_name)
|
69
|
+
"{" + attribute_names(model, method_name).map { |name| ":#{name} => _#{name}" }.join(",") + "}"
|
67
70
|
end
|
68
|
-
CODE
|
69
71
|
end
|
72
|
+
end
|
70
73
|
|
71
|
-
|
72
|
-
|
73
|
-
"#{finder}(#{attributes_hash})"
|
74
|
-
end
|
74
|
+
class FindBy < Method
|
75
|
+
@pattern = make_pattern("find_by", "")
|
75
76
|
|
76
|
-
|
77
|
-
|
78
|
-
def signature
|
79
|
-
attribute_names.map { |name| "_#{name}" }.join(", ")
|
80
|
-
end
|
77
|
+
class << self
|
78
|
+
attr_reader :pattern
|
81
79
|
|
82
|
-
|
83
|
-
|
84
|
-
def attributes_hash
|
85
|
-
"{" + attribute_names.map { |name| ":#{name} => _#{name}" }.join(",") + "}"
|
80
|
+
def match?(name)
|
81
|
+
pattern.match?(name) && self
|
86
82
|
end
|
87
83
|
|
88
84
|
def finder
|
89
|
-
|
85
|
+
"find_by"
|
90
86
|
end
|
91
|
-
end
|
92
|
-
|
93
|
-
class FindBy < Method
|
94
|
-
Method.matchers << self
|
95
|
-
|
96
|
-
def self.prefix
|
97
|
-
"find_by"
|
98
|
-
end
|
99
|
-
|
100
|
-
def finder
|
101
|
-
"find_by"
|
102
87
|
end
|
103
88
|
end
|
104
89
|
|
105
90
|
class FindByBang < Method
|
106
|
-
|
91
|
+
@pattern = make_pattern("find_by", "!")
|
107
92
|
|
108
|
-
|
109
|
-
|
110
|
-
end
|
93
|
+
class << self
|
94
|
+
attr_reader :pattern
|
111
95
|
|
112
|
-
|
113
|
-
|
114
|
-
|
96
|
+
def match?(name)
|
97
|
+
pattern.match?(name) && self
|
98
|
+
end
|
115
99
|
|
116
|
-
|
117
|
-
|
100
|
+
def finder
|
101
|
+
"find_by!"
|
102
|
+
end
|
118
103
|
end
|
119
104
|
end
|
120
105
|
end
|
@@ -30,10 +30,10 @@ module ActiveRecord
|
|
30
30
|
# will use the oldest encryption scheme to encrypt new data by default. You can change this by setting
|
31
31
|
# <tt>deterministic: { fixed: false }</tt>. That will make it use the newest encryption scheme for encrypting new
|
32
32
|
# data.
|
33
|
-
# * <tt>:support_unencrypted_data</tt> -
|
34
|
-
#
|
35
|
-
# scenarios where you encrypt one column, and want to disable support for unencrypted data
|
36
|
-
# the global setting.
|
33
|
+
# * <tt>:support_unencrypted_data</tt> - When true, unencrypted data can be read normally. When false, it will raise errors.
|
34
|
+
# Falls back to +config.active_record.encryption.support_unencrypted_data+ if no value is provided.
|
35
|
+
# This is useful for scenarios where you encrypt one column, and want to disable support for unencrypted data
|
36
|
+
# without having to tweak the global setting.
|
37
37
|
# * <tt>:downcase</tt> - When true, it converts the encrypted content to downcase automatically. This allows to
|
38
38
|
# effectively ignore case when querying data. Notice that the case is lost. Use +:ignore_case+ if you are interested
|
39
39
|
# in preserving it.
|
data/lib/active_record/enum.rb
CHANGED
@@ -221,7 +221,7 @@ module ActiveRecord
|
|
221
221
|
|
222
222
|
private
|
223
223
|
def _enum(name, values, prefix: nil, suffix: nil, scopes: true, instance_methods: true, validate: false, **options)
|
224
|
-
assert_valid_enum_definition_values(values)
|
224
|
+
values = assert_valid_enum_definition_values(values)
|
225
225
|
assert_valid_enum_options(options)
|
226
226
|
|
227
227
|
# statuses = { }
|
@@ -342,6 +342,20 @@ module ActiveRecord
|
|
342
342
|
if values.keys.any?(&:blank?)
|
343
343
|
raise ArgumentError, "Enum values #{values} must not contain a blank name."
|
344
344
|
end
|
345
|
+
|
346
|
+
values = values.transform_values do |value|
|
347
|
+
value.is_a?(Symbol) ? value.name : value
|
348
|
+
end
|
349
|
+
|
350
|
+
values.each_value do |value|
|
351
|
+
case value
|
352
|
+
when String, Integer, true, false, nil
|
353
|
+
# noop
|
354
|
+
else
|
355
|
+
raise ArgumentError, "Enum values #{values} must be only booleans, integers, symbols or strings, got: #{value.class}"
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
345
359
|
when Array
|
346
360
|
if values.empty?
|
347
361
|
raise ArgumentError, "Enum values #{values} must not be empty."
|
@@ -357,6 +371,8 @@ module ActiveRecord
|
|
357
371
|
else
|
358
372
|
raise ArgumentError, "Enum values #{values} must be either a non-empty hash or an array."
|
359
373
|
end
|
374
|
+
|
375
|
+
values
|
360
376
|
end
|
361
377
|
|
362
378
|
def assert_valid_enum_options(options)
|
@@ -368,25 +384,25 @@ module ActiveRecord
|
|
368
384
|
|
369
385
|
ENUM_CONFLICT_MESSAGE = \
|
370
386
|
"You tried to define an enum named \"%{enum}\" on the model \"%{klass}\", but " \
|
371
|
-
"this will generate
|
387
|
+
"this will generate %{type} method \"%{method}\", which is already defined " \
|
372
388
|
"by %{source}."
|
373
389
|
private_constant :ENUM_CONFLICT_MESSAGE
|
374
390
|
|
375
391
|
def detect_enum_conflict!(enum_name, method_name, klass_method = false)
|
376
392
|
if klass_method && dangerous_class_method?(method_name)
|
377
|
-
raise_conflict_error(enum_name, method_name,
|
393
|
+
raise_conflict_error(enum_name, method_name, "a class")
|
378
394
|
elsif klass_method && method_defined_within?(method_name, Relation)
|
379
|
-
raise_conflict_error(enum_name, method_name,
|
395
|
+
raise_conflict_error(enum_name, method_name, "a class", source: Relation.name)
|
380
396
|
elsif klass_method && method_name.to_sym == :id
|
381
|
-
raise_conflict_error(enum_name, method_name)
|
397
|
+
raise_conflict_error(enum_name, method_name, "an instance")
|
382
398
|
elsif !klass_method && dangerous_attribute_method?(method_name)
|
383
|
-
raise_conflict_error(enum_name, method_name)
|
399
|
+
raise_conflict_error(enum_name, method_name, "an instance")
|
384
400
|
elsif !klass_method && method_defined_within?(method_name, _enum_methods_module, Module)
|
385
|
-
raise_conflict_error(enum_name, method_name, source: "another enum")
|
401
|
+
raise_conflict_error(enum_name, method_name, "an instance", source: "another enum")
|
386
402
|
end
|
387
403
|
end
|
388
404
|
|
389
|
-
def raise_conflict_error(enum_name, method_name, type
|
405
|
+
def raise_conflict_error(enum_name, method_name, type, source: "Active Record")
|
390
406
|
raise ArgumentError, ENUM_CONFLICT_MESSAGE % {
|
391
407
|
enum: enum_name,
|
392
408
|
klass: name,
|