activesupport 5.0.0 → 6.1.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activesupport might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/CHANGELOG.md +343 -590
- data/MIT-LICENSE +1 -1
- data/README.rdoc +5 -4
- data/lib/active_support/actionable_error.rb +48 -0
- data/lib/active_support/all.rb +5 -3
- data/lib/active_support/array_inquirer.rb +11 -5
- data/lib/active_support/backtrace_cleaner.rb +33 -5
- data/lib/active_support/benchmarkable.rb +5 -3
- data/lib/active_support/builder.rb +3 -1
- data/lib/active_support/cache/file_store.rb +45 -53
- data/lib/active_support/cache/mem_cache_store.rb +81 -79
- data/lib/active_support/cache/memory_store.rb +69 -41
- data/lib/active_support/cache/null_store.rb +11 -4
- data/lib/active_support/cache/redis_cache_store.rb +493 -0
- data/lib/active_support/cache/strategy/local_cache.rb +74 -37
- data/lib/active_support/cache/strategy/local_cache_middleware.rb +10 -9
- data/lib/active_support/cache.rb +332 -161
- data/lib/active_support/callbacks.rb +657 -586
- data/lib/active_support/concern.rb +79 -6
- data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +35 -0
- data/lib/active_support/concurrency/share_lock.rb +59 -19
- data/lib/active_support/configurable.rb +15 -17
- data/lib/active_support/configuration_file.rb +46 -0
- data/lib/active_support/core_ext/array/access.rb +21 -7
- data/lib/active_support/core_ext/array/conversions.rb +20 -18
- data/lib/active_support/core_ext/array/extract.rb +21 -0
- data/lib/active_support/core_ext/array/extract_options.rb +2 -0
- data/lib/active_support/core_ext/array/grouping.rb +3 -1
- data/lib/active_support/core_ext/array/inquiry.rb +3 -1
- data/lib/active_support/core_ext/array/wrap.rb +2 -0
- data/lib/active_support/core_ext/array.rb +9 -7
- data/lib/active_support/core_ext/benchmark.rb +5 -3
- data/lib/active_support/core_ext/big_decimal/conversions.rb +6 -6
- data/lib/active_support/core_ext/big_decimal.rb +3 -1
- data/lib/active_support/core_ext/class/attribute.rb +52 -49
- data/lib/active_support/core_ext/class/attribute_accessors.rb +3 -1
- data/lib/active_support/core_ext/class/subclasses.rb +18 -26
- data/lib/active_support/core_ext/class.rb +4 -2
- data/lib/active_support/core_ext/date/acts_like.rb +3 -1
- data/lib/active_support/core_ext/date/blank.rb +3 -1
- data/lib/active_support/core_ext/date/calculations.rb +16 -13
- data/lib/active_support/core_ext/date/conversions.rb +23 -21
- data/lib/active_support/core_ext/date/zones.rb +4 -2
- data/lib/active_support/core_ext/date.rb +7 -5
- data/lib/active_support/core_ext/date_and_time/calculations.rb +82 -53
- data/lib/active_support/core_ext/date_and_time/compatibility.rb +18 -5
- data/lib/active_support/core_ext/date_and_time/zones.rb +9 -9
- data/lib/active_support/core_ext/date_time/acts_like.rb +4 -2
- data/lib/active_support/core_ext/date_time/blank.rb +3 -1
- data/lib/active_support/core_ext/date_time/calculations.rb +23 -11
- data/lib/active_support/core_ext/date_time/compatibility.rb +15 -2
- data/lib/active_support/core_ext/date_time/conversions.rb +14 -13
- data/lib/active_support/core_ext/date_time.rb +7 -5
- data/lib/active_support/core_ext/digest/uuid.rb +7 -5
- data/lib/active_support/core_ext/digest.rb +3 -0
- data/lib/active_support/core_ext/enumerable.rb +165 -29
- data/lib/active_support/core_ext/file/atomic.rb +7 -5
- data/lib/active_support/core_ext/file.rb +3 -1
- data/lib/active_support/core_ext/hash/conversions.rb +40 -39
- data/lib/active_support/core_ext/hash/deep_merge.rb +8 -12
- data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
- data/lib/active_support/core_ext/hash/except.rb +4 -2
- data/lib/active_support/core_ext/hash/indifferent_access.rb +3 -2
- data/lib/active_support/core_ext/hash/keys.rb +9 -36
- data/lib/active_support/core_ext/hash/reverse_merge.rb +5 -2
- data/lib/active_support/core_ext/hash/slice.rb +8 -29
- data/lib/active_support/core_ext/hash.rb +10 -9
- data/lib/active_support/core_ext/integer/inflections.rb +3 -1
- data/lib/active_support/core_ext/integer/multiple.rb +3 -1
- data/lib/active_support/core_ext/integer/time.rb +11 -18
- data/lib/active_support/core_ext/integer.rb +5 -3
- data/lib/active_support/core_ext/kernel/concern.rb +3 -1
- data/lib/active_support/core_ext/kernel/reporting.rb +3 -1
- data/lib/active_support/core_ext/kernel/singleton_class.rb +2 -0
- data/lib/active_support/core_ext/kernel.rb +5 -4
- data/lib/active_support/core_ext/load_error.rb +2 -23
- data/lib/active_support/core_ext/marshal.rb +6 -2
- data/lib/active_support/core_ext/module/aliasing.rb +5 -48
- data/lib/active_support/core_ext/module/anonymous.rb +2 -0
- data/lib/active_support/core_ext/module/attr_internal.rb +7 -5
- data/lib/active_support/core_ext/module/attribute_accessors.rb +53 -59
- data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +31 -24
- data/lib/active_support/core_ext/module/concerning.rb +16 -11
- data/lib/active_support/core_ext/module/delegation.rb +159 -44
- data/lib/active_support/core_ext/module/deprecation.rb +2 -0
- data/lib/active_support/core_ext/module/introspection.rb +23 -26
- data/lib/active_support/core_ext/module/redefine_method.rb +40 -0
- data/lib/active_support/core_ext/module/remove_method.rb +5 -23
- data/lib/active_support/core_ext/module.rb +13 -12
- data/lib/active_support/core_ext/name_error.rb +36 -2
- data/lib/active_support/core_ext/numeric/bytes.rb +2 -0
- data/lib/active_support/core_ext/numeric/conversions.rb +129 -134
- data/lib/active_support/core_ext/numeric/time.rb +18 -26
- data/lib/active_support/core_ext/numeric.rb +5 -4
- data/lib/active_support/core_ext/object/acts_like.rb +12 -1
- data/lib/active_support/core_ext/object/blank.rb +14 -2
- data/lib/active_support/core_ext/object/conversions.rb +6 -4
- data/lib/active_support/core_ext/object/deep_dup.rb +4 -2
- data/lib/active_support/core_ext/object/duplicable.rb +13 -62
- data/lib/active_support/core_ext/object/inclusion.rb +3 -1
- data/lib/active_support/core_ext/object/instance_variables.rb +2 -0
- data/lib/active_support/core_ext/object/json.rb +42 -15
- data/lib/active_support/core_ext/object/to_param.rb +3 -1
- data/lib/active_support/core_ext/object/to_query.rb +10 -5
- data/lib/active_support/core_ext/object/try.rb +20 -8
- data/lib/active_support/core_ext/object/with_options.rb +15 -2
- data/lib/active_support/core_ext/object.rb +14 -12
- data/lib/active_support/core_ext/range/compare_range.rb +82 -0
- data/lib/active_support/core_ext/range/conversions.rb +35 -25
- data/lib/active_support/core_ext/range/each.rb +5 -2
- data/lib/active_support/core_ext/range/include_time_with_zone.rb +28 -0
- data/lib/active_support/core_ext/range/overlaps.rb +2 -0
- data/lib/active_support/core_ext/range.rb +7 -4
- data/lib/active_support/core_ext/regexp.rb +10 -1
- data/lib/active_support/core_ext/securerandom.rb +28 -6
- data/lib/active_support/core_ext/string/access.rb +9 -18
- data/lib/active_support/core_ext/string/behavior.rb +2 -0
- data/lib/active_support/core_ext/string/conversions.rb +5 -2
- data/lib/active_support/core_ext/string/exclude.rb +2 -0
- data/lib/active_support/core_ext/string/filters.rb +47 -4
- data/lib/active_support/core_ext/string/indent.rb +6 -4
- data/lib/active_support/core_ext/string/inflections.rb +78 -29
- data/lib/active_support/core_ext/string/inquiry.rb +4 -1
- data/lib/active_support/core_ext/string/multibyte.rb +10 -5
- data/lib/active_support/core_ext/string/output_safety.rb +86 -31
- data/lib/active_support/core_ext/string/starts_ends_with.rb +4 -2
- data/lib/active_support/core_ext/string/strip.rb +5 -1
- data/lib/active_support/core_ext/string/zones.rb +4 -2
- data/lib/active_support/core_ext/string.rb +15 -13
- data/lib/active_support/core_ext/symbol/starts_ends_with.rb +14 -0
- data/lib/active_support/core_ext/symbol.rb +3 -0
- data/lib/active_support/core_ext/time/acts_like.rb +3 -1
- data/lib/active_support/core_ext/time/calculations.rb +117 -45
- data/lib/active_support/core_ext/time/compatibility.rb +13 -2
- data/lib/active_support/core_ext/time/conversions.rb +18 -12
- data/lib/active_support/core_ext/time/zones.rb +9 -7
- data/lib/active_support/core_ext/time.rb +7 -5
- data/lib/active_support/core_ext/uri.rb +12 -7
- data/lib/active_support/core_ext.rb +3 -2
- data/lib/active_support/current_attributes/test_helper.rb +13 -0
- data/lib/active_support/current_attributes.rb +208 -0
- data/lib/active_support/dependencies/autoload.rb +2 -0
- data/lib/active_support/dependencies/interlock.rb +7 -1
- data/lib/active_support/dependencies/zeitwerk_integration.rb +117 -0
- data/lib/active_support/dependencies.rb +172 -98
- data/lib/active_support/deprecation/behaviors.rb +45 -13
- data/lib/active_support/deprecation/constant_accessor.rb +52 -0
- data/lib/active_support/deprecation/disallowed.rb +56 -0
- data/lib/active_support/deprecation/instance_delegator.rb +16 -2
- data/lib/active_support/deprecation/method_wrappers.rb +32 -17
- data/lib/active_support/deprecation/proxy_wrappers.rb +35 -7
- data/lib/active_support/deprecation/reporting.rb +61 -16
- data/lib/active_support/deprecation.rb +17 -9
- data/lib/active_support/descendants_tracker.rb +61 -9
- data/lib/active_support/digest.rb +20 -0
- data/lib/active_support/duration/iso8601_parser.rb +67 -66
- data/lib/active_support/duration/iso8601_serializer.rb +25 -17
- data/lib/active_support/duration.rb +349 -46
- data/lib/active_support/encrypted_configuration.rb +45 -0
- data/lib/active_support/encrypted_file.rb +117 -0
- data/lib/active_support/environment_inquirer.rb +20 -0
- data/lib/active_support/evented_file_update_checker.rb +88 -112
- data/lib/active_support/execution_wrapper.rb +25 -13
- data/lib/active_support/executor.rb +3 -1
- data/lib/active_support/file_update_checker.rb +56 -51
- data/lib/active_support/fork_tracker.rb +62 -0
- data/lib/active_support/gem_version.rb +4 -2
- data/lib/active_support/gzip.rb +7 -5
- data/lib/active_support/hash_with_indifferent_access.rb +153 -49
- data/lib/active_support/i18n.rb +9 -6
- data/lib/active_support/i18n_railtie.rb +30 -20
- data/lib/active_support/inflections.rb +13 -11
- data/lib/active_support/inflector/inflections.rb +28 -15
- data/lib/active_support/inflector/methods.rb +120 -109
- data/lib/active_support/inflector/transliterate.rb +60 -25
- data/lib/active_support/inflector.rb +7 -5
- data/lib/active_support/json/decoding.rb +30 -29
- data/lib/active_support/json/encoding.rb +22 -11
- data/lib/active_support/json.rb +4 -2
- data/lib/active_support/key_generator.rb +6 -36
- data/lib/active_support/lazy_load_hooks.rb +53 -20
- data/lib/active_support/locale/en.rb +33 -0
- data/lib/active_support/locale/en.yml +7 -3
- data/lib/active_support/log_subscriber/test_helper.rb +11 -9
- data/lib/active_support/log_subscriber.rb +51 -18
- data/lib/active_support/logger.rb +9 -22
- data/lib/active_support/logger_silence.rb +14 -21
- data/lib/active_support/logger_thread_safe_level.rb +55 -8
- data/lib/active_support/message_encryptor.rb +170 -53
- data/lib/active_support/message_verifier.rb +91 -20
- data/lib/active_support/messages/metadata.rb +80 -0
- data/lib/active_support/messages/rotation_configuration.rb +23 -0
- data/lib/active_support/messages/rotator.rb +57 -0
- data/lib/active_support/multibyte/chars.rb +24 -78
- data/lib/active_support/multibyte/unicode.rb +21 -352
- data/lib/active_support/multibyte.rb +4 -2
- data/lib/active_support/notifications/fanout.rb +121 -19
- data/lib/active_support/notifications/instrumenter.rb +78 -14
- data/lib/active_support/notifications.rb +80 -12
- data/lib/active_support/number_helper/number_converter.rb +17 -16
- data/lib/active_support/number_helper/number_to_currency_converter.rb +6 -9
- data/lib/active_support/number_helper/number_to_delimited_converter.rb +5 -3
- data/lib/active_support/number_helper/number_to_human_converter.rb +13 -12
- data/lib/active_support/number_helper/number_to_human_size_converter.rb +11 -13
- data/lib/active_support/number_helper/number_to_percentage_converter.rb +5 -1
- data/lib/active_support/number_helper/number_to_phone_converter.rb +5 -4
- data/lib/active_support/number_helper/number_to_rounded_converter.rb +18 -55
- data/lib/active_support/number_helper/rounding_helper.rb +50 -0
- data/lib/active_support/number_helper.rb +45 -16
- data/lib/active_support/option_merger.rb +25 -4
- data/lib/active_support/ordered_hash.rb +6 -4
- data/lib/active_support/ordered_options.rb +23 -9
- data/lib/active_support/parameter_filter.rb +133 -0
- data/lib/active_support/per_thread_registry.rb +7 -5
- data/lib/active_support/proxy_object.rb +2 -0
- data/lib/active_support/rails.rb +8 -9
- data/lib/active_support/railtie.rb +62 -11
- data/lib/active_support/reloader.rb +12 -11
- data/lib/active_support/rescuable.rb +20 -11
- data/lib/active_support/secure_compare_rotator.rb +51 -0
- data/lib/active_support/security_utils.rb +26 -15
- data/lib/active_support/string_inquirer.rb +12 -3
- data/lib/active_support/subscriber.rb +77 -23
- data/lib/active_support/tagged_logging.rb +52 -17
- data/lib/active_support/test_case.rb +106 -29
- data/lib/active_support/testing/assertions.rb +144 -8
- data/lib/active_support/testing/autorun.rb +5 -10
- data/lib/active_support/testing/constant_lookup.rb +2 -1
- data/lib/active_support/testing/declarative.rb +3 -1
- data/lib/active_support/testing/deprecation.rb +4 -2
- data/lib/active_support/testing/file_fixtures.rb +4 -0
- data/lib/active_support/testing/isolation.rb +19 -24
- data/lib/active_support/testing/method_call_assertions.rb +31 -2
- data/lib/active_support/testing/parallelization/server.rb +78 -0
- data/lib/active_support/testing/parallelization/worker.rb +100 -0
- data/lib/active_support/testing/parallelization.rb +51 -0
- data/lib/active_support/testing/setup_and_teardown.rb +13 -8
- data/lib/active_support/testing/stream.rb +30 -29
- data/lib/active_support/testing/tagged_logging.rb +3 -1
- data/lib/active_support/testing/time_helpers.rb +125 -24
- data/lib/active_support/time.rb +14 -12
- data/lib/active_support/time_with_zone.rb +142 -55
- data/lib/active_support/values/time_zone.rb +160 -53
- data/lib/active_support/version.rb +3 -1
- data/lib/active_support/xml_mini/jdom.rb +115 -114
- data/lib/active_support/xml_mini/libxml.rb +15 -14
- data/lib/active_support/xml_mini/libxmlsax.rb +16 -18
- data/lib/active_support/xml_mini/nokogiri.rb +13 -13
- data/lib/active_support/xml_mini/nokogirisax.rb +15 -16
- data/lib/active_support/xml_mini/rexml.rb +18 -9
- data/lib/active_support/xml_mini.rb +44 -42
- data/lib/active_support.rb +19 -10
- metadata +79 -37
- data/lib/active_support/concurrency/latch.rb +0 -19
- data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -7
- data/lib/active_support/core_ext/hash/compact.rb +0 -20
- data/lib/active_support/core_ext/hash/transform_values.rb +0 -29
- data/lib/active_support/core_ext/kernel/agnostics.rb +0 -11
- data/lib/active_support/core_ext/kernel/debugger.rb +0 -3
- data/lib/active_support/core_ext/module/method_transplanting.rb +0 -3
- data/lib/active_support/core_ext/module/qualified_const.rb +0 -70
- data/lib/active_support/core_ext/module/reachable.rb +0 -8
- data/lib/active_support/core_ext/numeric/inquiry.rb +0 -26
- data/lib/active_support/core_ext/range/include_range.rb +0 -23
- data/lib/active_support/core_ext/struct.rb +0 -3
- data/lib/active_support/core_ext/time/marshal.rb +0 -3
- data/lib/active_support/values/unicode_tables.dat +0 -0
@@ -1,9 +1,9 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Extends the module object with class/module and instance accessors for
|
4
4
|
# class/module attributes, just like the native attr* accessors for instance
|
5
5
|
# attributes, but does so on a per-thread basis.
|
6
|
-
#
|
6
|
+
#
|
7
7
|
# So the values are scoped within the Thread.current space under the class name
|
8
8
|
# of the module.
|
9
9
|
class Module
|
@@ -25,7 +25,7 @@ class Module
|
|
25
25
|
# end
|
26
26
|
# # => NameError: invalid attribute name: 1_Badname
|
27
27
|
#
|
28
|
-
#
|
28
|
+
# To omit the instance reader method, pass
|
29
29
|
# <tt>instance_reader: false</tt> or <tt>instance_accessor: false</tt>.
|
30
30
|
#
|
31
31
|
# class Current
|
@@ -33,24 +33,27 @@ class Module
|
|
33
33
|
# end
|
34
34
|
#
|
35
35
|
# Current.new.user # => NoMethodError
|
36
|
-
def thread_mattr_reader(*syms)
|
37
|
-
options = syms.extract_options!
|
38
|
-
|
36
|
+
def thread_mattr_reader(*syms, instance_reader: true, instance_accessor: true, default: nil) # :nodoc:
|
39
37
|
syms.each do |sym|
|
40
|
-
raise NameError.new("invalid attribute name: #{sym}") unless
|
38
|
+
raise NameError.new("invalid attribute name: #{sym}") unless /^[_A-Za-z]\w*$/.match?(sym)
|
39
|
+
|
40
|
+
# The following generated method concatenates `name` because we want it
|
41
|
+
# to work with inheritance via polymorphism.
|
41
42
|
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
42
43
|
def self.#{sym}
|
43
|
-
Thread.current[
|
44
|
+
Thread.current["attr_" + name + "_#{sym}"]
|
44
45
|
end
|
45
46
|
EOS
|
46
47
|
|
47
|
-
|
48
|
+
if instance_reader && instance_accessor
|
48
49
|
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
49
50
|
def #{sym}
|
50
|
-
|
51
|
+
self.class.#{sym}
|
51
52
|
end
|
52
53
|
EOS
|
53
54
|
end
|
55
|
+
|
56
|
+
Thread.current["attr_" + name + "_#{sym}"] = default unless default.nil?
|
54
57
|
end
|
55
58
|
end
|
56
59
|
alias :thread_cattr_reader :thread_mattr_reader
|
@@ -65,7 +68,7 @@ class Module
|
|
65
68
|
# Current.user = "DHH"
|
66
69
|
# Thread.current[:attr_Current_user] # => "DHH"
|
67
70
|
#
|
68
|
-
#
|
71
|
+
# To omit the instance writer method, pass
|
69
72
|
# <tt>instance_writer: false</tt> or <tt>instance_accessor: false</tt>.
|
70
73
|
#
|
71
74
|
# class Current
|
@@ -73,23 +76,27 @@ class Module
|
|
73
76
|
# end
|
74
77
|
#
|
75
78
|
# Current.new.user = "DHH" # => NoMethodError
|
76
|
-
def thread_mattr_writer(*syms)
|
77
|
-
options = syms.extract_options!
|
79
|
+
def thread_mattr_writer(*syms, instance_writer: true, instance_accessor: true, default: nil) # :nodoc:
|
78
80
|
syms.each do |sym|
|
79
|
-
raise NameError.new("invalid attribute name: #{sym}") unless
|
81
|
+
raise NameError.new("invalid attribute name: #{sym}") unless /^[_A-Za-z]\w*$/.match?(sym)
|
82
|
+
|
83
|
+
# The following generated method concatenates `name` because we want it
|
84
|
+
# to work with inheritance via polymorphism.
|
80
85
|
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
81
86
|
def self.#{sym}=(obj)
|
82
|
-
Thread.current[
|
87
|
+
Thread.current["attr_" + name + "_#{sym}"] = obj
|
83
88
|
end
|
84
89
|
EOS
|
85
90
|
|
86
|
-
|
91
|
+
if instance_writer && instance_accessor
|
87
92
|
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
88
93
|
def #{sym}=(obj)
|
89
|
-
|
94
|
+
self.class.#{sym} = obj
|
90
95
|
end
|
91
96
|
EOS
|
92
97
|
end
|
98
|
+
|
99
|
+
public_send("#{sym}=", default) unless default.nil?
|
93
100
|
end
|
94
101
|
end
|
95
102
|
alias :thread_cattr_writer :thread_mattr_writer
|
@@ -115,8 +122,8 @@ class Module
|
|
115
122
|
# Customer.user # => "Rafael"
|
116
123
|
# Account.user # => "DHH"
|
117
124
|
#
|
118
|
-
# To
|
119
|
-
# To
|
125
|
+
# To omit the instance writer method, pass <tt>instance_writer: false</tt>.
|
126
|
+
# To omit the instance reader method, pass <tt>instance_reader: false</tt>.
|
120
127
|
#
|
121
128
|
# class Current
|
122
129
|
# thread_mattr_accessor :user, instance_writer: false, instance_reader: false
|
@@ -125,17 +132,17 @@ class Module
|
|
125
132
|
# Current.new.user = "DHH" # => NoMethodError
|
126
133
|
# Current.new.user # => NoMethodError
|
127
134
|
#
|
128
|
-
# Or pass <tt>instance_accessor: false</tt>, to
|
135
|
+
# Or pass <tt>instance_accessor: false</tt>, to omit both instance methods.
|
129
136
|
#
|
130
137
|
# class Current
|
131
|
-
#
|
138
|
+
# thread_mattr_accessor :user, instance_accessor: false
|
132
139
|
# end
|
133
140
|
#
|
134
141
|
# Current.new.user = "DHH" # => NoMethodError
|
135
142
|
# Current.new.user # => NoMethodError
|
136
|
-
def thread_mattr_accessor(*syms,
|
137
|
-
thread_mattr_reader(*syms,
|
138
|
-
thread_mattr_writer(*syms,
|
143
|
+
def thread_mattr_accessor(*syms, instance_reader: true, instance_writer: true, instance_accessor: true, default: nil)
|
144
|
+
thread_mattr_reader(*syms, instance_reader: instance_reader, instance_accessor: instance_accessor, default: default)
|
145
|
+
thread_mattr_writer(*syms, instance_writer: instance_writer, instance_accessor: instance_accessor)
|
139
146
|
end
|
140
147
|
alias :thread_cattr_accessor :thread_mattr_accessor
|
141
148
|
end
|
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/concern"
|
2
4
|
|
3
5
|
class Module
|
4
6
|
# = Bite-sized separation of concerns
|
@@ -20,7 +22,7 @@ class Module
|
|
20
22
|
#
|
21
23
|
# == Using comments:
|
22
24
|
#
|
23
|
-
# class Todo
|
25
|
+
# class Todo < ApplicationRecord
|
24
26
|
# # Other todo implementation
|
25
27
|
# # ...
|
26
28
|
#
|
@@ -28,7 +30,6 @@ class Module
|
|
28
30
|
# has_many :events
|
29
31
|
#
|
30
32
|
# before_create :track_creation
|
31
|
-
# after_destroy :track_deletion
|
32
33
|
#
|
33
34
|
# private
|
34
35
|
# def track_creation
|
@@ -40,7 +41,7 @@ class Module
|
|
40
41
|
#
|
41
42
|
# Noisy syntax.
|
42
43
|
#
|
43
|
-
# class Todo
|
44
|
+
# class Todo < ApplicationRecord
|
44
45
|
# # Other todo implementation
|
45
46
|
# # ...
|
46
47
|
#
|
@@ -50,7 +51,6 @@ class Module
|
|
50
51
|
# included do
|
51
52
|
# has_many :events
|
52
53
|
# before_create :track_creation
|
53
|
-
# after_destroy :track_deletion
|
54
54
|
# end
|
55
55
|
#
|
56
56
|
# private
|
@@ -68,7 +68,7 @@ class Module
|
|
68
68
|
# increased overhead can be a reasonable tradeoff even if it reduces our
|
69
69
|
# at-a-glance perception of how things work.
|
70
70
|
#
|
71
|
-
# class Todo
|
71
|
+
# class Todo < ApplicationRecord
|
72
72
|
# # Other todo implementation
|
73
73
|
# # ...
|
74
74
|
#
|
@@ -80,7 +80,7 @@ class Module
|
|
80
80
|
# By quieting the mix-in noise, we arrive at a natural, low-ceremony way to
|
81
81
|
# separate bite-sized concerns.
|
82
82
|
#
|
83
|
-
# class Todo
|
83
|
+
# class Todo < ApplicationRecord
|
84
84
|
# # Other todo implementation
|
85
85
|
# # ...
|
86
86
|
#
|
@@ -88,7 +88,6 @@ class Module
|
|
88
88
|
# included do
|
89
89
|
# has_many :events
|
90
90
|
# before_create :track_creation
|
91
|
-
# after_destroy :track_deletion
|
92
91
|
# end
|
93
92
|
#
|
94
93
|
# private
|
@@ -99,16 +98,22 @@ class Module
|
|
99
98
|
# end
|
100
99
|
#
|
101
100
|
# Todo.ancestors
|
102
|
-
# # => [Todo, Todo::EventTracking, Object]
|
101
|
+
# # => [Todo, Todo::EventTracking, ApplicationRecord, Object]
|
103
102
|
#
|
104
103
|
# This small step has some wonderful ripple effects. We can
|
105
104
|
# * grok the behavior of our class in one glance,
|
106
105
|
# * clean up monolithic junk-drawer classes by separating their concerns, and
|
107
106
|
# * stop leaning on protected/private for crude "this is internal stuff" modularity.
|
107
|
+
#
|
108
|
+
# === Prepending concerning
|
109
|
+
#
|
110
|
+
# <tt>concerning</tt> supports a <tt>prepend: true</tt> argument which will <tt>prepend</tt> the
|
111
|
+
# concern instead of using <tt>include</tt> for it.
|
108
112
|
module Concerning
|
109
113
|
# Define a new concern and mix it in.
|
110
|
-
def concerning(topic, &block)
|
111
|
-
|
114
|
+
def concerning(topic, prepend: false, &block)
|
115
|
+
method = prepend ? :prepend : :include
|
116
|
+
__send__(method, concern(topic, &block))
|
112
117
|
end
|
113
118
|
|
114
119
|
# A low-cruft shortcut to define a concern.
|
@@ -1,24 +1,29 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "set"
|
2
4
|
|
3
5
|
class Module
|
4
6
|
# Error generated by +delegate+ when a method is called on +nil+ and +allow_nil+
|
5
7
|
# option is not used.
|
6
8
|
class DelegationError < NoMethodError; end
|
7
9
|
|
10
|
+
RUBY_RESERVED_KEYWORDS = %w(alias and BEGIN begin break case class def defined? do
|
11
|
+
else elsif END end ensure false for if in module next nil not or redo rescue retry
|
12
|
+
return self super then true undef unless until when while yield)
|
13
|
+
DELEGATION_RESERVED_KEYWORDS = %w(_ arg args block)
|
8
14
|
DELEGATION_RESERVED_METHOD_NAMES = Set.new(
|
9
|
-
|
10
|
-
else elsif END end ensure false for if in module next nil not or redo
|
11
|
-
rescue retry return self super then true undef unless until when while
|
12
|
-
yield)
|
15
|
+
RUBY_RESERVED_KEYWORDS + DELEGATION_RESERVED_KEYWORDS
|
13
16
|
).freeze
|
14
17
|
|
15
18
|
# Provides a +delegate+ class method to easily expose contained objects'
|
16
19
|
# public methods as your own.
|
17
20
|
#
|
18
21
|
# ==== Options
|
19
|
-
# * <tt>:to</tt> - Specifies the target object
|
22
|
+
# * <tt>:to</tt> - Specifies the target object name as a symbol or string
|
20
23
|
# * <tt>:prefix</tt> - Prefixes the new method with the target name or a custom prefix
|
21
|
-
# * <tt>:allow_nil</tt> -
|
24
|
+
# * <tt>:allow_nil</tt> - If set to true, prevents a +Module::DelegationError+
|
25
|
+
# from being raised
|
26
|
+
# * <tt>:private</tt> - If set to true, changes method visibility to private
|
22
27
|
#
|
23
28
|
# The macro receives one or more method names (specified as symbols or
|
24
29
|
# strings) and the name of the target object via the <tt>:to</tt> option
|
@@ -109,19 +114,34 @@ class Module
|
|
109
114
|
# invoice.customer_name # => 'John Doe'
|
110
115
|
# invoice.customer_address # => 'Vimmersvej 13'
|
111
116
|
#
|
117
|
+
# The delegated methods are public by default.
|
118
|
+
# Pass <tt>private: true</tt> to change that.
|
119
|
+
#
|
120
|
+
# class User < ActiveRecord::Base
|
121
|
+
# has_one :profile
|
122
|
+
# delegate :first_name, to: :profile
|
123
|
+
# delegate :date_of_birth, to: :profile, private: true
|
124
|
+
#
|
125
|
+
# def age
|
126
|
+
# Date.today.year - date_of_birth.year
|
127
|
+
# end
|
128
|
+
# end
|
129
|
+
#
|
130
|
+
# User.new.first_name # => "Tomas"
|
131
|
+
# User.new.date_of_birth # => NoMethodError: private method `date_of_birth' called for #<User:0x00000008221340>
|
132
|
+
# User.new.age # => 2
|
133
|
+
#
|
112
134
|
# If the target is +nil+ and does not respond to the delegated method a
|
113
|
-
# +
|
114
|
-
#
|
115
|
-
# <tt>:allow_nil</tt> option: If the target is not +nil+, or it is and
|
116
|
-
# responds to the method, everything works as usual. But if it is +nil+ and
|
117
|
-
# does not respond to the delegated method, +nil+ is returned.
|
135
|
+
# +Module::DelegationError+ is raised. If you wish to instead return +nil+,
|
136
|
+
# use the <tt>:allow_nil</tt> option.
|
118
137
|
#
|
119
138
|
# class User < ActiveRecord::Base
|
120
139
|
# has_one :profile
|
121
140
|
# delegate :age, to: :profile
|
122
141
|
# end
|
123
142
|
#
|
124
|
-
# User.new.age
|
143
|
+
# User.new.age
|
144
|
+
# # => Module::DelegationError: User#age delegated to profile.age, but profile is nil
|
125
145
|
#
|
126
146
|
# But if not having a profile yet is fine and should not be an error
|
127
147
|
# condition:
|
@@ -148,21 +168,20 @@ class Module
|
|
148
168
|
# Foo.new("Bar").name # raises NoMethodError: undefined method `name'
|
149
169
|
#
|
150
170
|
# The target method must be public, otherwise it will raise +NoMethodError+.
|
151
|
-
|
152
|
-
def delegate(*methods, to: nil, prefix: nil, allow_nil: nil)
|
171
|
+
def delegate(*methods, to: nil, prefix: nil, allow_nil: nil, private: nil)
|
153
172
|
unless to
|
154
|
-
raise ArgumentError,
|
173
|
+
raise ArgumentError, "Delegation needs a target. Supply a keyword argument 'to' (e.g. delegate :hello, to: :greeter)."
|
155
174
|
end
|
156
175
|
|
157
|
-
if prefix == true &&
|
158
|
-
raise ArgumentError,
|
176
|
+
if prefix == true && /^[^a-z_]/.match?(to)
|
177
|
+
raise ArgumentError, "Can only automatically set the delegation prefix when delegating to a method."
|
159
178
|
end
|
160
179
|
|
161
180
|
method_prefix = \
|
162
181
|
if prefix
|
163
182
|
"#{prefix == true ? to : prefix}_"
|
164
183
|
else
|
165
|
-
|
184
|
+
""
|
166
185
|
end
|
167
186
|
|
168
187
|
location = caller_locations(1, 1).first
|
@@ -171,10 +190,22 @@ class Module
|
|
171
190
|
to = to.to_s
|
172
191
|
to = "self.#{to}" if DELEGATION_RESERVED_METHOD_NAMES.include?(to)
|
173
192
|
|
174
|
-
|
193
|
+
method_def = []
|
194
|
+
method_names = []
|
195
|
+
|
196
|
+
methods.map do |method|
|
197
|
+
method_name = prefix ? "#{method_prefix}#{method}" : method
|
198
|
+
method_names << method_name.to_sym
|
199
|
+
|
175
200
|
# Attribute writer methods only accept one argument. Makes sure []=
|
176
201
|
# methods still accept two arguments.
|
177
|
-
definition =
|
202
|
+
definition = if /[^\]]=$/.match?(method)
|
203
|
+
"arg"
|
204
|
+
elsif RUBY_VERSION >= "2.7"
|
205
|
+
"..."
|
206
|
+
else
|
207
|
+
"*args, &block"
|
208
|
+
end
|
178
209
|
|
179
210
|
# The following generated method calls the target exactly once, storing
|
180
211
|
# the returned value in a dummy variable.
|
@@ -184,32 +215,116 @@ class Module
|
|
184
215
|
# whereas conceptually, from the user point of view, the delegator should
|
185
216
|
# be doing one call.
|
186
217
|
if allow_nil
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
"
|
191
|
-
" _
|
192
|
-
"
|
193
|
-
|
194
|
-
|
218
|
+
method = method.to_s
|
219
|
+
|
220
|
+
method_def <<
|
221
|
+
"def #{method_name}(#{definition})" <<
|
222
|
+
" _ = #{to}" <<
|
223
|
+
" if !_.nil? || nil.respond_to?(:#{method})" <<
|
224
|
+
" _.#{method}(#{definition})" <<
|
225
|
+
" end" <<
|
226
|
+
"end"
|
195
227
|
else
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
"
|
201
|
-
" _
|
202
|
-
"
|
203
|
-
"
|
204
|
-
"
|
205
|
-
"
|
206
|
-
"
|
207
|
-
"
|
228
|
+
method = method.to_s
|
229
|
+
method_name = method_name.to_s
|
230
|
+
|
231
|
+
method_def <<
|
232
|
+
"def #{method_name}(#{definition})" <<
|
233
|
+
" _ = #{to}" <<
|
234
|
+
" _.#{method}(#{definition})" <<
|
235
|
+
"rescue NoMethodError => e" <<
|
236
|
+
" if _.nil? && e.name == :#{method}" <<
|
237
|
+
%( raise DelegationError, "#{self}##{method_name} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}") <<
|
238
|
+
" else" <<
|
239
|
+
" raise" <<
|
240
|
+
" end" <<
|
208
241
|
"end"
|
209
|
-
].join ';'
|
210
242
|
end
|
211
|
-
|
212
|
-
module_eval(method_def, file, line)
|
213
243
|
end
|
244
|
+
module_eval(method_def.join(";"), file, line)
|
245
|
+
private(*method_names) if private
|
246
|
+
method_names
|
247
|
+
end
|
248
|
+
|
249
|
+
# When building decorators, a common pattern may emerge:
|
250
|
+
#
|
251
|
+
# class Partition
|
252
|
+
# def initialize(event)
|
253
|
+
# @event = event
|
254
|
+
# end
|
255
|
+
#
|
256
|
+
# def person
|
257
|
+
# detail.person || creator
|
258
|
+
# end
|
259
|
+
#
|
260
|
+
# private
|
261
|
+
# def respond_to_missing?(name, include_private = false)
|
262
|
+
# @event.respond_to?(name, include_private)
|
263
|
+
# end
|
264
|
+
#
|
265
|
+
# def method_missing(method, *args, &block)
|
266
|
+
# @event.send(method, *args, &block)
|
267
|
+
# end
|
268
|
+
# end
|
269
|
+
#
|
270
|
+
# With <tt>Module#delegate_missing_to</tt>, the above is condensed to:
|
271
|
+
#
|
272
|
+
# class Partition
|
273
|
+
# delegate_missing_to :@event
|
274
|
+
#
|
275
|
+
# def initialize(event)
|
276
|
+
# @event = event
|
277
|
+
# end
|
278
|
+
#
|
279
|
+
# def person
|
280
|
+
# detail.person || creator
|
281
|
+
# end
|
282
|
+
# end
|
283
|
+
#
|
284
|
+
# The target can be anything callable within the object, e.g. instance
|
285
|
+
# variables, methods, constants, etc.
|
286
|
+
#
|
287
|
+
# The delegated method must be public on the target, otherwise it will
|
288
|
+
# raise +DelegationError+. If you wish to instead return +nil+,
|
289
|
+
# use the <tt>:allow_nil</tt> option.
|
290
|
+
#
|
291
|
+
# The <tt>marshal_dump</tt> and <tt>_dump</tt> methods are exempt from
|
292
|
+
# delegation due to possible interference when calling
|
293
|
+
# <tt>Marshal.dump(object)</tt>, should the delegation target method
|
294
|
+
# of <tt>object</tt> add or remove instance variables.
|
295
|
+
def delegate_missing_to(target, allow_nil: nil)
|
296
|
+
target = target.to_s
|
297
|
+
target = "self.#{target}" if DELEGATION_RESERVED_METHOD_NAMES.include?(target)
|
298
|
+
|
299
|
+
module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
300
|
+
def respond_to_missing?(name, include_private = false)
|
301
|
+
# It may look like an oversight, but we deliberately do not pass
|
302
|
+
# +include_private+, because they do not get delegated.
|
303
|
+
|
304
|
+
return false if name == :marshal_dump || name == :_dump
|
305
|
+
#{target}.respond_to?(name) || super
|
306
|
+
end
|
307
|
+
|
308
|
+
def method_missing(method, *args, &block)
|
309
|
+
if #{target}.respond_to?(method)
|
310
|
+
#{target}.public_send(method, *args, &block)
|
311
|
+
else
|
312
|
+
begin
|
313
|
+
super
|
314
|
+
rescue NoMethodError
|
315
|
+
if #{target}.nil?
|
316
|
+
if #{allow_nil == true}
|
317
|
+
nil
|
318
|
+
else
|
319
|
+
raise DelegationError, "\#{method} delegated to #{target}, but #{target} is nil"
|
320
|
+
end
|
321
|
+
else
|
322
|
+
raise
|
323
|
+
end
|
324
|
+
end
|
325
|
+
end
|
326
|
+
end
|
327
|
+
ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
|
328
|
+
RUBY
|
214
329
|
end
|
215
330
|
end
|
@@ -1,14 +1,19 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/string/filters"
|
4
|
+
require "active_support/inflector"
|
2
5
|
|
3
6
|
class Module
|
4
7
|
# Returns the name of the module containing this one.
|
5
8
|
#
|
6
|
-
# M::N.
|
7
|
-
def
|
8
|
-
if defined?
|
9
|
+
# M::N.module_parent_name # => "M"
|
10
|
+
def module_parent_name
|
11
|
+
if defined?(@parent_name)
|
9
12
|
@parent_name
|
10
13
|
else
|
11
|
-
|
14
|
+
parent_name = name =~ /::[^:]+\z/ ? -$` : nil
|
15
|
+
@parent_name = parent_name unless frozen?
|
16
|
+
parent_name
|
12
17
|
end
|
13
18
|
end
|
14
19
|
|
@@ -20,15 +25,15 @@ class Module
|
|
20
25
|
# end
|
21
26
|
# X = M::N
|
22
27
|
#
|
23
|
-
# M::N.
|
24
|
-
# X.
|
28
|
+
# M::N.module_parent # => M
|
29
|
+
# X.module_parent # => M
|
25
30
|
#
|
26
31
|
# The parent of top-level and anonymous modules is Object.
|
27
32
|
#
|
28
|
-
# M.
|
29
|
-
# Module.new.
|
30
|
-
def
|
31
|
-
|
33
|
+
# M.module_parent # => Object
|
34
|
+
# Module.new.module_parent # => Object
|
35
|
+
def module_parent
|
36
|
+
module_parent_name ? ActiveSupport::Inflector.constantize(module_parent_name) : Object
|
32
37
|
end
|
33
38
|
|
34
39
|
# Returns all the parents of this module according to its name, ordered from
|
@@ -40,27 +45,19 @@ class Module
|
|
40
45
|
# end
|
41
46
|
# X = M::N
|
42
47
|
#
|
43
|
-
# M.
|
44
|
-
# M::N.
|
45
|
-
# X.
|
46
|
-
def
|
48
|
+
# M.module_parents # => [Object]
|
49
|
+
# M::N.module_parents # => [M, Object]
|
50
|
+
# X.module_parents # => [M, Object]
|
51
|
+
def module_parents
|
47
52
|
parents = []
|
48
|
-
if
|
49
|
-
parts =
|
53
|
+
if module_parent_name
|
54
|
+
parts = module_parent_name.split("::")
|
50
55
|
until parts.empty?
|
51
|
-
parents << ActiveSupport::Inflector.constantize(parts *
|
56
|
+
parents << ActiveSupport::Inflector.constantize(parts * "::")
|
52
57
|
parts.pop
|
53
58
|
end
|
54
59
|
end
|
55
60
|
parents << Object unless parents.include? Object
|
56
61
|
parents
|
57
62
|
end
|
58
|
-
|
59
|
-
def local_constants #:nodoc:
|
60
|
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
61
|
-
Module#local_constants is deprecated and will be removed in Rails 5.1.
|
62
|
-
Use Module#constants(false) instead.
|
63
|
-
MSG
|
64
|
-
constants(false)
|
65
|
-
end
|
66
63
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Module
|
4
|
+
# Marks the named method as intended to be redefined, if it exists.
|
5
|
+
# Suppresses the Ruby method redefinition warning. Prefer
|
6
|
+
# #redefine_method where possible.
|
7
|
+
def silence_redefinition_of_method(method)
|
8
|
+
if method_defined?(method) || private_method_defined?(method)
|
9
|
+
# This suppresses the "method redefined" warning; the self-alias
|
10
|
+
# looks odd, but means we don't need to generate a unique name
|
11
|
+
alias_method method, method
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# Replaces the existing method definition, if there is one, with the passed
|
16
|
+
# block as its body.
|
17
|
+
def redefine_method(method, &block)
|
18
|
+
visibility = method_visibility(method)
|
19
|
+
silence_redefinition_of_method(method)
|
20
|
+
define_method(method, &block)
|
21
|
+
send(visibility, method)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Replaces the existing singleton method definition, if there is one, with
|
25
|
+
# the passed block as its body.
|
26
|
+
def redefine_singleton_method(method, &block)
|
27
|
+
singleton_class.redefine_method(method, &block)
|
28
|
+
end
|
29
|
+
|
30
|
+
def method_visibility(method) # :nodoc:
|
31
|
+
case
|
32
|
+
when private_method_defined?(method)
|
33
|
+
:private
|
34
|
+
when protected_method_defined?(method)
|
35
|
+
:protected
|
36
|
+
else
|
37
|
+
:public
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -1,3 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/module/redefine_method"
|
4
|
+
|
1
5
|
class Module
|
2
6
|
# Removes the named method, if it exists.
|
3
7
|
def remove_possible_method(method)
|
@@ -8,28 +12,6 @@ class Module
|
|
8
12
|
|
9
13
|
# Removes the named singleton method, if it exists.
|
10
14
|
def remove_possible_singleton_method(method)
|
11
|
-
singleton_class.
|
12
|
-
remove_possible_method(method)
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
# Replaces the existing method definition, if there is one, with the passed
|
17
|
-
# block as its body.
|
18
|
-
def redefine_method(method, &block)
|
19
|
-
visibility = method_visibility(method)
|
20
|
-
remove_possible_method(method)
|
21
|
-
define_method(method, &block)
|
22
|
-
send(visibility, method)
|
23
|
-
end
|
24
|
-
|
25
|
-
def method_visibility(method) # :nodoc:
|
26
|
-
case
|
27
|
-
when private_method_defined?(method)
|
28
|
-
:private
|
29
|
-
when protected_method_defined?(method)
|
30
|
-
:protected
|
31
|
-
else
|
32
|
-
:public
|
33
|
-
end
|
15
|
+
singleton_class.remove_possible_method(method)
|
34
16
|
end
|
35
17
|
end
|