activesupport 4.2.11.1 → 5.2.4
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 +4 -4
- data/CHANGELOG.md +399 -440
- data/MIT-LICENSE +2 -2
- data/README.rdoc +4 -5
- data/lib/active_support/all.rb +5 -3
- data/lib/active_support/array_inquirer.rb +48 -0
- data/lib/active_support/backtrace_cleaner.rb +7 -5
- data/lib/active_support/benchmarkable.rb +6 -4
- data/lib/active_support/builder.rb +3 -1
- data/lib/active_support/cache/file_store.rb +41 -35
- data/lib/active_support/cache/mem_cache_store.rb +97 -88
- data/lib/active_support/cache/memory_store.rb +27 -30
- data/lib/active_support/cache/null_store.rb +7 -8
- data/lib/active_support/cache/redis_cache_store.rb +461 -0
- data/lib/active_support/cache/strategy/local_cache.rb +67 -34
- data/lib/active_support/cache/strategy/local_cache_middleware.rb +10 -9
- data/lib/active_support/cache.rb +287 -196
- data/lib/active_support/callbacks.rb +640 -590
- data/lib/active_support/concern.rb +11 -5
- data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +17 -0
- data/lib/active_support/concurrency/share_lock.rb +227 -0
- data/lib/active_support/configurable.rb +8 -5
- data/lib/active_support/core_ext/array/access.rb +29 -1
- data/lib/active_support/core_ext/array/conversions.rb +22 -18
- data/lib/active_support/core_ext/array/extract_options.rb +2 -0
- data/lib/active_support/core_ext/array/grouping.rb +11 -18
- data/lib/active_support/core_ext/array/inquiry.rb +19 -0
- data/lib/active_support/core_ext/array/prepend_and_append.rb +5 -3
- data/lib/active_support/core_ext/array/wrap.rb +7 -4
- data/lib/active_support/core_ext/array.rb +9 -6
- data/lib/active_support/core_ext/benchmark.rb +3 -1
- data/lib/active_support/core_ext/big_decimal/conversions.rb +10 -12
- data/lib/active_support/core_ext/big_decimal.rb +3 -1
- data/lib/active_support/core_ext/class/attribute.rb +41 -22
- data/lib/active_support/core_ext/class/attribute_accessors.rb +3 -1
- data/lib/active_support/core_ext/class/subclasses.rb +20 -6
- data/lib/active_support/core_ext/class.rb +4 -3
- data/lib/active_support/core_ext/date/acts_like.rb +3 -1
- data/lib/active_support/core_ext/date/blank.rb +14 -0
- data/lib/active_support/core_ext/date/calculations.rb +11 -9
- data/lib/active_support/core_ext/date/conversions.rb +25 -23
- data/lib/active_support/core_ext/date/zones.rb +4 -2
- data/lib/active_support/core_ext/date.rb +6 -4
- data/lib/active_support/core_ext/date_and_time/calculations.rb +170 -58
- data/lib/active_support/core_ext/date_and_time/compatibility.rb +4 -3
- data/lib/active_support/core_ext/date_and_time/zones.rb +12 -12
- data/lib/active_support/core_ext/date_time/acts_like.rb +4 -2
- data/lib/active_support/core_ext/date_time/blank.rb +14 -0
- data/lib/active_support/core_ext/date_time/calculations.rb +36 -18
- data/lib/active_support/core_ext/date_time/compatibility.rb +8 -6
- data/lib/active_support/core_ext/date_time/conversions.rb +16 -12
- 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 +101 -33
- data/lib/active_support/core_ext/file/atomic.rb +38 -31
- data/lib/active_support/core_ext/file.rb +3 -1
- data/lib/active_support/core_ext/hash/compact.rb +14 -9
- data/lib/active_support/core_ext/hash/conversions.rb +62 -41
- data/lib/active_support/core_ext/hash/deep_merge.rb +9 -13
- data/lib/active_support/core_ext/hash/except.rb +11 -8
- data/lib/active_support/core_ext/hash/indifferent_access.rb +4 -3
- data/lib/active_support/core_ext/hash/keys.rb +33 -27
- data/lib/active_support/core_ext/hash/reverse_merge.rb +5 -2
- data/lib/active_support/core_ext/hash/slice.rb +8 -8
- data/lib/active_support/core_ext/hash/transform_values.rb +14 -5
- data/lib/active_support/core_ext/hash.rb +11 -9
- data/lib/active_support/core_ext/integer/inflections.rb +3 -1
- data/lib/active_support/core_ext/integer/multiple.rb +2 -0
- 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/agnostics.rb +2 -0
- data/lib/active_support/core_ext/kernel/concern.rb +5 -1
- data/lib/active_support/core_ext/kernel/reporting.rb +4 -84
- data/lib/active_support/core_ext/kernel/singleton_class.rb +2 -0
- data/lib/active_support/core_ext/kernel.rb +6 -5
- data/lib/active_support/core_ext/load_error.rb +3 -22
- data/lib/active_support/core_ext/marshal.rb +8 -8
- data/lib/active_support/core_ext/module/aliasing.rb +6 -44
- data/lib/active_support/core_ext/module/anonymous.rb +12 -1
- data/lib/active_support/core_ext/module/attr_internal.rb +8 -9
- data/lib/active_support/core_ext/module/attribute_accessors.rb +43 -40
- data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +150 -0
- data/lib/active_support/core_ext/module/concerning.rb +11 -12
- data/lib/active_support/core_ext/module/delegation.rb +99 -29
- data/lib/active_support/core_ext/module/deprecation.rb +4 -2
- data/lib/active_support/core_ext/module/introspection.rb +9 -9
- data/lib/active_support/core_ext/module/reachable.rb +5 -2
- data/lib/active_support/core_ext/module/redefine_method.rb +49 -0
- data/lib/active_support/core_ext/module/remove_method.rb +8 -3
- data/lib/active_support/core_ext/module.rb +14 -11
- data/lib/active_support/core_ext/name_error.rb +22 -2
- data/lib/active_support/core_ext/numeric/bytes.rb +22 -0
- data/lib/active_support/core_ext/numeric/conversions.rb +78 -81
- data/lib/active_support/core_ext/numeric/inquiry.rb +28 -0
- data/lib/active_support/core_ext/numeric/time.rb +35 -23
- data/lib/active_support/core_ext/numeric.rb +6 -3
- data/lib/active_support/core_ext/object/acts_like.rb +12 -1
- data/lib/active_support/core_ext/object/blank.rb +27 -2
- data/lib/active_support/core_ext/object/conversions.rb +6 -4
- data/lib/active_support/core_ext/object/deep_dup.rb +13 -4
- data/lib/active_support/core_ext/object/duplicable.rb +41 -14
- data/lib/active_support/core_ext/object/inclusion.rb +5 -3
- data/lib/active_support/core_ext/object/instance_variables.rb +3 -1
- data/lib/active_support/core_ext/object/json.rb +49 -19
- 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 +69 -21
- data/lib/active_support/core_ext/object/with_options.rb +16 -3
- data/lib/active_support/core_ext/object.rb +14 -13
- data/lib/active_support/core_ext/range/compare_range.rb +61 -0
- data/lib/active_support/core_ext/range/conversions.rb +27 -7
- data/lib/active_support/core_ext/range/each.rb +19 -17
- data/lib/active_support/core_ext/range/include_range.rb +2 -22
- data/lib/active_support/core_ext/range/include_time_with_zone.rb +23 -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 +6 -0
- data/lib/active_support/core_ext/securerandom.rb +25 -0
- data/lib/active_support/core_ext/string/access.rb +8 -6
- data/lib/active_support/core_ext/string/behavior.rb +3 -1
- data/lib/active_support/core_ext/string/conversions.rb +7 -4
- data/lib/active_support/core_ext/string/exclude.rb +2 -0
- data/lib/active_support/core_ext/string/filters.rb +6 -5
- data/lib/active_support/core_ext/string/indent.rb +6 -4
- data/lib/active_support/core_ext/string/inflections.rb +61 -24
- data/lib/active_support/core_ext/string/inquiry.rb +3 -1
- data/lib/active_support/core_ext/string/multibyte.rb +15 -7
- data/lib/active_support/core_ext/string/output_safety.rb +34 -38
- data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -0
- data/lib/active_support/core_ext/string/strip.rb +4 -5
- 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/time/acts_like.rb +3 -1
- data/lib/active_support/core_ext/time/calculations.rb +85 -51
- data/lib/active_support/core_ext/time/compatibility.rb +4 -2
- data/lib/active_support/core_ext/time/conversions.rb +20 -13
- data/lib/active_support/core_ext/time/zones.rb +41 -7
- data/lib/active_support/core_ext/time.rb +7 -6
- data/lib/active_support/core_ext/uri.rb +6 -8
- data/lib/active_support/core_ext.rb +3 -1
- data/lib/active_support/current_attributes.rb +195 -0
- data/lib/active_support/dependencies/autoload.rb +2 -0
- data/lib/active_support/dependencies/interlock.rb +57 -0
- data/lib/active_support/dependencies.rb +152 -161
- data/lib/active_support/deprecation/behaviors.rb +44 -11
- data/lib/active_support/deprecation/constant_accessor.rb +52 -0
- data/lib/active_support/deprecation/instance_delegator.rb +17 -2
- data/lib/active_support/deprecation/method_wrappers.rb +66 -20
- data/lib/active_support/deprecation/proxy_wrappers.rb +56 -28
- data/lib/active_support/deprecation/reporting.rb +32 -12
- data/lib/active_support/deprecation.rb +12 -9
- data/lib/active_support/descendants_tracker.rb +2 -0
- data/lib/active_support/digest.rb +20 -0
- data/lib/active_support/duration/iso8601_parser.rb +125 -0
- data/lib/active_support/duration/iso8601_serializer.rb +55 -0
- data/lib/active_support/duration.rb +307 -35
- data/lib/active_support/encrypted_configuration.rb +49 -0
- data/lib/active_support/encrypted_file.rb +99 -0
- data/lib/active_support/evented_file_update_checker.rb +205 -0
- data/lib/active_support/execution_wrapper.rb +128 -0
- data/lib/active_support/executor.rb +8 -0
- data/lib/active_support/file_update_checker.rb +63 -37
- data/lib/active_support/gem_version.rb +6 -4
- data/lib/active_support/gzip.rb +7 -5
- data/lib/active_support/hash_with_indifferent_access.rb +123 -28
- data/lib/active_support/i18n.rb +8 -6
- data/lib/active_support/i18n_railtie.rb +37 -13
- data/lib/active_support/inflections.rb +13 -11
- data/lib/active_support/inflector/inflections.rb +61 -12
- data/lib/active_support/inflector/methods.rb +163 -136
- data/lib/active_support/inflector/transliterate.rb +48 -27
- data/lib/active_support/inflector.rb +7 -5
- data/lib/active_support/json/decoding.rb +16 -13
- data/lib/active_support/json/encoding.rb +11 -58
- data/lib/active_support/json.rb +4 -2
- data/lib/active_support/key_generator.rb +25 -25
- data/lib/active_support/lazy_load_hooks.rb +50 -20
- data/lib/active_support/locale/en.yml +2 -0
- data/lib/active_support/log_subscriber/test_helper.rb +14 -12
- data/lib/active_support/log_subscriber.rb +13 -10
- data/lib/active_support/logger.rb +8 -7
- data/lib/active_support/logger_silence.rb +6 -4
- data/lib/active_support/logger_thread_safe_level.rb +7 -5
- data/lib/active_support/message_encryptor.rb +168 -53
- data/lib/active_support/message_verifier.rb +150 -17
- data/lib/active_support/messages/metadata.rb +71 -0
- data/lib/active_support/messages/rotation_configuration.rb +22 -0
- data/lib/active_support/messages/rotator.rb +56 -0
- data/lib/active_support/multibyte/chars.rb +36 -23
- data/lib/active_support/multibyte/unicode.rb +100 -96
- data/lib/active_support/multibyte.rb +4 -2
- data/lib/active_support/notifications/fanout.rb +11 -9
- data/lib/active_support/notifications/instrumenter.rb +27 -7
- data/lib/active_support/notifications.rb +11 -7
- data/lib/active_support/number_helper/number_converter.rb +13 -11
- data/lib/active_support/number_helper/number_to_currency_converter.rb +9 -9
- data/lib/active_support/number_helper/number_to_delimited_converter.rb +9 -3
- data/lib/active_support/number_helper/number_to_human_converter.rb +11 -9
- data/lib/active_support/number_helper/number_to_human_size_converter.rb +9 -8
- data/lib/active_support/number_helper/number_to_percentage_converter.rb +3 -1
- data/lib/active_support/number_helper/number_to_phone_converter.rb +13 -4
- data/lib/active_support/number_helper/number_to_rounded_converter.rb +23 -56
- data/lib/active_support/number_helper/rounding_helper.rb +66 -0
- data/lib/active_support/number_helper.rb +94 -68
- data/lib/active_support/option_merger.rb +3 -1
- data/lib/active_support/ordered_hash.rb +6 -4
- data/lib/active_support/ordered_options.rb +23 -5
- data/lib/active_support/per_thread_registry.rb +9 -4
- data/lib/active_support/proxy_object.rb +2 -0
- data/lib/active_support/rails.rb +16 -8
- data/lib/active_support/railtie.rb +43 -9
- data/lib/active_support/reloader.rb +131 -0
- data/lib/active_support/rescuable.rb +108 -53
- data/lib/active_support/security_utils.rb +15 -11
- data/lib/active_support/string_inquirer.rb +11 -3
- data/lib/active_support/subscriber.rb +21 -16
- data/lib/active_support/tagged_logging.rb +14 -11
- data/lib/active_support/test_case.rb +19 -47
- data/lib/active_support/testing/assertions.rb +137 -20
- data/lib/active_support/testing/autorun.rb +4 -2
- 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 +14 -10
- data/lib/active_support/testing/file_fixtures.rb +36 -0
- data/lib/active_support/testing/isolation.rb +34 -25
- data/lib/active_support/testing/method_call_assertions.rb +43 -0
- data/lib/active_support/testing/setup_and_teardown.rb +13 -8
- data/lib/active_support/testing/stream.rb +44 -0
- data/lib/active_support/testing/tagged_logging.rb +3 -1
- data/lib/active_support/testing/time_helpers.rb +81 -15
- data/lib/active_support/time.rb +14 -12
- data/lib/active_support/time_with_zone.rb +169 -39
- data/lib/active_support/values/time_zone.rb +196 -61
- data/lib/active_support/values/unicode_tables.dat +0 -0
- data/lib/active_support/version.rb +3 -1
- data/lib/active_support/xml_mini/jdom.rb +116 -114
- data/lib/active_support/xml_mini/libxml.rb +16 -13
- data/lib/active_support/xml_mini/libxmlsax.rb +15 -14
- data/lib/active_support/xml_mini/nokogiri.rb +14 -12
- data/lib/active_support/xml_mini/nokogirisax.rb +14 -13
- data/lib/active_support/xml_mini/rexml.rb +11 -9
- data/lib/active_support/xml_mini.rb +37 -37
- data/lib/active_support.rb +12 -11
- metadata +54 -24
- data/lib/active_support/concurrency/latch.rb +0 -27
- data/lib/active_support/core_ext/big_decimal/yaml_conversions.rb +0 -16
- data/lib/active_support/core_ext/class/delegating_attributes.rb +0 -45
- data/lib/active_support/core_ext/date_time/zones.rb +0 -6
- data/lib/active_support/core_ext/kernel/debugger.rb +0 -10
- data/lib/active_support/core_ext/module/method_transplanting.rb +0 -13
- data/lib/active_support/core_ext/module/qualified_const.rb +0 -52
- data/lib/active_support/core_ext/object/itself.rb +0 -15
- data/lib/active_support/core_ext/struct.rb +0 -6
- data/lib/active_support/core_ext/thread.rb +0 -86
- data/lib/active_support/core_ext/time/marshal.rb +0 -30
@@ -1,12 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class Module
|
2
4
|
# Declares an attribute reader backed by an internally-named instance variable.
|
3
5
|
def attr_internal_reader(*attrs)
|
4
|
-
attrs.each {|attr_name| attr_internal_define(attr_name, :reader)}
|
6
|
+
attrs.each { |attr_name| attr_internal_define(attr_name, :reader) }
|
5
7
|
end
|
6
8
|
|
7
9
|
# Declares an attribute writer backed by an internally-named instance variable.
|
8
10
|
def attr_internal_writer(*attrs)
|
9
|
-
attrs.each {|attr_name| attr_internal_define(attr_name, :writer)}
|
11
|
+
attrs.each { |attr_name| attr_internal_define(attr_name, :writer) }
|
10
12
|
end
|
11
13
|
|
12
14
|
# Declares an attribute reader and writer backed by an internally-named instance
|
@@ -18,7 +20,7 @@ class Module
|
|
18
20
|
alias_method :attr_internal, :attr_internal_accessor
|
19
21
|
|
20
22
|
class << self; attr_accessor :attr_internal_naming_format end
|
21
|
-
self.attr_internal_naming_format =
|
23
|
+
self.attr_internal_naming_format = "@_%s"
|
22
24
|
|
23
25
|
private
|
24
26
|
def attr_internal_ivar_name(attr)
|
@@ -26,12 +28,9 @@ class Module
|
|
26
28
|
end
|
27
29
|
|
28
30
|
def attr_internal_define(attr_name, type)
|
29
|
-
internal_name = attr_internal_ivar_name(attr_name).sub(/\A@/,
|
30
|
-
#
|
31
|
-
|
32
|
-
# use native attr_* methods as they are faster on some Ruby implementations
|
33
|
-
send("attr_#{type}", internal_name)
|
34
|
-
end
|
31
|
+
internal_name = attr_internal_ivar_name(attr_name).sub(/\A@/, "")
|
32
|
+
# use native attr_* methods as they are faster on some Ruby implementations
|
33
|
+
send("attr_#{type}", internal_name)
|
35
34
|
attr_name, internal_name = "#{attr_name}=", "#{internal_name}=" if type == :writer
|
36
35
|
alias_method attr_name, internal_name
|
37
36
|
remove_method internal_name
|
@@ -1,12 +1,16 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/array/extract_options"
|
4
|
+
require "active_support/core_ext/regexp"
|
2
5
|
|
3
6
|
# Extends the module object with class/module and instance accessors for
|
4
7
|
# class/module attributes, just like the native attr* accessors for instance
|
5
8
|
# attributes.
|
6
9
|
class Module
|
7
10
|
# Defines a class attribute and creates a class and instance reader methods.
|
8
|
-
# The underlying
|
9
|
-
# defined.
|
11
|
+
# The underlying class variable is set to +nil+, if it is not previously
|
12
|
+
# defined. All class and instance methods created will be public, even if
|
13
|
+
# this method is called with a private or protected access modifier.
|
10
14
|
#
|
11
15
|
# module HairColors
|
12
16
|
# mattr_reader :hair_colors
|
@@ -19,15 +23,15 @@ class Module
|
|
19
23
|
# The attribute name must be a valid method name in Ruby.
|
20
24
|
#
|
21
25
|
# module Foo
|
22
|
-
# mattr_reader :"1_Badname
|
26
|
+
# mattr_reader :"1_Badname"
|
23
27
|
# end
|
24
|
-
# # => NameError: invalid attribute name
|
28
|
+
# # => NameError: invalid attribute name: 1_Badname
|
25
29
|
#
|
26
30
|
# If you want to opt out the creation on the instance reader method, pass
|
27
31
|
# <tt>instance_reader: false</tt> or <tt>instance_accessor: false</tt>.
|
28
32
|
#
|
29
33
|
# module HairColors
|
30
|
-
#
|
34
|
+
# mattr_reader :hair_colors, instance_reader: false
|
31
35
|
# end
|
32
36
|
#
|
33
37
|
# class Person
|
@@ -36,24 +40,20 @@ class Module
|
|
36
40
|
#
|
37
41
|
# Person.new.hair_colors # => NoMethodError
|
38
42
|
#
|
39
|
-
#
|
40
|
-
# Also, you can pass a block to set up the attribute with a default value.
|
43
|
+
# You can set a default value for the attribute.
|
41
44
|
#
|
42
45
|
# module HairColors
|
43
|
-
#
|
44
|
-
# [:brown, :black, :blonde, :red]
|
45
|
-
# end
|
46
|
+
# mattr_reader :hair_colors, default: [:brown, :black, :blonde, :red]
|
46
47
|
# end
|
47
48
|
#
|
48
49
|
# class Person
|
49
50
|
# include HairColors
|
50
51
|
# end
|
51
52
|
#
|
52
|
-
# Person.hair_colors # => [:brown, :black, :blonde, :red]
|
53
|
-
def mattr_reader(*syms)
|
54
|
-
options = syms.extract_options!
|
53
|
+
# Person.new.hair_colors # => [:brown, :black, :blonde, :red]
|
54
|
+
def mattr_reader(*syms, instance_reader: true, instance_accessor: true, default: nil)
|
55
55
|
syms.each do |sym|
|
56
|
-
raise NameError.new("invalid attribute name: #{sym}") unless
|
56
|
+
raise NameError.new("invalid attribute name: #{sym}") unless /\A[_A-Za-z]\w*\z/.match?(sym)
|
57
57
|
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
58
58
|
@@#{sym} = nil unless defined? @@#{sym}
|
59
59
|
|
@@ -62,20 +62,24 @@ class Module
|
|
62
62
|
end
|
63
63
|
EOS
|
64
64
|
|
65
|
-
|
65
|
+
if instance_reader && instance_accessor
|
66
66
|
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
67
67
|
def #{sym}
|
68
68
|
@@#{sym}
|
69
69
|
end
|
70
70
|
EOS
|
71
71
|
end
|
72
|
-
|
72
|
+
|
73
|
+
sym_default_value = (block_given? && default.nil?) ? yield : default
|
74
|
+
class_variable_set("@@#{sym}", sym_default_value) unless sym_default_value.nil?
|
73
75
|
end
|
74
76
|
end
|
75
77
|
alias :cattr_reader :mattr_reader
|
76
78
|
|
77
79
|
# Defines a class attribute and creates a class and instance writer methods to
|
78
|
-
# allow assignment to the attribute.
|
80
|
+
# allow assignment to the attribute. All class and instance methods created
|
81
|
+
# will be public, even if this method is called with a private or protected
|
82
|
+
# access modifier.
|
79
83
|
#
|
80
84
|
# module HairColors
|
81
85
|
# mattr_writer :hair_colors
|
@@ -103,12 +107,10 @@ class Module
|
|
103
107
|
#
|
104
108
|
# Person.new.hair_colors = [:blonde, :red] # => NoMethodError
|
105
109
|
#
|
106
|
-
#
|
110
|
+
# You can set a default value for the attribute.
|
107
111
|
#
|
108
|
-
#
|
109
|
-
# mattr_writer :hair_colors
|
110
|
-
# [:brown, :black, :blonde, :red]
|
111
|
-
# end
|
112
|
+
# module HairColors
|
113
|
+
# mattr_writer :hair_colors, default: [:brown, :black, :blonde, :red]
|
112
114
|
# end
|
113
115
|
#
|
114
116
|
# class Person
|
@@ -116,10 +118,9 @@ class Module
|
|
116
118
|
# end
|
117
119
|
#
|
118
120
|
# Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red]
|
119
|
-
def mattr_writer(*syms)
|
120
|
-
options = syms.extract_options!
|
121
|
+
def mattr_writer(*syms, instance_writer: true, instance_accessor: true, default: nil)
|
121
122
|
syms.each do |sym|
|
122
|
-
raise NameError.new("invalid attribute name: #{sym}") unless
|
123
|
+
raise NameError.new("invalid attribute name: #{sym}") unless /\A[_A-Za-z]\w*\z/.match?(sym)
|
123
124
|
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
124
125
|
@@#{sym} = nil unless defined? @@#{sym}
|
125
126
|
|
@@ -128,19 +129,23 @@ class Module
|
|
128
129
|
end
|
129
130
|
EOS
|
130
131
|
|
131
|
-
|
132
|
+
if instance_writer && instance_accessor
|
132
133
|
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
133
134
|
def #{sym}=(obj)
|
134
135
|
@@#{sym} = obj
|
135
136
|
end
|
136
137
|
EOS
|
137
138
|
end
|
138
|
-
|
139
|
+
|
140
|
+
sym_default_value = (block_given? && default.nil?) ? yield : default
|
141
|
+
send("#{sym}=", sym_default_value) unless sym_default_value.nil?
|
139
142
|
end
|
140
143
|
end
|
141
144
|
alias :cattr_writer :mattr_writer
|
142
145
|
|
143
146
|
# Defines both class and instance accessors for class attributes.
|
147
|
+
# All class and instance methods created will be public, even if
|
148
|
+
# this method is called with a private or protected access modifier.
|
144
149
|
#
|
145
150
|
# module HairColors
|
146
151
|
# mattr_accessor :hair_colors
|
@@ -150,8 +155,8 @@ class Module
|
|
150
155
|
# include HairColors
|
151
156
|
# end
|
152
157
|
#
|
153
|
-
#
|
154
|
-
#
|
158
|
+
# HairColors.hair_colors = [:brown, :black, :blonde, :red]
|
159
|
+
# HairColors.hair_colors # => [:brown, :black, :blonde, :red]
|
155
160
|
# Person.new.hair_colors # => [:brown, :black, :blonde, :red]
|
156
161
|
#
|
157
162
|
# If a subclass changes the value then that would also change the value for
|
@@ -161,8 +166,8 @@ class Module
|
|
161
166
|
# class Male < Person
|
162
167
|
# end
|
163
168
|
#
|
164
|
-
# Male.hair_colors << :blue
|
165
|
-
# Person.hair_colors # => [:brown, :black, :blonde, :red, :blue]
|
169
|
+
# Male.new.hair_colors << :blue
|
170
|
+
# Person.new.hair_colors # => [:brown, :black, :blonde, :red, :blue]
|
166
171
|
#
|
167
172
|
# To opt out of the instance writer method, pass <tt>instance_writer: false</tt>.
|
168
173
|
# To opt out of the instance reader method, pass <tt>instance_reader: false</tt>.
|
@@ -191,22 +196,20 @@ class Module
|
|
191
196
|
# Person.new.hair_colors = [:brown] # => NoMethodError
|
192
197
|
# Person.new.hair_colors # => NoMethodError
|
193
198
|
#
|
194
|
-
#
|
199
|
+
# You can set a default value for the attribute.
|
195
200
|
#
|
196
201
|
# module HairColors
|
197
|
-
# mattr_accessor :hair_colors
|
198
|
-
# [:brown, :black, :blonde, :red]
|
199
|
-
# end
|
202
|
+
# mattr_accessor :hair_colors, default: [:brown, :black, :blonde, :red]
|
200
203
|
# end
|
201
204
|
#
|
202
205
|
# class Person
|
203
206
|
# include HairColors
|
204
207
|
# end
|
205
208
|
#
|
206
|
-
# Person.class_variable_get("@@hair_colors")
|
207
|
-
def mattr_accessor(*syms, &blk)
|
208
|
-
mattr_reader(*syms, &blk)
|
209
|
-
mattr_writer(*syms,
|
209
|
+
# Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red]
|
210
|
+
def mattr_accessor(*syms, instance_reader: true, instance_writer: true, instance_accessor: true, default: nil, &blk)
|
211
|
+
mattr_reader(*syms, instance_reader: instance_reader, instance_accessor: instance_accessor, default: default, &blk)
|
212
|
+
mattr_writer(*syms, instance_writer: instance_writer, instance_accessor: instance_accessor, default: default)
|
210
213
|
end
|
211
214
|
alias :cattr_accessor :mattr_accessor
|
212
215
|
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/array/extract_options"
|
4
|
+
require "active_support/core_ext/regexp"
|
5
|
+
|
6
|
+
# Extends the module object with class/module and instance accessors for
|
7
|
+
# class/module attributes, just like the native attr* accessors for instance
|
8
|
+
# attributes, but does so on a per-thread basis.
|
9
|
+
#
|
10
|
+
# So the values are scoped within the Thread.current space under the class name
|
11
|
+
# of the module.
|
12
|
+
class Module
|
13
|
+
# Defines a per-thread class attribute and creates class and instance reader methods.
|
14
|
+
# The underlying per-thread class variable is set to +nil+, if it is not previously defined.
|
15
|
+
#
|
16
|
+
# module Current
|
17
|
+
# thread_mattr_reader :user
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# Current.user # => nil
|
21
|
+
# Thread.current[:attr_Current_user] = "DHH"
|
22
|
+
# Current.user # => "DHH"
|
23
|
+
#
|
24
|
+
# The attribute name must be a valid method name in Ruby.
|
25
|
+
#
|
26
|
+
# module Foo
|
27
|
+
# thread_mattr_reader :"1_Badname"
|
28
|
+
# end
|
29
|
+
# # => NameError: invalid attribute name: 1_Badname
|
30
|
+
#
|
31
|
+
# If you want to opt out of the creation of the instance reader method, pass
|
32
|
+
# <tt>instance_reader: false</tt> or <tt>instance_accessor: false</tt>.
|
33
|
+
#
|
34
|
+
# class Current
|
35
|
+
# thread_mattr_reader :user, instance_reader: false
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# Current.new.user # => NoMethodError
|
39
|
+
def thread_mattr_reader(*syms) # :nodoc:
|
40
|
+
options = syms.extract_options!
|
41
|
+
|
42
|
+
syms.each do |sym|
|
43
|
+
raise NameError.new("invalid attribute name: #{sym}") unless /^[_A-Za-z]\w*$/.match?(sym)
|
44
|
+
|
45
|
+
# The following generated method concatenates `name` because we want it
|
46
|
+
# to work with inheritance via polymorphism.
|
47
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
48
|
+
def self.#{sym}
|
49
|
+
Thread.current["attr_" + name + "_#{sym}"]
|
50
|
+
end
|
51
|
+
EOS
|
52
|
+
|
53
|
+
unless options[:instance_reader] == false || options[:instance_accessor] == false
|
54
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
55
|
+
def #{sym}
|
56
|
+
self.class.#{sym}
|
57
|
+
end
|
58
|
+
EOS
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
alias :thread_cattr_reader :thread_mattr_reader
|
63
|
+
|
64
|
+
# Defines a per-thread class attribute and creates a class and instance writer methods to
|
65
|
+
# allow assignment to the attribute.
|
66
|
+
#
|
67
|
+
# module Current
|
68
|
+
# thread_mattr_writer :user
|
69
|
+
# end
|
70
|
+
#
|
71
|
+
# Current.user = "DHH"
|
72
|
+
# Thread.current[:attr_Current_user] # => "DHH"
|
73
|
+
#
|
74
|
+
# If you want to opt out of the creation of the instance writer method, pass
|
75
|
+
# <tt>instance_writer: false</tt> or <tt>instance_accessor: false</tt>.
|
76
|
+
#
|
77
|
+
# class Current
|
78
|
+
# thread_mattr_writer :user, instance_writer: false
|
79
|
+
# end
|
80
|
+
#
|
81
|
+
# Current.new.user = "DHH" # => NoMethodError
|
82
|
+
def thread_mattr_writer(*syms) # :nodoc:
|
83
|
+
options = syms.extract_options!
|
84
|
+
syms.each do |sym|
|
85
|
+
raise NameError.new("invalid attribute name: #{sym}") unless /^[_A-Za-z]\w*$/.match?(sym)
|
86
|
+
|
87
|
+
# The following generated method concatenates `name` because we want it
|
88
|
+
# to work with inheritance via polymorphism.
|
89
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
90
|
+
def self.#{sym}=(obj)
|
91
|
+
Thread.current["attr_" + name + "_#{sym}"] = obj
|
92
|
+
end
|
93
|
+
EOS
|
94
|
+
|
95
|
+
unless options[:instance_writer] == false || options[:instance_accessor] == false
|
96
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
97
|
+
def #{sym}=(obj)
|
98
|
+
self.class.#{sym} = obj
|
99
|
+
end
|
100
|
+
EOS
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
alias :thread_cattr_writer :thread_mattr_writer
|
105
|
+
|
106
|
+
# Defines both class and instance accessors for class attributes.
|
107
|
+
#
|
108
|
+
# class Account
|
109
|
+
# thread_mattr_accessor :user
|
110
|
+
# end
|
111
|
+
#
|
112
|
+
# Account.user = "DHH"
|
113
|
+
# Account.user # => "DHH"
|
114
|
+
# Account.new.user # => "DHH"
|
115
|
+
#
|
116
|
+
# If a subclass changes the value, the parent class' value is not changed.
|
117
|
+
# Similarly, if the parent class changes the value, the value of subclasses
|
118
|
+
# is not changed.
|
119
|
+
#
|
120
|
+
# class Customer < Account
|
121
|
+
# end
|
122
|
+
#
|
123
|
+
# Customer.user = "Rafael"
|
124
|
+
# Customer.user # => "Rafael"
|
125
|
+
# Account.user # => "DHH"
|
126
|
+
#
|
127
|
+
# To opt out of the instance writer method, pass <tt>instance_writer: false</tt>.
|
128
|
+
# To opt out of the instance reader method, pass <tt>instance_reader: false</tt>.
|
129
|
+
#
|
130
|
+
# class Current
|
131
|
+
# thread_mattr_accessor :user, instance_writer: false, instance_reader: false
|
132
|
+
# end
|
133
|
+
#
|
134
|
+
# Current.new.user = "DHH" # => NoMethodError
|
135
|
+
# Current.new.user # => NoMethodError
|
136
|
+
#
|
137
|
+
# Or pass <tt>instance_accessor: false</tt>, to opt out both instance methods.
|
138
|
+
#
|
139
|
+
# class Current
|
140
|
+
# mattr_accessor :user, instance_accessor: false
|
141
|
+
# end
|
142
|
+
#
|
143
|
+
# Current.new.user = "DHH" # => NoMethodError
|
144
|
+
# Current.new.user # => NoMethodError
|
145
|
+
def thread_mattr_accessor(*syms)
|
146
|
+
thread_mattr_reader(*syms)
|
147
|
+
thread_mattr_writer(*syms)
|
148
|
+
end
|
149
|
+
alias :thread_cattr_accessor :thread_mattr_accessor
|
150
|
+
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
|
@@ -63,12 +63,12 @@ class Module
|
|
63
63
|
#
|
64
64
|
# == Mix-in noise exiled to its own file:
|
65
65
|
#
|
66
|
-
# Once our chunk of behavior starts pushing the scroll-to-understand
|
66
|
+
# Once our chunk of behavior starts pushing the scroll-to-understand-it
|
67
67
|
# boundary, we give in and move it to a separate file. At this size, the
|
68
|
-
# overhead
|
69
|
-
#
|
68
|
+
# increased overhead can be a reasonable tradeoff even if it reduces our
|
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,7 +98,7 @@ 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,
|
@@ -1,14 +1,19 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "set"
|
4
|
+
require "active_support/core_ext/regexp"
|
2
5
|
|
3
6
|
class Module
|
4
7
|
# Error generated by +delegate+ when a method is called on +nil+ and +allow_nil+
|
5
8
|
# option is not used.
|
6
9
|
class DelegationError < NoMethodError; end
|
7
10
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
11
|
+
RUBY_RESERVED_KEYWORDS = %w(alias and BEGIN begin break case class def defined? do
|
12
|
+
else elsif END end ensure false for if in module next nil not or redo rescue retry
|
13
|
+
return self super then true undef unless until when while yield)
|
14
|
+
DELEGATION_RESERVED_KEYWORDS = %w(_ arg args block)
|
15
|
+
DELEGATION_RESERVED_METHOD_NAMES = Set.new(
|
16
|
+
RUBY_RESERVED_KEYWORDS + DELEGATION_RESERVED_KEYWORDS
|
12
17
|
).freeze
|
13
18
|
|
14
19
|
# Provides a +delegate+ class method to easily expose contained objects'
|
@@ -17,7 +22,8 @@ class Module
|
|
17
22
|
# ==== Options
|
18
23
|
# * <tt>:to</tt> - Specifies the target object
|
19
24
|
# * <tt>:prefix</tt> - Prefixes the new method with the target name or a custom prefix
|
20
|
-
# * <tt>:allow_nil</tt> - if set to true, prevents a +
|
25
|
+
# * <tt>:allow_nil</tt> - if set to true, prevents a +Module::DelegationError+
|
26
|
+
# from being raised
|
21
27
|
#
|
22
28
|
# The macro receives one or more method names (specified as symbols or
|
23
29
|
# strings) and the name of the target object via the <tt>:to</tt> option
|
@@ -109,18 +115,16 @@ class Module
|
|
109
115
|
# invoice.customer_address # => 'Vimmersvej 13'
|
110
116
|
#
|
111
117
|
# If the target is +nil+ and does not respond to the delegated method a
|
112
|
-
# +
|
113
|
-
#
|
114
|
-
# <tt>:allow_nil</tt> option: If the target is not +nil+, or it is and
|
115
|
-
# responds to the method, everything works as usual. But if it is +nil+ and
|
116
|
-
# does not respond to the delegated method, +nil+ is returned.
|
118
|
+
# +Module::DelegationError+ is raised. If you wish to instead return +nil+,
|
119
|
+
# use the <tt>:allow_nil</tt> option.
|
117
120
|
#
|
118
121
|
# class User < ActiveRecord::Base
|
119
122
|
# has_one :profile
|
120
123
|
# delegate :age, to: :profile
|
121
124
|
# end
|
122
125
|
#
|
123
|
-
# User.new.age
|
126
|
+
# User.new.age
|
127
|
+
# # => Module::DelegationError: User#age delegated to profile.age, but profile is nil
|
124
128
|
#
|
125
129
|
# But if not having a profile yet is fine and should not be an error
|
126
130
|
# condition:
|
@@ -147,36 +151,32 @@ class Module
|
|
147
151
|
# Foo.new("Bar").name # raises NoMethodError: undefined method `name'
|
148
152
|
#
|
149
153
|
# The target method must be public, otherwise it will raise +NoMethodError+.
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
unless options.is_a?(Hash) && to = options[:to]
|
154
|
-
raise ArgumentError, 'Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, to: :greeter).'
|
154
|
+
def delegate(*methods, to: nil, prefix: nil, allow_nil: nil)
|
155
|
+
unless to
|
156
|
+
raise ArgumentError, "Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, to: :greeter)."
|
155
157
|
end
|
156
158
|
|
157
|
-
prefix
|
158
|
-
|
159
|
-
if prefix == true && to =~ /^[^a-z_]/
|
160
|
-
raise ArgumentError, 'Can only automatically set the delegation prefix when delegating to a method.'
|
159
|
+
if prefix == true && /^[^a-z_]/.match?(to)
|
160
|
+
raise ArgumentError, "Can only automatically set the delegation prefix when delegating to a method."
|
161
161
|
end
|
162
162
|
|
163
163
|
method_prefix = \
|
164
164
|
if prefix
|
165
165
|
"#{prefix == true ? to : prefix}_"
|
166
166
|
else
|
167
|
-
|
167
|
+
""
|
168
168
|
end
|
169
169
|
|
170
|
-
|
171
|
-
line =
|
170
|
+
location = caller_locations(1, 1).first
|
171
|
+
file, line = location.path, location.lineno
|
172
172
|
|
173
173
|
to = to.to_s
|
174
|
-
to = "self.#{to}" if
|
174
|
+
to = "self.#{to}" if DELEGATION_RESERVED_METHOD_NAMES.include?(to)
|
175
175
|
|
176
|
-
methods.
|
176
|
+
methods.map do |method|
|
177
177
|
# Attribute writer methods only accept one argument. Makes sure []=
|
178
178
|
# methods still accept two arguments.
|
179
|
-
definition =
|
179
|
+
definition = /[^\]]=$/.match?(method) ? "arg" : "*args, &block"
|
180
180
|
|
181
181
|
# The following generated method calls the target exactly once, storing
|
182
182
|
# the returned value in a dummy variable.
|
@@ -193,7 +193,7 @@ class Module
|
|
193
193
|
" _.#{method}(#{definition})",
|
194
194
|
"end",
|
195
195
|
"end"
|
196
|
-
].join
|
196
|
+
].join ";"
|
197
197
|
else
|
198
198
|
exception = %(raise DelegationError, "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}")
|
199
199
|
|
@@ -208,10 +208,80 @@ class Module
|
|
208
208
|
" raise",
|
209
209
|
" end",
|
210
210
|
"end"
|
211
|
-
].join
|
211
|
+
].join ";"
|
212
212
|
end
|
213
213
|
|
214
214
|
module_eval(method_def, file, line)
|
215
215
|
end
|
216
216
|
end
|
217
|
+
|
218
|
+
# When building decorators, a common pattern may emerge:
|
219
|
+
#
|
220
|
+
# class Partition
|
221
|
+
# def initialize(event)
|
222
|
+
# @event = event
|
223
|
+
# end
|
224
|
+
#
|
225
|
+
# def person
|
226
|
+
# @event.detail.person || @event.creator
|
227
|
+
# end
|
228
|
+
#
|
229
|
+
# private
|
230
|
+
# def respond_to_missing?(name, include_private = false)
|
231
|
+
# @event.respond_to?(name, include_private)
|
232
|
+
# end
|
233
|
+
#
|
234
|
+
# def method_missing(method, *args, &block)
|
235
|
+
# @event.send(method, *args, &block)
|
236
|
+
# end
|
237
|
+
# end
|
238
|
+
#
|
239
|
+
# With <tt>Module#delegate_missing_to</tt>, the above is condensed to:
|
240
|
+
#
|
241
|
+
# class Partition
|
242
|
+
# delegate_missing_to :@event
|
243
|
+
#
|
244
|
+
# def initialize(event)
|
245
|
+
# @event = event
|
246
|
+
# end
|
247
|
+
#
|
248
|
+
# def person
|
249
|
+
# @event.detail.person || @event.creator
|
250
|
+
# end
|
251
|
+
# end
|
252
|
+
#
|
253
|
+
# The target can be anything callable within the object, e.g. instance
|
254
|
+
# variables, methods, constants, etc.
|
255
|
+
#
|
256
|
+
# The delegated method must be public on the target, otherwise it will
|
257
|
+
# raise +NoMethodError+.
|
258
|
+
def delegate_missing_to(target)
|
259
|
+
target = target.to_s
|
260
|
+
target = "self.#{target}" if DELEGATION_RESERVED_METHOD_NAMES.include?(target)
|
261
|
+
|
262
|
+
module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
263
|
+
def respond_to_missing?(name, include_private = false)
|
264
|
+
# It may look like an oversight, but we deliberately do not pass
|
265
|
+
# +include_private+, because they do not get delegated.
|
266
|
+
|
267
|
+
#{target}.respond_to?(name) || super
|
268
|
+
end
|
269
|
+
|
270
|
+
def method_missing(method, *args, &block)
|
271
|
+
if #{target}.respond_to?(method)
|
272
|
+
#{target}.public_send(method, *args, &block)
|
273
|
+
else
|
274
|
+
begin
|
275
|
+
super
|
276
|
+
rescue NoMethodError
|
277
|
+
if #{target}.nil?
|
278
|
+
raise DelegationError, "\#{method} delegated to #{target}, but #{target} is nil"
|
279
|
+
else
|
280
|
+
raise
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
285
|
+
RUBY
|
286
|
+
end
|
217
287
|
end
|