activesupport 1.2.4 → 8.1.2
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 +7 -0
- data/CHANGELOG.md +505 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +40 -0
- data/lib/active_support/actionable_error.rb +50 -0
- data/lib/active_support/all.rb +5 -0
- data/lib/active_support/array_inquirer.rb +50 -0
- data/lib/active_support/backtrace_cleaner.rb +234 -0
- data/lib/active_support/benchmark.rb +21 -0
- data/lib/active_support/benchmarkable.rb +53 -0
- data/lib/active_support/broadcast_logger.rb +238 -0
- data/lib/active_support/builder.rb +8 -0
- data/lib/active_support/cache/coder.rb +153 -0
- data/lib/active_support/cache/entry.rb +134 -0
- data/lib/active_support/cache/file_store.rb +244 -0
- data/lib/active_support/cache/mem_cache_store.rb +288 -0
- data/lib/active_support/cache/memory_store.rb +264 -0
- data/lib/active_support/cache/null_store.rb +62 -0
- data/lib/active_support/cache/redis_cache_store.rb +498 -0
- data/lib/active_support/cache/serializer_with_fallback.rb +152 -0
- data/lib/active_support/cache/strategy/local_cache.rb +246 -0
- data/lib/active_support/cache/strategy/local_cache_middleware.rb +45 -0
- data/lib/active_support/cache.rb +1170 -0
- data/lib/active_support/callbacks.rb +960 -0
- data/lib/active_support/class_attribute.rb +33 -0
- data/lib/active_support/code_generator.rb +79 -0
- data/lib/active_support/concern.rb +217 -0
- data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +18 -0
- data/lib/active_support/concurrency/null_lock.rb +13 -0
- data/lib/active_support/concurrency/share_lock.rb +225 -0
- data/lib/active_support/concurrency/thread_monitor.rb +55 -0
- data/lib/active_support/configurable.rb +193 -0
- data/lib/active_support/configuration_file.rb +60 -0
- data/lib/active_support/continuous_integration.rb +145 -0
- data/lib/active_support/core_ext/array/access.rb +100 -0
- data/lib/active_support/core_ext/array/conversions.rb +209 -26
- data/lib/active_support/core_ext/array/extract.rb +21 -0
- data/lib/active_support/core_ext/array/extract_options.rb +31 -0
- data/lib/active_support/core_ext/array/grouping.rb +109 -0
- data/lib/active_support/core_ext/array/inquiry.rb +19 -0
- data/lib/active_support/core_ext/array/wrap.rb +48 -0
- data/lib/active_support/core_ext/array.rb +8 -4
- data/lib/active_support/core_ext/benchmark.rb +6 -0
- data/lib/active_support/core_ext/big_decimal/conversions.rb +14 -0
- data/lib/active_support/core_ext/big_decimal.rb +3 -0
- data/lib/active_support/core_ext/class/attribute.rb +137 -0
- data/lib/active_support/core_ext/class/attribute_accessors.rb +6 -0
- data/lib/active_support/core_ext/class/subclasses.rb +24 -0
- data/lib/active_support/core_ext/class.rb +4 -0
- data/lib/active_support/core_ext/date/acts_like.rb +10 -0
- data/lib/active_support/core_ext/date/blank.rb +18 -0
- data/lib/active_support/core_ext/date/calculations.rb +161 -0
- data/lib/active_support/core_ext/date/conversions.rb +95 -28
- data/lib/active_support/core_ext/date/zones.rb +8 -0
- data/lib/active_support/core_ext/date.rb +6 -5
- data/lib/active_support/core_ext/date_and_time/calculations.rb +374 -0
- data/lib/active_support/core_ext/date_and_time/compatibility.rb +23 -0
- data/lib/active_support/core_ext/date_and_time/zones.rb +40 -0
- data/lib/active_support/core_ext/date_time/acts_like.rb +16 -0
- data/lib/active_support/core_ext/date_time/blank.rb +18 -0
- data/lib/active_support/core_ext/date_time/calculations.rb +215 -0
- data/lib/active_support/core_ext/date_time/compatibility.rb +16 -0
- data/lib/active_support/core_ext/date_time/conversions.rb +108 -0
- data/lib/active_support/core_ext/date_time.rb +7 -0
- data/lib/active_support/core_ext/digest/uuid.rb +76 -0
- data/lib/active_support/core_ext/digest.rb +3 -0
- data/lib/active_support/core_ext/enumerable.rb +277 -7
- data/lib/active_support/core_ext/erb/util.rb +201 -0
- data/lib/active_support/core_ext/file/atomic.rb +72 -0
- data/lib/active_support/core_ext/file.rb +3 -0
- data/lib/active_support/core_ext/hash/conversions.rb +262 -0
- data/lib/active_support/core_ext/hash/deep_merge.rb +43 -0
- data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
- data/lib/active_support/core_ext/hash/except.rb +12 -0
- data/lib/active_support/core_ext/hash/indifferent_access.rb +19 -55
- data/lib/active_support/core_ext/hash/keys.rb +134 -44
- data/lib/active_support/core_ext/hash/reverse_merge.rb +22 -22
- data/lib/active_support/core_ext/hash/slice.rb +27 -0
- data/lib/active_support/core_ext/hash.rb +9 -8
- data/lib/active_support/core_ext/integer/inflections.rb +29 -13
- data/lib/active_support/core_ext/integer/multiple.rb +12 -0
- data/lib/active_support/core_ext/integer/time.rb +22 -0
- data/lib/active_support/core_ext/integer.rb +4 -6
- data/lib/active_support/core_ext/kernel/concern.rb +14 -0
- data/lib/active_support/core_ext/kernel/reporting.rb +45 -0
- data/lib/active_support/core_ext/kernel/singleton_class.rb +8 -0
- data/lib/active_support/core_ext/kernel.rb +4 -78
- data/lib/active_support/core_ext/load_error.rb +6 -35
- data/lib/active_support/core_ext/module/aliasing.rb +31 -0
- data/lib/active_support/core_ext/module/anonymous.rb +30 -0
- data/lib/active_support/core_ext/module/attr_internal.rb +48 -0
- data/lib/active_support/core_ext/module/attribute_accessors.rb +214 -0
- data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +175 -0
- data/lib/active_support/core_ext/module/concerning.rb +140 -0
- data/lib/active_support/core_ext/module/delegation.rb +225 -0
- data/lib/active_support/core_ext/module/deprecation.rb +25 -0
- data/lib/active_support/core_ext/module/introspection.rb +65 -0
- data/lib/active_support/core_ext/module/redefine_method.rb +40 -0
- data/lib/active_support/core_ext/module/remove_method.rb +17 -0
- data/lib/active_support/core_ext/module.rb +13 -0
- data/lib/active_support/core_ext/name_error.rb +59 -0
- data/lib/active_support/core_ext/numeric/bytes.rb +73 -42
- data/lib/active_support/core_ext/numeric/conversions.rb +145 -0
- data/lib/active_support/core_ext/numeric/time.rb +64 -57
- data/lib/active_support/core_ext/numeric.rb +4 -6
- data/lib/active_support/core_ext/object/acts_like.rb +45 -0
- data/lib/active_support/core_ext/object/blank.rb +199 -0
- data/lib/active_support/core_ext/object/conversions.rb +6 -0
- data/lib/active_support/core_ext/object/deep_dup.rb +71 -0
- data/lib/active_support/core_ext/object/duplicable.rb +69 -0
- data/lib/active_support/core_ext/object/inclusion.rb +37 -0
- data/lib/active_support/core_ext/object/instance_variables.rb +32 -0
- data/lib/active_support/core_ext/object/json.rb +267 -0
- data/lib/active_support/core_ext/object/to_param.rb +3 -0
- data/lib/active_support/core_ext/object/to_query.rb +93 -0
- data/lib/active_support/core_ext/object/try.rb +158 -0
- data/lib/active_support/core_ext/object/with.rb +46 -0
- data/lib/active_support/core_ext/object/with_options.rb +101 -0
- data/lib/active_support/core_ext/object.rb +17 -0
- data/lib/active_support/core_ext/pathname/blank.rb +20 -0
- data/lib/active_support/core_ext/pathname/existence.rb +23 -0
- data/lib/active_support/core_ext/pathname.rb +4 -0
- data/lib/active_support/core_ext/range/compare_range.rb +57 -0
- data/lib/active_support/core_ext/range/conversions.rb +58 -17
- data/lib/active_support/core_ext/range/overlap.rb +40 -0
- data/lib/active_support/core_ext/range/sole.rb +17 -0
- data/lib/active_support/core_ext/range.rb +5 -4
- data/lib/active_support/core_ext/regexp.rb +14 -0
- data/lib/active_support/core_ext/securerandom.rb +57 -0
- data/lib/active_support/core_ext/string/access.rb +93 -56
- data/lib/active_support/core_ext/string/behavior.rb +8 -0
- data/lib/active_support/core_ext/string/conversions.rb +57 -16
- data/lib/active_support/core_ext/string/exclude.rb +13 -0
- data/lib/active_support/core_ext/string/filters.rb +151 -0
- data/lib/active_support/core_ext/string/indent.rb +45 -0
- data/lib/active_support/core_ext/string/inflections.rb +297 -54
- data/lib/active_support/core_ext/string/inquiry.rb +16 -0
- data/lib/active_support/core_ext/string/multibyte.rb +67 -0
- data/lib/active_support/core_ext/string/output_safety.rb +235 -0
- data/lib/active_support/core_ext/string/starts_ends_with.rb +4 -18
- data/lib/active_support/core_ext/string/strip.rb +27 -0
- data/lib/active_support/core_ext/string/zones.rb +16 -0
- data/lib/active_support/core_ext/string.rb +14 -10
- data/lib/active_support/core_ext/symbol/starts_ends_with.rb +6 -0
- data/lib/active_support/core_ext/symbol.rb +3 -0
- data/lib/active_support/core_ext/thread/backtrace/location.rb +7 -0
- data/lib/active_support/core_ext/time/acts_like.rb +10 -0
- data/lib/active_support/core_ext/time/calculations.rb +358 -153
- data/lib/active_support/core_ext/time/compatibility.rb +15 -0
- data/lib/active_support/core_ext/time/conversions.rb +69 -30
- data/lib/active_support/core_ext/time/zones.rb +97 -0
- data/lib/active_support/core_ext/time.rb +6 -6
- data/lib/active_support/core_ext.rb +5 -1
- data/lib/active_support/current_attributes/test_helper.rb +13 -0
- data/lib/active_support/current_attributes.rb +243 -0
- data/lib/active_support/deep_mergeable.rb +53 -0
- data/lib/active_support/delegation.rb +183 -0
- data/lib/active_support/dependencies/autoload.rb +72 -0
- data/lib/active_support/dependencies/interlock.rb +55 -0
- data/lib/active_support/dependencies/require_dependency.rb +28 -0
- data/lib/active_support/dependencies.rb +84 -222
- data/lib/active_support/deprecation/behaviors.rb +148 -0
- data/lib/active_support/deprecation/constant_accessor.rb +74 -0
- data/lib/active_support/deprecation/deprecators.rb +104 -0
- data/lib/active_support/deprecation/disallowed.rb +54 -0
- data/lib/active_support/deprecation/method_wrappers.rb +68 -0
- data/lib/active_support/deprecation/proxy_wrappers.rb +189 -0
- data/lib/active_support/deprecation/reporting.rb +162 -0
- data/lib/active_support/deprecation.rb +81 -0
- data/lib/active_support/deprecator.rb +7 -0
- data/lib/active_support/descendants_tracker.rb +112 -0
- data/lib/active_support/digest.rb +22 -0
- data/lib/active_support/duration/iso8601_parser.rb +123 -0
- data/lib/active_support/duration/iso8601_serializer.rb +64 -0
- data/lib/active_support/duration.rb +524 -0
- data/lib/active_support/editor.rb +70 -0
- data/lib/active_support/encrypted_configuration.rb +126 -0
- data/lib/active_support/encrypted_file.rb +133 -0
- data/lib/active_support/environment_inquirer.rb +40 -0
- data/lib/active_support/error_reporter/test_helper.rb +15 -0
- data/lib/active_support/error_reporter.rb +318 -0
- data/lib/active_support/event_reporter/test_helper.rb +32 -0
- data/lib/active_support/event_reporter.rb +592 -0
- data/lib/active_support/evented_file_update_checker.rb +185 -0
- data/lib/active_support/execution_context/test_helper.rb +13 -0
- data/lib/active_support/execution_context.rb +110 -0
- data/lib/active_support/execution_wrapper.rb +150 -0
- data/lib/active_support/executor/test_helper.rb +7 -0
- data/lib/active_support/executor.rb +8 -0
- data/lib/active_support/file_update_checker.rb +166 -0
- data/lib/active_support/fork_tracker.rb +43 -0
- data/lib/active_support/gem_version.rb +17 -0
- data/lib/active_support/gzip.rb +41 -0
- data/lib/active_support/hash_with_indifferent_access.rb +464 -0
- data/lib/active_support/html_safe_translation.rb +56 -0
- data/lib/active_support/i18n.rb +17 -0
- data/lib/active_support/i18n_railtie.rb +140 -0
- data/lib/active_support/inflections.rb +68 -49
- data/lib/active_support/inflector/inflections.rb +290 -0
- data/lib/active_support/inflector/methods.rb +387 -0
- data/lib/active_support/inflector/transliterate.rb +147 -0
- data/lib/active_support/inflector.rb +7 -164
- data/lib/active_support/isolated_execution_state.rb +76 -0
- data/lib/active_support/json/decoding.rb +78 -0
- data/lib/active_support/json/encoding.rb +256 -0
- data/lib/active_support/json.rb +4 -0
- data/lib/active_support/key_generator.rb +66 -0
- data/lib/active_support/lazy_load_hooks.rb +107 -0
- data/lib/active_support/locale/en.rb +33 -0
- data/lib/active_support/locale/en.yml +141 -0
- data/lib/active_support/log_subscriber/test_helper.rb +106 -0
- data/lib/active_support/log_subscriber.rb +188 -0
- data/lib/active_support/logger.rb +55 -0
- data/lib/active_support/logger_silence.rb +21 -0
- data/lib/active_support/logger_thread_safe_level.rb +50 -0
- data/lib/active_support/message_encryptor.rb +374 -0
- data/lib/active_support/message_encryptors.rb +193 -0
- data/lib/active_support/message_pack/cache_serializer.rb +23 -0
- data/lib/active_support/message_pack/extensions.rb +310 -0
- data/lib/active_support/message_pack/serializer.rb +63 -0
- data/lib/active_support/message_pack.rb +50 -0
- data/lib/active_support/message_verifier.rb +377 -0
- data/lib/active_support/message_verifiers.rb +189 -0
- data/lib/active_support/messages/codec.rb +65 -0
- data/lib/active_support/messages/metadata.rb +146 -0
- data/lib/active_support/messages/rotation_configuration.rb +23 -0
- data/lib/active_support/messages/rotation_coordinator.rb +102 -0
- data/lib/active_support/messages/rotator.rb +69 -0
- data/lib/active_support/messages/serializer_with_fallback.rb +158 -0
- data/lib/active_support/multibyte/chars.rb +188 -0
- data/lib/active_support/multibyte/unicode.rb +42 -0
- data/lib/active_support/multibyte.rb +27 -0
- data/lib/active_support/notifications/fanout.rb +467 -0
- data/lib/active_support/notifications/instrumenter.rb +240 -0
- data/lib/active_support/notifications.rb +281 -0
- data/lib/active_support/number_helper/number_converter.rb +190 -0
- data/lib/active_support/number_helper/number_to_currency_converter.rb +46 -0
- data/lib/active_support/number_helper/number_to_delimited_converter.rb +30 -0
- data/lib/active_support/number_helper/number_to_human_converter.rb +69 -0
- data/lib/active_support/number_helper/number_to_human_size_converter.rb +60 -0
- data/lib/active_support/number_helper/number_to_percentage_converter.rb +16 -0
- data/lib/active_support/number_helper/number_to_phone_converter.rb +60 -0
- data/lib/active_support/number_helper/number_to_rounded_converter.rb +59 -0
- data/lib/active_support/number_helper/rounding_helper.rb +46 -0
- data/lib/active_support/number_helper.rb +479 -0
- data/lib/active_support/option_merger.rb +38 -0
- data/lib/active_support/ordered_hash.rb +50 -0
- data/lib/active_support/ordered_options.rb +141 -25
- data/lib/active_support/parameter_filter.rb +157 -0
- data/lib/active_support/rails.rb +26 -0
- data/lib/active_support/railtie.rb +180 -0
- data/lib/active_support/reloader.rb +138 -0
- data/lib/active_support/rescuable.rb +176 -0
- data/lib/active_support/secure_compare_rotator.rb +58 -0
- data/lib/active_support/security_utils.rb +38 -0
- data/lib/active_support/string_inquirer.rb +35 -0
- data/lib/active_support/structured_event_subscriber.rb +99 -0
- data/lib/active_support/subscriber.rb +141 -0
- data/lib/active_support/syntax_error_proxy.rb +67 -0
- data/lib/active_support/tagged_logging.rb +157 -0
- data/lib/active_support/test_case.rb +365 -0
- data/lib/active_support/testing/assertions.rb +369 -0
- data/lib/active_support/testing/autorun.rb +10 -0
- data/lib/active_support/testing/constant_lookup.rb +51 -0
- data/lib/active_support/testing/constant_stubbing.rb +54 -0
- data/lib/active_support/testing/declarative.rb +28 -0
- data/lib/active_support/testing/deprecation.rb +82 -0
- data/lib/active_support/testing/error_reporter_assertions.rb +124 -0
- data/lib/active_support/testing/event_reporter_assertions.rb +227 -0
- data/lib/active_support/testing/file_fixtures.rb +38 -0
- data/lib/active_support/testing/isolation.rb +121 -0
- data/lib/active_support/testing/method_call_assertions.rb +69 -0
- data/lib/active_support/testing/notification_assertions.rb +92 -0
- data/lib/active_support/testing/parallelization/server.rb +98 -0
- data/lib/active_support/testing/parallelization/worker.rb +107 -0
- data/lib/active_support/testing/parallelization.rb +79 -0
- data/lib/active_support/testing/parallelize_executor.rb +81 -0
- data/lib/active_support/testing/setup_and_teardown.rb +57 -0
- data/lib/active_support/testing/stream.rb +41 -0
- data/lib/active_support/testing/tagged_logging.rb +27 -0
- data/lib/active_support/testing/tests_without_assertions.rb +19 -0
- data/lib/active_support/testing/time_helpers.rb +273 -0
- data/lib/active_support/time.rb +20 -0
- data/lib/active_support/time_with_zone.rb +613 -0
- data/lib/active_support/values/time_zone.rb +599 -158
- data/lib/active_support/version.rb +7 -6
- data/lib/active_support/xml_mini/jdom.rb +175 -0
- data/lib/active_support/xml_mini/libxml.rb +80 -0
- data/lib/active_support/xml_mini/libxmlsax.rb +83 -0
- data/lib/active_support/xml_mini/nokogiri.rb +83 -0
- data/lib/active_support/xml_mini/nokogirisax.rb +86 -0
- data/lib/active_support/xml_mini/rexml.rb +137 -0
- data/lib/active_support/xml_mini.rb +212 -0
- data/lib/active_support.rb +122 -10
- metadata +524 -93
- data/CHANGELOG +0 -283
- data/lib/active_support/binding_of_caller.rb +0 -84
- data/lib/active_support/breakpoint.rb +0 -523
- data/lib/active_support/class_attribute_accessors.rb +0 -57
- data/lib/active_support/class_inheritable_attributes.rb +0 -117
- data/lib/active_support/clean_logger.rb +0 -36
- data/lib/active_support/core_ext/blank.rb +0 -38
- data/lib/active_support/core_ext/cgi/escape_skipping_slashes.rb +0 -14
- data/lib/active_support/core_ext/cgi.rb +0 -5
- data/lib/active_support/core_ext/exception.rb +0 -29
- data/lib/active_support/core_ext/integer/even_odd.rb +0 -24
- data/lib/active_support/core_ext/object_and_class.rb +0 -44
- data/lib/active_support/module_attribute_accessors.rb +0 -57
- data/lib/active_support/whiny_nil.rb +0 -38
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "time"
|
|
4
|
+
require "active_support/json"
|
|
5
|
+
require_relative "serializer_with_fallback"
|
|
6
|
+
|
|
7
|
+
module ActiveSupport
|
|
8
|
+
module Messages # :nodoc:
|
|
9
|
+
module Metadata # :nodoc:
|
|
10
|
+
singleton_class.attr_accessor :use_message_serializer_for_metadata
|
|
11
|
+
|
|
12
|
+
ENVELOPE_SERIALIZERS = [
|
|
13
|
+
*SerializerWithFallback::SERIALIZERS.values,
|
|
14
|
+
ActiveSupport::JSON,
|
|
15
|
+
::JSON,
|
|
16
|
+
Marshal,
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
TIMESTAMP_SERIALIZERS = [
|
|
20
|
+
SerializerWithFallback::SERIALIZERS.fetch(:message_pack),
|
|
21
|
+
SerializerWithFallback::SERIALIZERS.fetch(:message_pack_allow_marshal),
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
ActiveSupport.on_load(:message_pack) do
|
|
25
|
+
ENVELOPE_SERIALIZERS << ActiveSupport::MessagePack
|
|
26
|
+
TIMESTAMP_SERIALIZERS << ActiveSupport::MessagePack
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
def serialize_with_metadata(data, **metadata)
|
|
31
|
+
has_metadata = metadata.any? { |k, v| v }
|
|
32
|
+
|
|
33
|
+
if has_metadata && !use_message_serializer_for_metadata?
|
|
34
|
+
data_string = serialize_to_json_safe_string(data)
|
|
35
|
+
envelope = wrap_in_metadata_legacy_envelope({ "message" => data_string }, **metadata)
|
|
36
|
+
serialize_to_json(envelope)
|
|
37
|
+
else
|
|
38
|
+
data = wrap_in_metadata_envelope({ "data" => data }, **metadata) if has_metadata
|
|
39
|
+
serialize(data)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def deserialize_with_metadata(message, **expected_metadata)
|
|
44
|
+
if dual_serialized_metadata_envelope_json?(message)
|
|
45
|
+
envelope = deserialize_from_json(message)
|
|
46
|
+
extracted = extract_from_metadata_envelope(envelope, **expected_metadata)
|
|
47
|
+
deserialize_from_json_safe_string(extracted["message"])
|
|
48
|
+
else
|
|
49
|
+
deserialized = deserialize(message)
|
|
50
|
+
if metadata_envelope?(deserialized)
|
|
51
|
+
extract_from_metadata_envelope(deserialized, **expected_metadata)["data"]
|
|
52
|
+
elsif expected_metadata.none? { |k, v| v }
|
|
53
|
+
deserialized
|
|
54
|
+
else
|
|
55
|
+
throw :invalid_message_content, "missing metadata"
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def use_message_serializer_for_metadata?
|
|
61
|
+
Metadata.use_message_serializer_for_metadata && Metadata::ENVELOPE_SERIALIZERS.include?(serializer)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def wrap_in_metadata_envelope(hash, expires_at: nil, expires_in: nil, purpose: nil)
|
|
65
|
+
expiry = pick_expiry(expires_at, expires_in)
|
|
66
|
+
hash["exp"] = expiry if expiry
|
|
67
|
+
hash["pur"] = purpose.to_s if purpose
|
|
68
|
+
{ "_rails" => hash }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def wrap_in_metadata_legacy_envelope(hash, expires_at: nil, expires_in: nil, purpose: nil)
|
|
72
|
+
expiry = pick_expiry(expires_at, expires_in)
|
|
73
|
+
hash["exp"] = expiry
|
|
74
|
+
hash["pur"] = purpose
|
|
75
|
+
{ "_rails" => hash }
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def extract_from_metadata_envelope(envelope, purpose: nil)
|
|
79
|
+
hash = envelope["_rails"]
|
|
80
|
+
|
|
81
|
+
if hash["exp"] && Time.now.utc >= parse_expiry(hash["exp"])
|
|
82
|
+
throw :invalid_message_content, "expired"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
if hash["pur"].to_s != purpose.to_s
|
|
86
|
+
throw :invalid_message_content, "mismatched purpose"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
hash
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def metadata_envelope?(object)
|
|
93
|
+
object.is_a?(Hash) && object.key?("_rails")
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def dual_serialized_metadata_envelope_json?(string)
|
|
97
|
+
string.start_with?('{"_rails":{"message":')
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def pick_expiry(expires_at, expires_in)
|
|
101
|
+
expiry = if expires_at
|
|
102
|
+
expires_at.utc
|
|
103
|
+
elsif expires_in
|
|
104
|
+
Time.now.utc.advance(seconds: expires_in)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
unless Metadata::TIMESTAMP_SERIALIZERS.include?(serializer)
|
|
108
|
+
expiry = expiry&.iso8601(3)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
expiry
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def parse_expiry(expires_at)
|
|
115
|
+
if !expires_at.is_a?(String)
|
|
116
|
+
expires_at
|
|
117
|
+
elsif ActiveSupport.use_standard_json_time_format
|
|
118
|
+
Time.iso8601(expires_at)
|
|
119
|
+
else
|
|
120
|
+
Time.parse(expires_at)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def serialize_to_json(data)
|
|
125
|
+
ActiveSupport::JSON.encode(data)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def deserialize_from_json(serialized)
|
|
129
|
+
ActiveSupport::JSON.decode(serialized)
|
|
130
|
+
rescue ::JSON::ParserError => error
|
|
131
|
+
# Throw :invalid_message_format instead of :invalid_message_serialization
|
|
132
|
+
# because here a parse error is due to a bad message rather than an
|
|
133
|
+
# incompatible `self.serializer`.
|
|
134
|
+
throw :invalid_message_format, error
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def serialize_to_json_safe_string(data)
|
|
138
|
+
encode(serialize(data), url_safe: false)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def deserialize_from_json_safe_string(string)
|
|
142
|
+
deserialize(decode(string, url_safe: false))
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveSupport
|
|
4
|
+
module Messages
|
|
5
|
+
class RotationConfiguration # :nodoc:
|
|
6
|
+
attr_reader :signed, :encrypted
|
|
7
|
+
|
|
8
|
+
def initialize
|
|
9
|
+
@signed, @encrypted = [], []
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def rotate(kind, *args, **options)
|
|
13
|
+
args << options unless options.empty?
|
|
14
|
+
case kind
|
|
15
|
+
when :signed
|
|
16
|
+
@signed << args
|
|
17
|
+
when :encrypted
|
|
18
|
+
@encrypted << args
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/core_ext/hash/slice"
|
|
4
|
+
|
|
5
|
+
module ActiveSupport
|
|
6
|
+
module Messages
|
|
7
|
+
class RotationCoordinator # :nodoc:
|
|
8
|
+
attr_accessor :transitional
|
|
9
|
+
|
|
10
|
+
def initialize(&secret_generator)
|
|
11
|
+
raise ArgumentError, "A secret generator block is required" unless secret_generator
|
|
12
|
+
@secret_generator = secret_generator
|
|
13
|
+
@rotate_options = []
|
|
14
|
+
@on_rotation = nil
|
|
15
|
+
@codecs = {}
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def [](salt)
|
|
19
|
+
@codecs[salt] ||= build_with_rotations(salt)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def []=(salt, codec)
|
|
23
|
+
@codecs[salt] = codec
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def rotate(**options, &block)
|
|
27
|
+
raise ArgumentError, "Options cannot be specified when using a block" if block && !options.empty?
|
|
28
|
+
changing_configuration!
|
|
29
|
+
|
|
30
|
+
@rotate_options << (block || options)
|
|
31
|
+
|
|
32
|
+
self
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def prepend(**options, &block)
|
|
36
|
+
raise ArgumentError, "Options cannot be specified when using a block" if block && !options.empty?
|
|
37
|
+
changing_configuration!
|
|
38
|
+
|
|
39
|
+
@rotate_options.unshift(block || options)
|
|
40
|
+
|
|
41
|
+
self
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def rotate_defaults
|
|
45
|
+
rotate()
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def clear_rotations
|
|
49
|
+
changing_configuration!
|
|
50
|
+
@rotate_options.clear
|
|
51
|
+
self
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def on_rotation(&callback)
|
|
55
|
+
changing_configuration!
|
|
56
|
+
@on_rotation = callback
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
def changing_configuration!
|
|
61
|
+
if @codecs.any?
|
|
62
|
+
raise <<~MESSAGE
|
|
63
|
+
Cannot change #{self.class} configuration after it has already been applied.
|
|
64
|
+
|
|
65
|
+
The configuration has been applied with the following salts:
|
|
66
|
+
#{@codecs.keys.map { |salt| "- #{salt.inspect}" }.join("\n")}
|
|
67
|
+
MESSAGE
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def normalize_options(options)
|
|
72
|
+
options = options.dup
|
|
73
|
+
|
|
74
|
+
options[:secret_generator] ||= @secret_generator
|
|
75
|
+
|
|
76
|
+
secret_generator_kwargs = options[:secret_generator].parameters.
|
|
77
|
+
filter_map { |type, name| name if type == :key || type == :keyreq }
|
|
78
|
+
options[:secret_generator_options] = options.extract!(*secret_generator_kwargs)
|
|
79
|
+
|
|
80
|
+
options[:on_rotation] = @on_rotation
|
|
81
|
+
|
|
82
|
+
options
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def build_with_rotations(salt)
|
|
86
|
+
rotate_options = @rotate_options.map { |options| options.is_a?(Proc) ? options.(salt) : options }
|
|
87
|
+
transitional = self.transitional && rotate_options.first
|
|
88
|
+
rotate_options.compact!
|
|
89
|
+
rotate_options[0..1] = rotate_options[0..1].reverse if transitional
|
|
90
|
+
rotate_options = rotate_options.map { |options| normalize_options(options) }.uniq
|
|
91
|
+
|
|
92
|
+
raise "No options have been configured for #{salt}" if rotate_options.empty?
|
|
93
|
+
|
|
94
|
+
rotate_options.map { |options| build(salt.to_s, **options) }.reduce(&:fall_back_to)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def build(salt, secret_generator:, secret_generator_options:, **options)
|
|
98
|
+
raise NotImplementedError
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveSupport
|
|
4
|
+
module Messages
|
|
5
|
+
module Rotator # :nodoc:
|
|
6
|
+
def initialize(*args, on_rotation: nil, **options)
|
|
7
|
+
super(*args, **options)
|
|
8
|
+
@args = args
|
|
9
|
+
@options = options
|
|
10
|
+
@rotations = []
|
|
11
|
+
@on_rotation = on_rotation
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def rotate(*args, **options)
|
|
15
|
+
fall_back_to build_rotation(*args, **options)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def on_rotation(&on_rotation)
|
|
19
|
+
@on_rotation = on_rotation
|
|
20
|
+
self
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def fall_back_to(fallback)
|
|
24
|
+
@rotations << fallback
|
|
25
|
+
self
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def read_message(message, on_rotation: @on_rotation, **options)
|
|
29
|
+
if @rotations.empty?
|
|
30
|
+
super(message, **options)
|
|
31
|
+
else
|
|
32
|
+
thrown, error = catch_rotation_error do
|
|
33
|
+
return super(message, **options)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
@rotations.each do |rotation|
|
|
37
|
+
catch_rotation_error do
|
|
38
|
+
value = rotation.read_message(message, **options)
|
|
39
|
+
on_rotation&.call
|
|
40
|
+
return value
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
throw thrown, error
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def initialize_dup(*)
|
|
49
|
+
super
|
|
50
|
+
@rotations = @rotations.dup
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
def build_rotation(*args, **options)
|
|
55
|
+
self.class.new(*args, *@args.drop(args.length), **@options, **options)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def catch_rotation_error(&block)
|
|
59
|
+
error = catch :invalid_message_format do
|
|
60
|
+
error = catch :invalid_message_serialization do
|
|
61
|
+
return [nil, block.call]
|
|
62
|
+
end
|
|
63
|
+
return [:invalid_message_serialization, error]
|
|
64
|
+
end
|
|
65
|
+
[:invalid_message_format, error]
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/core_ext/kernel/reporting"
|
|
4
|
+
require "active_support/notifications"
|
|
5
|
+
|
|
6
|
+
module ActiveSupport
|
|
7
|
+
module Messages # :nodoc:
|
|
8
|
+
module SerializerWithFallback # :nodoc:
|
|
9
|
+
def self.[](format)
|
|
10
|
+
if format.to_s.include?("message_pack") && !defined?(ActiveSupport::MessagePack)
|
|
11
|
+
require "active_support/message_pack"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
SERIALIZERS.fetch(format)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def load(dumped)
|
|
18
|
+
format = detect_format(dumped)
|
|
19
|
+
|
|
20
|
+
if format == self.format
|
|
21
|
+
_load(dumped)
|
|
22
|
+
elsif format && fallback?(format)
|
|
23
|
+
payload = { serializer: SERIALIZERS.key(self), fallback: format, serialized: dumped }
|
|
24
|
+
ActiveSupport::Notifications.instrument("message_serializer_fallback.active_support", payload) do
|
|
25
|
+
payload[:deserialized] = SERIALIZERS[format]._load(dumped)
|
|
26
|
+
end
|
|
27
|
+
else
|
|
28
|
+
raise "Unsupported serialization format"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
def detect_format(dumped)
|
|
34
|
+
case
|
|
35
|
+
when MessagePackWithFallback.dumped?(dumped)
|
|
36
|
+
:message_pack
|
|
37
|
+
when MarshalWithFallback.dumped?(dumped)
|
|
38
|
+
:marshal
|
|
39
|
+
when JsonWithFallback.dumped?(dumped)
|
|
40
|
+
:json
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def fallback?(format)
|
|
45
|
+
format != :marshal
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
module AllowMarshal
|
|
49
|
+
private
|
|
50
|
+
def fallback?(format)
|
|
51
|
+
super || format == :marshal
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
module MarshalWithFallback
|
|
56
|
+
include SerializerWithFallback
|
|
57
|
+
extend self
|
|
58
|
+
|
|
59
|
+
def format
|
|
60
|
+
:marshal
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def dump(object)
|
|
64
|
+
Marshal.dump(object)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def _load(dumped)
|
|
68
|
+
Marshal.load(dumped)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
MARSHAL_SIGNATURE = "\x04\x08"
|
|
72
|
+
|
|
73
|
+
def dumped?(dumped)
|
|
74
|
+
dumped.start_with?(MARSHAL_SIGNATURE)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
module JsonWithFallback
|
|
79
|
+
include SerializerWithFallback
|
|
80
|
+
extend self
|
|
81
|
+
|
|
82
|
+
def format
|
|
83
|
+
:json
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def dump(object)
|
|
87
|
+
ActiveSupport::JSON.encode(object)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def _load(dumped)
|
|
91
|
+
ActiveSupport::JSON.decode(dumped)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
JSON_START_WITH = /\A(?:[{\["]|-?\d|true|false|null)/
|
|
95
|
+
|
|
96
|
+
def dumped?(dumped)
|
|
97
|
+
JSON_START_WITH.match?(dumped)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
private
|
|
101
|
+
def detect_format(dumped)
|
|
102
|
+
# Assume JSON format if format could not be determined.
|
|
103
|
+
super || :json
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
module JsonWithFallbackAllowMarshal
|
|
108
|
+
include JsonWithFallback
|
|
109
|
+
include AllowMarshal
|
|
110
|
+
extend self
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
module MessagePackWithFallback
|
|
114
|
+
include SerializerWithFallback
|
|
115
|
+
extend self
|
|
116
|
+
|
|
117
|
+
def format
|
|
118
|
+
:message_pack
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def dump(object)
|
|
122
|
+
ActiveSupport::MessagePack.dump(object)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def _load(dumped)
|
|
126
|
+
ActiveSupport::MessagePack.load(dumped)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def dumped?(dumped)
|
|
130
|
+
available? && ActiveSupport::MessagePack.signature?(dumped)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
private
|
|
134
|
+
def available?
|
|
135
|
+
return @available if defined?(@available)
|
|
136
|
+
silence_warnings { require "active_support/message_pack" }
|
|
137
|
+
@available = true
|
|
138
|
+
rescue LoadError
|
|
139
|
+
@available = false
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
module MessagePackWithFallbackAllowMarshal
|
|
144
|
+
include MessagePackWithFallback
|
|
145
|
+
include AllowMarshal
|
|
146
|
+
extend self
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
SERIALIZERS = {
|
|
150
|
+
marshal: MarshalWithFallback,
|
|
151
|
+
json: JsonWithFallback,
|
|
152
|
+
json_allow_marshal: JsonWithFallbackAllowMarshal,
|
|
153
|
+
message_pack: MessagePackWithFallback,
|
|
154
|
+
message_pack_allow_marshal: MessagePackWithFallbackAllowMarshal,
|
|
155
|
+
}
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/json"
|
|
4
|
+
require "active_support/core_ext/string/access"
|
|
5
|
+
require "active_support/core_ext/string/behavior"
|
|
6
|
+
require "active_support/core_ext/module/delegation"
|
|
7
|
+
|
|
8
|
+
module ActiveSupport # :nodoc:
|
|
9
|
+
module Multibyte # :nodoc:
|
|
10
|
+
# = Active Support \Multibyte \Chars
|
|
11
|
+
#
|
|
12
|
+
# Chars enables you to work transparently with UTF-8 encoding in the Ruby
|
|
13
|
+
# String class without having extensive knowledge about the encoding. A
|
|
14
|
+
# Chars object accepts a string upon initialization and proxies String
|
|
15
|
+
# methods in an encoding safe manner. All the normal String methods are also
|
|
16
|
+
# implemented on the proxy.
|
|
17
|
+
#
|
|
18
|
+
# String methods are proxied through the Chars object, and can be accessed
|
|
19
|
+
# through the +mb_chars+ method. Methods which would normally return a
|
|
20
|
+
# String object now return a Chars object so methods can be chained.
|
|
21
|
+
#
|
|
22
|
+
# 'The Perfect String '.mb_chars.downcase.strip
|
|
23
|
+
# # => #<ActiveSupport::Multibyte::Chars:0x007fdc434ccc10 @wrapped_string="the perfect string">
|
|
24
|
+
#
|
|
25
|
+
# Chars objects are perfectly interchangeable with String objects as long as
|
|
26
|
+
# no explicit class checks are made. If certain methods do explicitly check
|
|
27
|
+
# the class, call +to_s+ before you pass chars objects to them.
|
|
28
|
+
#
|
|
29
|
+
# bad.explicit_checking_method 'T'.mb_chars.downcase.to_s
|
|
30
|
+
#
|
|
31
|
+
# The default Chars implementation assumes that the encoding of the string
|
|
32
|
+
# is UTF-8, if you want to handle different encodings you can write your own
|
|
33
|
+
# multibyte string handler and configure it through
|
|
34
|
+
# ActiveSupport::Multibyte.proxy_class.
|
|
35
|
+
#
|
|
36
|
+
# class CharsForUTF32
|
|
37
|
+
# def size
|
|
38
|
+
# @wrapped_string.size / 4
|
|
39
|
+
# end
|
|
40
|
+
#
|
|
41
|
+
# def self.accepts?(string)
|
|
42
|
+
# string.length % 4 == 0
|
|
43
|
+
# end
|
|
44
|
+
# end
|
|
45
|
+
#
|
|
46
|
+
# ActiveSupport::Multibyte.proxy_class = CharsForUTF32
|
|
47
|
+
class Chars
|
|
48
|
+
include Comparable
|
|
49
|
+
attr_reader :wrapped_string
|
|
50
|
+
alias to_s wrapped_string
|
|
51
|
+
alias to_str wrapped_string
|
|
52
|
+
|
|
53
|
+
delegate :<=>, :=~, :match?, :acts_like_string?, to: :wrapped_string
|
|
54
|
+
|
|
55
|
+
# Creates a new Chars instance by wrapping _string_.
|
|
56
|
+
def initialize(string, deprecation: true)
|
|
57
|
+
if deprecation
|
|
58
|
+
ActiveSupport.deprecator.warn(
|
|
59
|
+
"ActiveSupport::Multibyte::Chars is deprecated and will be removed in Rails 8.2. " \
|
|
60
|
+
"Use normal string methods instead."
|
|
61
|
+
)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
@wrapped_string = string
|
|
65
|
+
if string.encoding != Encoding::UTF_8
|
|
66
|
+
@wrapped_string = @wrapped_string.dup
|
|
67
|
+
@wrapped_string.force_encoding(Encoding::UTF_8)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Forward all undefined methods to the wrapped string.
|
|
72
|
+
def method_missing(method, ...)
|
|
73
|
+
result = @wrapped_string.__send__(method, ...)
|
|
74
|
+
if method.end_with?("!")
|
|
75
|
+
self if result
|
|
76
|
+
else
|
|
77
|
+
result.kind_of?(String) ? chars(result) : result
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Returns +true+ if _obj_ responds to the given method. Private methods
|
|
82
|
+
# are included in the search only if the optional second parameter
|
|
83
|
+
# evaluates to +true+.
|
|
84
|
+
def respond_to_missing?(method, include_private)
|
|
85
|
+
@wrapped_string.respond_to?(method, include_private)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Works just like <tt>String#split</tt>, with the exception that the items
|
|
89
|
+
# in the resulting list are Chars instances instead of String. This makes
|
|
90
|
+
# chaining methods easier.
|
|
91
|
+
#
|
|
92
|
+
# 'Café périferôl'.mb_chars.split(/é/).map { |part| part.upcase.to_s } # => ["CAF", " P", "RIFERÔL"]
|
|
93
|
+
def split(*args)
|
|
94
|
+
@wrapped_string.split(*args).map { |i| self.class.new(i) }
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Works like <tt>String#slice!</tt>, but returns an instance of
|
|
98
|
+
# Chars, or +nil+ if the string was not modified. The string will not be
|
|
99
|
+
# modified if the range given is out of bounds
|
|
100
|
+
#
|
|
101
|
+
# string = 'Welcome'
|
|
102
|
+
# string.mb_chars.slice!(3) # => #<ActiveSupport::Multibyte::Chars:0x000000038109b8 @wrapped_string="c">
|
|
103
|
+
# string # => 'Welome'
|
|
104
|
+
# string.mb_chars.slice!(0..3) # => #<ActiveSupport::Multibyte::Chars:0x00000002eb80a0 @wrapped_string="Welo">
|
|
105
|
+
# string # => 'me'
|
|
106
|
+
def slice!(*args)
|
|
107
|
+
string_sliced = @wrapped_string.slice!(*args)
|
|
108
|
+
if string_sliced
|
|
109
|
+
chars(string_sliced)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Reverses all characters in the string.
|
|
114
|
+
#
|
|
115
|
+
# 'Café'.mb_chars.reverse.to_s # => 'éfaC'
|
|
116
|
+
def reverse
|
|
117
|
+
chars(@wrapped_string.grapheme_clusters.reverse.join)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Limits the byte size of the string to a number of bytes without breaking
|
|
121
|
+
# characters. Usable when the storage for a string is limited for some
|
|
122
|
+
# reason.
|
|
123
|
+
#
|
|
124
|
+
# 'こんにちは'.mb_chars.limit(7).to_s # => "こん"
|
|
125
|
+
def limit(limit)
|
|
126
|
+
chars(@wrapped_string.truncate_bytes(limit, omission: nil))
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Capitalizes the first letter of every word, when possible.
|
|
130
|
+
#
|
|
131
|
+
# "ÉL QUE SE ENTERÓ".mb_chars.titleize.to_s # => "Él Que Se Enteró"
|
|
132
|
+
# "日本語".mb_chars.titleize.to_s # => "日本語"
|
|
133
|
+
def titleize
|
|
134
|
+
chars(downcase.to_s.gsub(/\b('?\S)/u) { $1.upcase })
|
|
135
|
+
end
|
|
136
|
+
alias_method :titlecase, :titleize
|
|
137
|
+
|
|
138
|
+
# Performs canonical decomposition on all the characters.
|
|
139
|
+
#
|
|
140
|
+
# 'é'.length # => 1
|
|
141
|
+
# 'é'.mb_chars.decompose.to_s.length # => 2
|
|
142
|
+
def decompose
|
|
143
|
+
chars(Unicode.decompose(:canonical, @wrapped_string.codepoints.to_a).pack("U*"))
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Performs composition on all the characters.
|
|
147
|
+
#
|
|
148
|
+
# 'é'.length # => 1
|
|
149
|
+
# 'é'.mb_chars.compose.to_s.length # => 1
|
|
150
|
+
def compose
|
|
151
|
+
chars(Unicode.compose(@wrapped_string.codepoints.to_a).pack("U*"))
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Returns the number of grapheme clusters in the string.
|
|
155
|
+
#
|
|
156
|
+
# 'क्षि'.mb_chars.length # => 4
|
|
157
|
+
# 'क्षि'.mb_chars.grapheme_length # => 2
|
|
158
|
+
def grapheme_length
|
|
159
|
+
@wrapped_string.grapheme_clusters.length
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent
|
|
163
|
+
# resulting in a valid UTF-8 string.
|
|
164
|
+
#
|
|
165
|
+
# Passing +true+ will forcibly tidy all bytes, assuming that the string's
|
|
166
|
+
# encoding is entirely CP1252 or ISO-8859-1.
|
|
167
|
+
def tidy_bytes(force = false)
|
|
168
|
+
chars(Unicode.tidy_bytes(@wrapped_string, force))
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def as_json(options = nil) # :nodoc:
|
|
172
|
+
to_s.as_json(options)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
%w(reverse tidy_bytes).each do |method|
|
|
176
|
+
define_method("#{method}!") do |*args|
|
|
177
|
+
@wrapped_string = public_send(method, *args).to_s
|
|
178
|
+
self
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
private
|
|
183
|
+
def chars(string)
|
|
184
|
+
self.class.new(string)
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|