aboutsummaryrefslogtreecommitdiffstats
path: root/activesupport
diff options
context:
space:
mode:
Diffstat (limited to 'activesupport')
-rw-r--r--activesupport/CHANGELOG.md587
-rw-r--r--activesupport/MIT-LICENSE2
-rw-r--r--activesupport/README.rdoc4
-rw-r--r--activesupport/Rakefile2
-rw-r--r--activesupport/activesupport.gemspec9
-rwxr-xr-xactivesupport/bin/generate_tables3
-rwxr-xr-xactivesupport/bin/test3
-rw-r--r--activesupport/lib/active_support.rb14
-rw-r--r--activesupport/lib/active_support/all.rb2
-rw-r--r--activesupport/lib/active_support/array_inquirer.rb2
-rw-r--r--activesupport/lib/active_support/backtrace_cleaner.rb2
-rw-r--r--activesupport/lib/active_support/benchmarkable.rb2
-rw-r--r--activesupport/lib/active_support/builder.rb2
-rw-r--r--activesupport/lib/active_support/cache.rb173
-rw-r--r--activesupport/lib/active_support/cache/file_store.rb11
-rw-r--r--activesupport/lib/active_support/cache/mem_cache_store.rb19
-rw-r--r--activesupport/lib/active_support/cache/memory_store.rb4
-rw-r--r--activesupport/lib/active_support/cache/null_store.rb2
-rw-r--r--activesupport/lib/active_support/cache/redis_cache_store.rb406
-rw-r--r--activesupport/lib/active_support/cache/strategy/local_cache.rb17
-rw-r--r--activesupport/lib/active_support/cache/strategy/local_cache_middleware.rb10
-rw-r--r--activesupport/lib/active_support/callbacks.rb75
-rw-r--r--activesupport/lib/active_support/concern.rb4
-rw-r--r--activesupport/lib/active_support/concurrency/load_interlock_aware_monitor.rb17
-rw-r--r--activesupport/lib/active_support/concurrency/share_lock.rb2
-rw-r--r--activesupport/lib/active_support/configurable.rb2
-rw-r--r--activesupport/lib/active_support/core_ext.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/array.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/array/access.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/array/conversions.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/array/extract_options.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/array/grouping.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/array/inquiry.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/array/prepend_and_append.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/array/wrap.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/benchmark.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/big_decimal.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/big_decimal/conversions.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/class.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/class/attribute.rb50
-rw-r--r--activesupport/lib/active_support/core_ext/class/attribute_accessors.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/class/subclasses.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/date.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/date/acts_like.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/date/blank.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/date/calculations.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/date/conversions.rb19
-rw-r--r--activesupport/lib/active_support/core_ext/date/zones.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/date_and_time/calculations.rb66
-rw-r--r--activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/date_and_time/zones.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/date_time.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/acts_like.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/blank.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/calculations.rb20
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/compatibility.rb15
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/conversions.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/digest/uuid.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/enumerable.rb70
-rw-r--r--activesupport/lib/active_support/core_ext/file.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/file/atomic.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/hash.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/hash/compact.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/hash/conversions.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/hash/deep_merge.rb20
-rw-r--r--activesupport/lib/active_support/core_ext/hash/except.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/hash/indifferent_access.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/hash/keys.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/hash/reverse_merge.rb7
-rw-r--r--activesupport/lib/active_support/core_ext/hash/slice.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/hash/transform_values.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/integer.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/integer/inflections.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/integer/multiple.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/integer/time.rb21
-rw-r--r--activesupport/lib/active_support/core_ext/kernel.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/kernel/agnostics.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/kernel/concern.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/kernel/reporting.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/kernel/singleton_class.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/load_error.rb9
-rw-r--r--activesupport/lib/active_support/core_ext/marshal.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/module.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/module/aliasing.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/module/anonymous.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/module/attr_internal.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/module/attribute_accessors.rb45
-rw-r--r--activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/module/concerning.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/module/delegation.rb65
-rw-r--r--activesupport/lib/active_support/core_ext/module/deprecation.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/module/introspection.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/module/reachable.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/module/redefine_method.rb49
-rw-r--r--activesupport/lib/active_support/core_ext/module/remove_method.rb28
-rw-r--r--activesupport/lib/active_support/core_ext/name_error.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/numeric.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/bytes.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/conversions.rb16
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/inquiry.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/time.rb22
-rw-r--r--activesupport/lib/active_support/core_ext/object.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/object/acts_like.rb13
-rw-r--r--activesupport/lib/active_support/core_ext/object/blank.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/object/conversions.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/object/deep_dup.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/object/duplicable.rb40
-rw-r--r--activesupport/lib/active_support/core_ext/object/inclusion.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/object/instance_variables.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/object/json.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/object/to_param.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/object/to_query.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/object/try.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/object/with_options.rb15
-rw-r--r--activesupport/lib/active_support/core_ext/range.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/range/conversions.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/range/each.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/range/include_range.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/range/include_time_with_zone.rb23
-rw-r--r--activesupport/lib/active_support/core_ext/range/overlaps.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/regexp.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/securerandom.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/access.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/behavior.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/conversions.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/exclude.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/filters.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/indent.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/inflections.rb38
-rw-r--r--activesupport/lib/active_support/core_ext/string/inquiry.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/multibyte.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/string/output_safety.rb13
-rw-r--r--activesupport/lib/active_support/core_ext/string/starts_ends_with.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/strip.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/zones.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/time.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/time/acts_like.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/time/calculations.rb61
-rw-r--r--activesupport/lib/active_support/core_ext/time/compatibility.rb13
-rw-r--r--activesupport/lib/active_support/core_ext/time/conversions.rb5
-rw-r--r--activesupport/lib/active_support/core_ext/time/zones.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/uri.rb5
-rw-r--r--activesupport/lib/active_support/current_attributes.rb195
-rw-r--r--activesupport/lib/active_support/dependencies.rb39
-rw-r--r--activesupport/lib/active_support/dependencies/autoload.rb2
-rw-r--r--activesupport/lib/active_support/dependencies/interlock.rb2
-rw-r--r--activesupport/lib/active_support/deprecation.rb7
-rw-r--r--activesupport/lib/active_support/deprecation/behaviors.rb31
-rw-r--r--activesupport/lib/active_support/deprecation/constant_accessor.rb52
-rw-r--r--activesupport/lib/active_support/deprecation/instance_delegator.rb2
-rw-r--r--activesupport/lib/active_support/deprecation/method_wrappers.rb25
-rw-r--r--activesupport/lib/active_support/deprecation/proxy_wrappers.rb7
-rw-r--r--activesupport/lib/active_support/deprecation/reporting.rb12
-rw-r--r--activesupport/lib/active_support/descendants_tracker.rb2
-rw-r--r--activesupport/lib/active_support/digest.rb20
-rw-r--r--activesupport/lib/active_support/duration.rb203
-rw-r--r--activesupport/lib/active_support/duration/iso8601_parser.rb6
-rw-r--r--activesupport/lib/active_support/duration/iso8601_serializer.rb8
-rw-r--r--activesupport/lib/active_support/encrypted_configuration.rb49
-rw-r--r--activesupport/lib/active_support/encrypted_file.rb99
-rw-r--r--activesupport/lib/active_support/evented_file_update_checker.rb7
-rw-r--r--activesupport/lib/active_support/execution_wrapper.rb2
-rw-r--r--activesupport/lib/active_support/executor.rb2
-rw-r--r--activesupport/lib/active_support/file_update_checker.rb2
-rw-r--r--activesupport/lib/active_support/gem_version.rb6
-rw-r--r--activesupport/lib/active_support/gzip.rb4
-rw-r--r--activesupport/lib/active_support/hash_with_indifferent_access.rb80
-rw-r--r--activesupport/lib/active_support/i18n.rb4
-rw-r--r--activesupport/lib/active_support/i18n_railtie.rb12
-rw-r--r--activesupport/lib/active_support/inflections.rb2
-rw-r--r--activesupport/lib/active_support/inflector.rb2
-rw-r--r--activesupport/lib/active_support/inflector/inflections.rb22
-rw-r--r--activesupport/lib/active_support/inflector/methods.rb63
-rw-r--r--activesupport/lib/active_support/inflector/transliterate.rb25
-rw-r--r--activesupport/lib/active_support/json.rb2
-rw-r--r--activesupport/lib/active_support/json/decoding.rb2
-rw-r--r--activesupport/lib/active_support/json/encoding.rb2
-rw-r--r--activesupport/lib/active_support/key_generator.rb4
-rw-r--r--activesupport/lib/active_support/lazy_load_hooks.rb40
-rw-r--r--activesupport/lib/active_support/log_subscriber.rb11
-rw-r--r--activesupport/lib/active_support/log_subscriber/test_helper.rb2
-rw-r--r--activesupport/lib/active_support/logger.rb2
-rw-r--r--activesupport/lib/active_support/logger_silence.rb5
-rw-r--r--activesupport/lib/active_support/logger_thread_safe_level.rb2
-rw-r--r--activesupport/lib/active_support/message_encryptor.rb123
-rw-r--r--activesupport/lib/active_support/message_verifier.rb85
-rw-r--r--activesupport/lib/active_support/messages/metadata.rb71
-rw-r--r--activesupport/lib/active_support/messages/rotation_configuration.rb22
-rw-r--r--activesupport/lib/active_support/messages/rotator.rb56
-rw-r--r--activesupport/lib/active_support/multibyte.rb2
-rw-r--r--activesupport/lib/active_support/multibyte/chars.rb11
-rw-r--r--activesupport/lib/active_support/multibyte/unicode.rb4
-rw-r--r--activesupport/lib/active_support/notifications.rb4
-rw-r--r--activesupport/lib/active_support/notifications/fanout.rb2
-rw-r--r--activesupport/lib/active_support/notifications/instrumenter.rb2
-rw-r--r--activesupport/lib/active_support/number_helper.rb3
-rw-r--r--activesupport/lib/active_support/number_helper/number_converter.rb2
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_currency_converter.rb2
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb2
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_human_converter.rb8
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb2
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_percentage_converter.rb2
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_phone_converter.rb4
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb54
-rw-r--r--activesupport/lib/active_support/number_helper/rounding_helper.rb66
-rw-r--r--activesupport/lib/active_support/option_merger.rb2
-rw-r--r--activesupport/lib/active_support/ordered_hash.rb2
-rw-r--r--activesupport/lib/active_support/ordered_options.rb6
-rw-r--r--activesupport/lib/active_support/per_thread_registry.rb2
-rw-r--r--activesupport/lib/active_support/proxy_object.rb2
-rw-r--r--activesupport/lib/active_support/rails.rb2
-rw-r--r--activesupport/lib/active_support/railtie.rb42
-rw-r--r--activesupport/lib/active_support/reloader.rb12
-rw-r--r--activesupport/lib/active_support/rescuable.rb15
-rw-r--r--activesupport/lib/active_support/security_utils.rb26
-rw-r--r--activesupport/lib/active_support/string_inquirer.rb2
-rw-r--r--activesupport/lib/active_support/subscriber.rb2
-rw-r--r--activesupport/lib/active_support/tagged_logging.rb2
-rw-r--r--activesupport/lib/active_support/test_case.rb3
-rw-r--r--activesupport/lib/active_support/testing/assertions.rb10
-rw-r--r--activesupport/lib/active_support/testing/autorun.rb6
-rw-r--r--activesupport/lib/active_support/testing/constant_lookup.rb2
-rw-r--r--activesupport/lib/active_support/testing/declarative.rb2
-rw-r--r--activesupport/lib/active_support/testing/deprecation.rb2
-rw-r--r--activesupport/lib/active_support/testing/file_fixtures.rb2
-rw-r--r--activesupport/lib/active_support/testing/isolation.rb4
-rw-r--r--activesupport/lib/active_support/testing/method_call_assertions.rb2
-rw-r--r--activesupport/lib/active_support/testing/setup_and_teardown.rb2
-rw-r--r--activesupport/lib/active_support/testing/stream.rb2
-rw-r--r--activesupport/lib/active_support/testing/tagged_logging.rb2
-rw-r--r--activesupport/lib/active_support/testing/time_helpers.rb37
-rw-r--r--activesupport/lib/active_support/time.rb2
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb53
-rw-r--r--activesupport/lib/active_support/values/time_zone.rb105
-rw-r--r--activesupport/lib/active_support/version.rb2
-rw-r--r--activesupport/lib/active_support/xml_mini.rb2
-rw-r--r--activesupport/lib/active_support/xml_mini/jdom.rb6
-rw-r--r--activesupport/lib/active_support/xml_mini/libxml.rb10
-rw-r--r--activesupport/lib/active_support/xml_mini/libxmlsax.rb11
-rw-r--r--activesupport/lib/active_support/xml_mini/nokogiri.rb8
-rw-r--r--activesupport/lib/active_support/xml_mini/nokogirisax.rb8
-rw-r--r--activesupport/lib/active_support/xml_mini/rexml.rb4
-rw-r--r--activesupport/test/abstract_unit.rb6
-rw-r--r--activesupport/test/array_inquirer_test.rb2
-rw-r--r--activesupport/test/autoload_test.rb2
-rw-r--r--activesupport/test/autoloading_fixtures/a/b.rb2
-rw-r--r--activesupport/test/autoloading_fixtures/a/c/d.rb2
-rw-r--r--activesupport/test/autoloading_fixtures/a/c/em/f.rb2
-rw-r--r--activesupport/test/autoloading_fixtures/application.rb2
-rw-r--r--activesupport/test/autoloading_fixtures/circular1.rb2
-rw-r--r--activesupport/test/autoloading_fixtures/circular2.rb2
-rw-r--r--activesupport/test/autoloading_fixtures/class_folder.rb2
-rw-r--r--activesupport/test/autoloading_fixtures/class_folder/class_folder_subclass.rb2
-rw-r--r--activesupport/test/autoloading_fixtures/class_folder/inline_class.rb2
-rw-r--r--activesupport/test/autoloading_fixtures/class_folder/nested_class.rb2
-rw-r--r--activesupport/test/autoloading_fixtures/conflict.rb2
-rw-r--r--activesupport/test/autoloading_fixtures/counting_loader.rb2
-rw-r--r--activesupport/test/autoloading_fixtures/cross_site_dependency.rb2
-rw-r--r--activesupport/test/autoloading_fixtures/d.rb2
-rw-r--r--activesupport/test/autoloading_fixtures/em.rb2
-rw-r--r--activesupport/test/autoloading_fixtures/html/some_class.rb2
-rw-r--r--activesupport/test/autoloading_fixtures/load_path/loaded_constant.rb2
-rw-r--r--activesupport/test/autoloading_fixtures/loads_constant.rb2
-rw-r--r--activesupport/test/autoloading_fixtures/module_folder/inline_class.rb2
-rw-r--r--activesupport/test/autoloading_fixtures/module_folder/nested_class.rb2
-rw-r--r--activesupport/test/autoloading_fixtures/module_folder/nested_sibling.rb2
-rw-r--r--activesupport/test/autoloading_fixtures/module_with_custom_const_missing/a/b.rb2
-rw-r--r--activesupport/test/autoloading_fixtures/multiple_constant_file.rb2
-rw-r--r--activesupport/test/autoloading_fixtures/prepend.rb2
-rw-r--r--activesupport/test/autoloading_fixtures/prepend/sub_class_conflict.rb2
-rw-r--r--activesupport/test/autoloading_fixtures/raises_arbitrary_exception.rb2
-rw-r--r--activesupport/test/autoloading_fixtures/raises_name_error.rb2
-rw-r--r--activesupport/test/autoloading_fixtures/raises_no_method_error.rb2
-rw-r--r--activesupport/test/autoloading_fixtures/requires_constant.rb2
-rw-r--r--activesupport/test/autoloading_fixtures/should_not_be_required.rb2
-rw-r--r--activesupport/test/autoloading_fixtures/throws.rb2
-rw-r--r--activesupport/test/autoloading_fixtures/typo.rb2
-rw-r--r--activesupport/test/benchmarkable_test.rb2
-rw-r--r--activesupport/test/broadcast_logger_test.rb2
-rw-r--r--activesupport/test/cache/behaviors.rb9
-rw-r--r--activesupport/test/cache/behaviors/autoloading_cache_behavior.rb43
-rw-r--r--activesupport/test/cache/behaviors/cache_delete_matched_behavior.rb15
-rw-r--r--activesupport/test/cache/behaviors/cache_increment_decrement_behavior.rb27
-rw-r--r--activesupport/test/cache/behaviors/cache_store_behavior.rb353
-rw-r--r--activesupport/test/cache/behaviors/cache_store_version_behavior.rb88
-rw-r--r--activesupport/test/cache/behaviors/encoded_key_cache_behavior.rb36
-rw-r--r--activesupport/test/cache/behaviors/local_cache_behavior.rb132
-rw-r--r--activesupport/test/cache/cache_entry_test.rb37
-rw-r--r--activesupport/test/cache/cache_key_test.rb90
-rw-r--r--activesupport/test/cache/cache_store_logger_test.rb36
-rw-r--r--activesupport/test/cache/cache_store_namespace_test.rb40
-rw-r--r--activesupport/test/cache/cache_store_setting_test.rb68
-rw-r--r--activesupport/test/cache/cache_store_write_multi_test.rb62
-rw-r--r--activesupport/test/cache/local_cache_middleware_test.rb63
-rw-r--r--activesupport/test/cache/stores/file_store_test.rb131
-rw-r--r--activesupport/test/cache/stores/mem_cache_store_test.rb92
-rw-r--r--activesupport/test/cache/stores/memory_store_test.rb109
-rw-r--r--activesupport/test/cache/stores/null_store_test.rb59
-rw-r--r--activesupport/test/cache/stores/redis_cache_store_test.rb151
-rw-r--r--activesupport/test/caching_test.rb1192
-rw-r--r--activesupport/test/callback_inheritance_test.rb2
-rw-r--r--activesupport/test/callbacks_test.rb26
-rw-r--r--activesupport/test/class_cache_test.rb2
-rw-r--r--activesupport/test/clean_backtrace_test.rb6
-rw-r--r--activesupport/test/clean_logger_test.rb2
-rw-r--r--activesupport/test/concern_test.rb2
-rw-r--r--activesupport/test/concurrency/load_interlock_aware_monitor_test.rb55
-rw-r--r--activesupport/test/configurable_test.rb2
-rw-r--r--activesupport/test/constantize_test_cases.rb2
-rw-r--r--activesupport/test/core_ext/array/access_test.rb2
-rw-r--r--activesupport/test/core_ext/array/conversions_test.rb10
-rw-r--r--activesupport/test/core_ext/array/extract_options_test.rb2
-rw-r--r--activesupport/test/core_ext/array/grouping_test.rb4
-rw-r--r--activesupport/test/core_ext/array/prepend_append_test.rb2
-rw-r--r--activesupport/test/core_ext/array/wrap_test.rb2
-rw-r--r--activesupport/test/core_ext/bigdecimal_test.rb4
-rw-r--r--activesupport/test/core_ext/class/attribute_test.rb12
-rw-r--r--activesupport/test/core_ext/class_test.rb2
-rw-r--r--activesupport/test/core_ext/date_and_time_behavior.rb100
-rw-r--r--activesupport/test/core_ext/date_and_time_compatibility_test.rb168
-rw-r--r--activesupport/test/core_ext/date_ext_test.rb16
-rw-r--r--activesupport/test/core_ext/date_time_ext_test.rb44
-rw-r--r--activesupport/test/core_ext/digest/uuid_test.rb2
-rw-r--r--activesupport/test/core_ext/duration_test.rb250
-rw-r--r--activesupport/test/core_ext/enumerable_test.rb8
-rw-r--r--activesupport/test/core_ext/file_test.rb2
-rw-r--r--activesupport/test/core_ext/hash/transform_keys_test.rb2
-rw-r--r--activesupport/test/core_ext/hash/transform_values_test.rb2
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb690
-rw-r--r--activesupport/test/core_ext/integer_ext_test.rb2
-rw-r--r--activesupport/test/core_ext/kernel/concern_test.rb2
-rw-r--r--activesupport/test/core_ext/kernel_test.rb2
-rw-r--r--activesupport/test/core_ext/load_error_test.rb4
-rw-r--r--activesupport/test/core_ext/marshal_test.rb15
-rw-r--r--activesupport/test/core_ext/module/anonymous_test.rb2
-rw-r--r--activesupport/test/core_ext/module/attr_internal_test.rb2
-rw-r--r--activesupport/test/core_ext/module/attribute_accessor_per_thread_test.rb2
-rw-r--r--activesupport/test/core_ext/module/attribute_accessor_test.rb40
-rw-r--r--activesupport/test/core_ext/module/attribute_aliasing_test.rb2
-rw-r--r--activesupport/test/core_ext/module/concerning_test.rb2
-rw-r--r--activesupport/test/core_ext/module/introspection_test.rb2
-rw-r--r--activesupport/test/core_ext/module/reachable_test.rb30
-rw-r--r--activesupport/test/core_ext/module/remove_method_test.rb10
-rw-r--r--activesupport/test/core_ext/module_test.rb88
-rw-r--r--activesupport/test/core_ext/name_error_test.rb2
-rw-r--r--activesupport/test/core_ext/numeric_ext_test.rb14
-rw-r--r--activesupport/test/core_ext/object/acts_like_test.rb2
-rw-r--r--activesupport/test/core_ext/object/blank_test.rb4
-rw-r--r--activesupport/test/core_ext/object/deep_dup_test.rb2
-rw-r--r--activesupport/test/core_ext/object/duplicable_test.rb13
-rw-r--r--activesupport/test/core_ext/object/inclusion_test.rb2
-rw-r--r--activesupport/test/core_ext/object/instance_variables_test.rb6
-rw-r--r--activesupport/test/core_ext/object/json_cherry_pick_test.rb2
-rw-r--r--activesupport/test/core_ext/object/json_gem_encoding_test.rb2
-rw-r--r--activesupport/test/core_ext/object/to_param_test.rb2
-rw-r--r--activesupport/test/core_ext/object/to_query_test.rb2
-rw-r--r--activesupport/test/core_ext/object/try_test.rb2
-rw-r--r--activesupport/test/core_ext/range_ext_test.rb22
-rw-r--r--activesupport/test/core_ext/regexp_ext_test.rb2
-rw-r--r--activesupport/test/core_ext/secure_random_test.rb2
-rw-r--r--activesupport/test/core_ext/string_ext_test.rb43
-rw-r--r--activesupport/test/core_ext/time_ext_test.rb59
-rw-r--r--activesupport/test/core_ext/time_with_zone_test.rb78
-rw-r--r--activesupport/test/core_ext/uri_ext_test.rb2
-rw-r--r--activesupport/test/current_attributes_test.rb105
-rw-r--r--activesupport/test/dependencies/check_warnings.rb2
-rw-r--r--activesupport/test/dependencies/conflict.rb2
-rw-r--r--activesupport/test/dependencies/cross_site_depender.rb2
-rw-r--r--activesupport/test/dependencies/mutual_one.rb2
-rw-r--r--activesupport/test/dependencies/mutual_two.rb2
-rw-r--r--activesupport/test/dependencies/raises_exception.rb2
-rw-r--r--activesupport/test/dependencies/raises_exception_without_blame_file.rb2
-rw-r--r--activesupport/test/dependencies/requires_nonexistent0.rb2
-rw-r--r--activesupport/test/dependencies/requires_nonexistent1.rb2
-rw-r--r--activesupport/test/dependencies/service_one.rb2
-rw-r--r--activesupport/test/dependencies/service_two.rb2
-rw-r--r--activesupport/test/dependencies_test.rb23
-rw-r--r--activesupport/test/dependencies_test_helpers.rb4
-rw-r--r--activesupport/test/deprecation/method_wrappers_test.rb24
-rw-r--r--activesupport/test/deprecation/proxy_wrappers_test.rb2
-rw-r--r--activesupport/test/deprecation_test.rb60
-rw-r--r--activesupport/test/descendants_tracker_test_cases.rb2
-rw-r--r--activesupport/test/descendants_tracker_with_autoloading_test.rb2
-rw-r--r--activesupport/test/descendants_tracker_without_autoloading_test.rb2
-rw-r--r--activesupport/test/digest_test.rb27
-rw-r--r--activesupport/test/encrypted_configuration_test.rb67
-rw-r--r--activesupport/test/encrypted_file_test.rb59
-rw-r--r--activesupport/test/evented_file_update_checker_test.rb2
-rw-r--r--activesupport/test/executor_test.rb2
-rw-r--r--activesupport/test/file_update_checker_shared_tests.rb2
-rw-r--r--activesupport/test/file_update_checker_test.rb2
-rw-r--r--activesupport/test/fixtures/autoload/another_class.rb2
-rw-r--r--activesupport/test/fixtures/autoload/some_class.rb2
-rw-r--r--activesupport/test/gzip_test.rb12
-rw-r--r--activesupport/test/hash_with_indifferent_access_test.rb803
-rw-r--r--activesupport/test/i18n_test.rb2
-rw-r--r--activesupport/test/inflector_test.rb41
-rw-r--r--activesupport/test/inflector_test_cases.rb20
-rw-r--r--activesupport/test/json/decoding_test.rb2
-rw-r--r--activesupport/test/json/encoding_test.rb18
-rw-r--r--activesupport/test/json/encoding_test_cases.rb6
-rw-r--r--activesupport/test/key_generator_test.rb2
-rw-r--r--activesupport/test/lazy_load_hooks_test.rb44
-rw-r--r--activesupport/test/log_subscriber_test.rb2
-rw-r--r--activesupport/test/logger_test.rb6
-rw-r--r--activesupport/test/message_encryptor_test.rb140
-rw-r--r--activesupport/test/message_verifier_test.rb121
-rw-r--r--activesupport/test/messages/rotation_configuration_test.rb25
-rw-r--r--activesupport/test/metadata/shared_metadata_tests.rb93
-rw-r--r--activesupport/test/multibyte_chars_test.rb31
-rw-r--r--activesupport/test/multibyte_conformance_test.rb2
-rw-r--r--activesupport/test/multibyte_grapheme_break_conformance_test.rb2
-rw-r--r--activesupport/test/multibyte_normalization_conformance_test.rb2
-rw-r--r--activesupport/test/multibyte_proxy_test.rb2
-rw-r--r--activesupport/test/multibyte_test_helpers.rb4
-rw-r--r--activesupport/test/multibyte_unicode_database_test.rb2
-rw-r--r--activesupport/test/notifications/evented_notification_test.rb2
-rw-r--r--activesupport/test/notifications/instrumenter_test.rb2
-rw-r--r--activesupport/test/notifications_test.rb2
-rw-r--r--activesupport/test/number_helper_i18n_test.rb20
-rw-r--r--activesupport/test/number_helper_test.rb20
-rw-r--r--activesupport/test/option_merger_test.rb2
-rw-r--r--activesupport/test/ordered_hash_test.rb2
-rw-r--r--activesupport/test/ordered_options_test.rb15
-rw-r--r--activesupport/test/reloader_test.rb2
-rw-r--r--activesupport/test/rescuable_test.rb36
-rw-r--r--activesupport/test/safe_buffer_test.rb2
-rw-r--r--activesupport/test/security_utils_test.rb14
-rw-r--r--activesupport/test/share_lock_test.rb2
-rw-r--r--activesupport/test/string_inquirer_test.rb2
-rw-r--r--activesupport/test/subscriber_test.rb2
-rw-r--r--activesupport/test/tagged_logging_test.rb2
-rw-r--r--activesupport/test/test_case_test.rb15
-rw-r--r--activesupport/test/testing/constant_lookup_test.rb2
-rw-r--r--activesupport/test/testing/file_fixtures_test.rb10
-rw-r--r--activesupport/test/testing/method_call_assertions_test.rb2
-rw-r--r--activesupport/test/time_travel_test.rb28
-rw-r--r--activesupport/test/time_zone_test.rb234
-rw-r--r--activesupport/test/time_zone_test_helpers.rb2
-rw-r--r--activesupport/test/transliterate_test.rb2
-rw-r--r--activesupport/test/xml_mini/jdom_engine_test.rb4
-rw-r--r--activesupport/test/xml_mini/libxml_engine_test.rb4
-rw-r--r--activesupport/test/xml_mini/libxmlsax_engine_test.rb2
-rw-r--r--activesupport/test/xml_mini/nokogiri_engine_test.rb2
-rw-r--r--activesupport/test/xml_mini/nokogirisax_engine_test.rb2
-rw-r--r--activesupport/test/xml_mini/rexml_engine_test.rb2
-rw-r--r--activesupport/test/xml_mini/xml_mini_engine_test.rb7
-rw-r--r--activesupport/test/xml_mini_test.rb4
449 files changed, 8341 insertions, 3084 deletions
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 37585b8c46..fccaeb5d32 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,407 +1,486 @@
-* Deprecate `.halt_and_display_warning_on_return_false`.
+* Allow the hash function used to generate non-sensitive digests, such as the
+ ETag header, to be specified with `config.active_support.hash_digest_class`.
- *Rafael Mendonça França*
-
-* Remove deprecated behavior that halts callbacks when the return is false.
+ The object provided must respond to `#hexdigest`, e.g. `Digest::SHA1`.
- *Rafael Mendonça França*
+ *Dmitri Dolguikh*
+
+## Rails 5.2.0.beta2 (November 28, 2017) ##
-* Deprecate passing string to `:if` and `:unless` conditional options
- on `set_callback` and `skip_callback`.
+* No changes.
- *Ryuta Kamizono*
-* Raise `ArgumentError` when passing string to define callback.
+## Rails 5.2.0.beta1 (November 27, 2017) ##
- *Ryuta Kamizono*
+* Changed default behaviour of `ActiveSupport::SecurityUtils.secure_compare`,
+ to make it not leak length information even for variable length string.
-* Updated Unicode version to 9.0.0
+ Renamed old `ActiveSupport::SecurityUtils.secure_compare` to `fixed_length_secure_compare`,
+ and started raising `ArgumentError` in case of length mismatch of passed strings.
- Now we can handle new emojis such like "👩‍👩‍👧‍👦" ("\u{1F469}\u{200D}\u{1F469}\u{200D}\u{1F467}\u{200D}\u{1F466}").
+ *Vipul A M*
- version 8.0.0
+* Make `ActiveSupport::TimeZone.all` return only time zones that are in
+ `ActiveSupport::TimeZone::MAPPING`.
- "👩‍👩‍👧‍👦".mb_chars.grapheme_length # => 4
- "👩‍👩‍👧‍👦".mb_chars.reverse # => "👦👧‍👩‍👩‍"
+ Fixes #7245.
- version 9.0.0
+ *Chris LaRose*
- "👩‍👩‍👧‍👦".mb_chars.grapheme_length # => 1
- "👩‍👩‍👧‍👦".mb_chars.reverse # => "👩‍👩‍👧‍👦"
+* MemCacheStore: Support expiring counters.
- *Fumiaki MATSUSHIMA*
+ Pass `expires_in: [seconds]` to `#increment` and `#decrement` options
+ to set the Memcached TTL (time-to-live) if the counter doesn't exist.
+ If the counter exists, Memcached doesn't extend its expiry when it's
+ incremented or decremented.
-* Changed `ActiveSupport::Inflector#transliterate` to raise `ArgumentError` when it receives
- anything except a string.
+ ```
+ Rails.cache.increment("my_counter", 1, expires_in: 2.minutes)
+ ```
- *Kevin McPhillips*
+ *Takumasa Ochi*
-* Fixed bugs that `StringInquirer#respond_to_missing?` and
- `ArrayInquirer#respond_to_missing?` do not fallback to `super`.
+* Handle `TZInfo::AmbiguousTime` errors
- *Akira Matsuda*
+ Make `ActiveSupport::TimeWithZone` match Ruby's handling of ambiguous
+ times by choosing the later period, e.g.
-* Fix inconsistent results when parsing large durations and constructing durations from code
+ Ruby:
+ ```
+ ENV["TZ"] = "Europe/Moscow"
+ Time.local(2014, 10, 26, 1, 0, 0) # => 2014-10-26 01:00:00 +0300
+ ```
- ActiveSupport::Duration.parse('P3Y') == 3.years # It should be true
+ Before:
+ ```
+ >> "2014-10-26 01:00:00".in_time_zone("Moscow")
+ TZInfo::AmbiguousTime: 26/10/2014 01:00 is an ambiguous local time.
+ ```
- Duration parsing made independent from any moment of time:
- Fixed length in seconds is assigned to each duration part during parsing.
+ After:
+ ```
+ >> "2014-10-26 01:00:00".in_time_zone("Moscow")
+ => Sun, 26 Oct 2014 01:00:00 MSK +03:00
+ ```
- Changed duration of months and years in seconds to more accurate and logical:
+ Fixes #17395.
- 1. The value of 365.2425 days in Gregorian year is more accurate
- as it accounts for every 400th non-leap year.
+ *Andrew White*
- 2. Month's length is bound to year's duration, which makes
- sensible comparisons like `12.months == 1.year` to be `true`
- and nonsensical ones like `30.days == 1.month` to be `false`.
+* Redis cache store.
+
+ ```
+ # Defaults to `redis://localhost:6379/0`. Only use for dev/test.
+ config.cache_store = :redis_cache_store
+
+ # Supports all common cache store options (:namespace, :compress,
+ # :compress_threshold, :expires_in, :race_condition_ttl) and all
+ # Redis options.
+ cache_password = Rails.application.secrets.redis_cache_password
+ config.cache_store = :redis_cache_store, driver: :hiredis,
+ namespace: 'myapp-cache', compress: true, timeout: 1,
+ url: "redis://:#{cache_password}@myapp-cache-1:6379/0"
+
+ # Supports Redis::Distributed with multiple hosts
+ config.cache_store = :redis_cache_store, driver: :hiredis
+ namespace: 'myapp-cache', compress: true,
+ url: %w[
+ redis://myapp-cache-1:6379/0
+ redis://myapp-cache-1:6380/0
+ redis://myapp-cache-2:6379/0
+ redis://myapp-cache-2:6380/0
+ redis://myapp-cache-3:6379/0
+ redis://myapp-cache-3:6380/0
+ ]
+
+ # Or pass a builder block
+ config.cache_store = :redis_cache_store,
+ namespace: 'myapp-cache', compress: true,
+ redis: -> { Redis.new … }
+ ```
+
+ Deployment note: Take care to use a *dedicated Redis cache* rather
+ than pointing this at your existing Redis server. It won't cope well
+ with mixed usage patterns and it won't expire cache entries by default.
+
+ Redis cache server setup guide: https://redis.io/topics/lru-cache
- Calculations on times and dates with durations shouldn't be affected as
- duration's numeric value isn't used in calculations, only parts are used.
+ *Jeremy Daer*
- Methods on `Numeric` like `2.days` now use these predefined durations
- to avoid duplicating of duration constants through the codebase and
- eliminate creation of intermediate durations.
+* Cache: Enable compression by default for values > 1kB.
- *Andrey Novikov, Andrew White*
+ Compression has long been available, but opt-in and at a 16kB threshold.
+ It wasn't enabled by default due to CPU cost. Today it's cheap and typical
+ cache data is eminently compressible, such as HTML or JSON fragments.
+ Compression dramatically reduces Memcached/Redis mem usage, which means
+ the same cache servers can store more data, which means higher hit rates.
-* Change return value of `Rational#duplicable?`, `ComplexClass#duplicable?`
- to false.
+ To disable compression, pass `compress: false` to the initializer.
- *utilum*
+ *Jeremy Daer*
-* Change return value of `NilClass#duplicable?`, `FalseClass#duplicable?`,
- `TrueClass#duplicable?`, `Symbol#duplicable?` and `Numeric#duplicable?`
- to true with Ruby 2.4+. These classes can dup with Ruby 2.4+.
+* Allow `Range#include?` on TWZ ranges
- *Yuji Yaginuma*
+ In #11474 we prevented TWZ ranges being iterated over which matched
+ Ruby's handling of Time ranges and as a consequence `include?`
+ stopped working with both Time ranges and TWZ ranges. However in
+ ruby/ruby@b061634 support was added for `include?` to use `cover?`
+ for 'linear' objects. Since we have no way of making Ruby consider
+ TWZ instances as 'linear' we have to override `Range#include?`.
-* Remove deprecated class `ActiveSupport::Concurrency::Latch`.
+ Fixes #30799.
*Andrew White*
-* Remove deprecated separator argument from `parameterize`.
+* Fix acronym support in `humanize`
- *Andrew White*
+ Acronym inflections are stored with lowercase keys in the hash but
+ the match wasn't being lowercased before being looked up in the hash.
+ This shouldn't have any performance impact because before it would
+ fail to find the acronym and perform the `downcase` operation anyway.
-* Remove deprecated method `Numeric#to_formatted_s`.
+ Fixes #31052.
*Andrew White*
-* Remove deprecated method `alias_method_chain`.
+* Add same method signature for `Time#prev_year` and `Time#next_year`
+ in accordance with `Date#prev_year`, `Date#next_year`.
- *Andrew White*
+ Allows pass argument for `Time#prev_year` and `Time#next_year`.
-* Remove deprecated constant `MissingSourceFile`.
+ Before:
+ ```
+ Time.new(2017, 9, 16, 17, 0).prev_year # => 2016-09-16 17:00:00 +0300
+ Time.new(2017, 9, 16, 17, 0).prev_year(1)
+ # => ArgumentError: wrong number of arguments (given 1, expected 0)
- *Andrew White*
+ Time.new(2017, 9, 16, 17, 0).next_year # => 2018-09-16 17:00:00 +0300
+ Time.new(2017, 9, 16, 17, 0).next_year(1)
+ # => ArgumentError: wrong number of arguments (given 1, expected 0)
+ ```
-* Remove deprecated methods `Module.qualified_const_defined?`,
- `Module.qualified_const_get` and `Module.qualified_const_set`.
+ After:
+ ```
+ Time.new(2017, 9, 16, 17, 0).prev_year # => 2016-09-16 17:00:00 +0300
+ Time.new(2017, 9, 16, 17, 0).prev_year(1) # => 2016-09-16 17:00:00 +0300
- *Andrew White*
+ Time.new(2017, 9, 16, 17, 0).next_year # => 2018-09-16 17:00:00 +0300
+ Time.new(2017, 9, 16, 17, 0).next_year(1) # => 2018-09-16 17:00:00 +0300
+ ```
-* Remove deprecated `:prefix` option from `number_to_human_size`.
+ *bogdanvlviv*
- *Andrew White*
+* Add same method signature for `Time#prev_month` and `Time#next_month`
+ in accordance with `Date#prev_month`, `Date#next_month`.
-* Remove deprecated method `ActiveSupport::HashWithIndifferentAccess.new_from_hash_copying_default`.
+ Allows pass argument for `Time#prev_month` and `Time#next_month`.
- *Andrew White*
+ Before:
+ ```
+ Time.new(2017, 9, 16, 17, 0).prev_month # => 2017-08-16 17:00:00 +0300
+ Time.new(2017, 9, 16, 17, 0).prev_month(1)
+ # => ArgumentError: wrong number of arguments (given 1, expected 0)
-* Remove deprecated file `active_support/core_ext/time/marshal.rb`.
+ Time.new(2017, 9, 16, 17, 0).next_month # => 2017-10-16 17:00:00 +0300
+ Time.new(2017, 9, 16, 17, 0).next_month(1)
+ # => ArgumentError: wrong number of arguments (given 1, expected 0)
+ ```
- *Andrew White*
+ After:
+ ```
+ Time.new(2017, 9, 16, 17, 0).prev_month # => 2017-08-16 17:00:00 +0300
+ Time.new(2017, 9, 16, 17, 0).prev_month(1) # => 2017-08-16 17:00:00 +0300
-* Remove deprecated file `active_support/core_ext/struct.rb`.
+ Time.new(2017, 9, 16, 17, 0).next_month # => 2017-10-16 17:00:00 +0300
+ Time.new(2017, 9, 16, 17, 0).next_month(1) # => 2017-10-16 17:00:00 +0300
+ ```
- *Andrew White*
+ *bogdanvlviv*
-* Remove deprecated file `active_support/core_ext/module/method_transplanting.rb`.
+* Add same method signature for `Time#prev_day` and `Time#next_day`
+ in accordance with `Date#prev_day`, `Date#next_day`.
- *Andrew White*
+ Allows pass argument for `Time#prev_day` and `Time#next_day`.
-* Remove deprecated method `Module.local_constants`.
+ Before:
+ ```
+ Time.new(2017, 9, 16, 17, 0).prev_day # => 2017-09-15 17:00:00 +0300
+ Time.new(2017, 9, 16, 17, 0).prev_day(1)
+ # => ArgumentError: wrong number of arguments (given 1, expected 0)
- *Andrew White*
+ Time.new(2017, 9, 16, 17, 0).next_day # => 2017-09-17 17:00:00 +0300
+ Time.new(2017, 9, 16, 17, 0).next_day(1)
+ # => ArgumentError: wrong number of arguments (given 1, expected 0)
+ ```
-* Remove deprecated file `active_support/core_ext/kernel/debugger.rb`.
+ After:
+ ```
+ Time.new(2017, 9, 16, 17, 0).prev_day # => 2017-09-15 17:00:00 +0300
+ Time.new(2017, 9, 16, 17, 0).prev_day(1) # => 2017-09-15 17:00:00 +0300
- *Andrew White*
+ Time.new(2017, 9, 16, 17, 0).next_day # => 2017-09-17 17:00:00 +0300
+ Time.new(2017, 9, 16, 17, 0).next_day(1) # => 2017-09-17 17:00:00 +0300
+ ```
-* Remove deprecated method `ActiveSupport::Cache::Store#namespaced_key`.
+ *bogdanvlviv*
- *Andrew White*
+* `IO#to_json` now returns the `to_s` representation, rather than
+ attempting to convert to an array. This fixes a bug where `IO#to_json`
+ would raise an `IOError` when called on an unreadable object.
-* Remove deprecated method `ActiveSupport::Cache::Strategy::LocalCache::LocalStore#set_cache_value`.
+ Fixes #26132.
- *Andrew White*
+ *Paul Kuruvilla*
-* Remove deprecated method `ActiveSupport::Cache::MemCacheStore#escape_key`.
+* Remove deprecated `halt_callback_chains_on_return_false` option.
- *Andrew White*
+ *Rafael Mendonça França*
-* Remove deprecated method `ActiveSupport::Cache::FileStore#key_file_path`.
+* Remove deprecated `:if` and `:unless` string filter for callbacks.
- *Andrew White*
+ *Rafael Mendonça França*
-* Ensure duration parsing is consistent across DST changes.
+* `Hash#slice` now falls back to Ruby 2.5+'s built-in definition if defined.
- Previously `ActiveSupport::Duration.parse` used `Time.current` and
- `Time#advance` to calculate the number of seconds in the duration
- from an arbitrary collection of parts. However as `advance` tries to
- be consistent across DST boundaries this meant that either the
- duration was shorter or longer depending on the time of year.
+ *Akira Matsuda*
- This was fixed by using an absolute reference point in UTC which
- isn't subject to DST transitions. An arbitrary date of Jan 1st, 2000
- was chosen for no other reason that it seemed appropriate.
+* Deprecate `secrets.secret_token`.
- Additionally, duration parsing should now be marginally faster as we
- are no longer creating instances of `ActiveSupport::TimeWithZone`
- every time we parse a duration string.
+ The architecture for secrets had a big upgrade between Rails 3 and Rails 4,
+ when the default changed from using `secret_token` to `secret_key_base`.
- Fixes #26941.
+ `secret_token` has been soft deprecated in documentation for four years
+ but is still in place to support apps created before Rails 4.
+ Deprecation warnings have been added to help developers upgrade their
+ applications to `secret_key_base`.
- *Andrew White*
+ *claudiob*, *Kasper Timm Hansen*
-* Use `Hash#compact` and `Hash#compact!` from Ruby 2.4. Old Ruby versions
- will continue to get these methods from Active Support as before.
+* Return an instance of `HashWithIndifferentAccess` from `HashWithIndifferentAccess#transform_keys`.
- *Prathamesh Sonpatki*
+ *Yuji Yaginuma*
-* Fix `ActiveSupport::TimeZone#strptime`.
- Support for timestamps in format of seconds (%s) and milliseconds (%Q).
+* Add key rotation support to `MessageEncryptor` and `MessageVerifier`
- Fixes #26840.
+ This change introduces a `rotate` method to both the `MessageEncryptor` and
+ `MessageVerifier` classes. This method accepts the same arguments and
+ options as the given classes' constructor. The `encrypt_and_verify` method
+ for `MessageEncryptor` and the `verified` method for `MessageVerifier` also
+ accept an optional keyword argument `:on_rotation` block which is called
+ when a rotated instance is used to decrypt or verify the message.
- *Lev Denisov*
+ *Michael J Coyne*
-* Fix `DateAndTime::Calculations#copy_time_to`. Copy `nsec` instead of `usec`.
+* Deprecate `Module#reachable?` method.
- Jumping forward or backward between weeks now preserves nanosecond digits.
+ *bogdanvlviv*
- *Josua Schmid*
+* Add `config/credentials.yml.enc` to store production app secrets.
-* Fix `ActiveSupport::TimeWithZone#in` across DST boundaries.
+ Allows saving any authentication credentials for third party services
+ directly in repo encrypted with `config/master.key` or `ENV["RAILS_MASTER_KEY"]`.
- Previously calls to `in` were being sent to the non-DST aware
- method `Time#since` via `method_missing`. It is now aliased to
- the DST aware `ActiveSupport::TimeWithZone#+` which handles
- transitions across DST boundaries, e.g:
+ This will eventually replace `Rails.application.secrets` and the encrypted
+ secrets introduced in Rails 5.1.
- Time.zone = "US/Eastern"
+ *DHH*, *Kasper Timm Hansen*
- t = Time.zone.local(2016,11,6,1)
- # => Sun, 06 Nov 2016 01:00:00 EDT -05:00
+* Add `ActiveSupport::EncryptedFile` and `ActiveSupport::EncryptedConfiguration`.
- t.in(1.hour)
- # => Sun, 06 Nov 2016 01:00:00 EST -05:00
+ Allows for stashing encrypted files or configuration directly in repo by
+ encrypting it with a key.
- Fixes #26580.
+ Backs the new credentials setup above, but can also be used independently.
- *Thomas Balthazar*
+ *DHH*, *Kasper Timm Hansen*
-* Remove unused parameter `options = nil` for `#clear` of
- `ActiveSupport::Cache::Strategy::LocalCache::LocalStore` and
- `ActiveSupport::Cache::Strategy::LocalCache`.
+* `Module#delegate_missing_to` now raises `DelegationError` if target is nil,
+ similar to `Module#delegate`.
- *Yosuke Kabuto*
+ *Anton Khamets*
-* Fix `thread_mattr_accessor` subclass no longer overwrites parent.
+* Update `String#camelize` to provide feedback when wrong option is passed
- Assigning a value to a subclass using `thread_mattr_accessor` no
- longer changes the value of the parent class. This brings the
- behavior inline with the documentation.
+ `String#camelize` was returning nil without any feedback when an
+ invalid option was passed as a parameter.
- Given:
+ Previously:
- class Account
- thread_mattr_accessor :user
- end
+ 'one_two'.camelize(true)
+ # => nil
- class Customer < Account
- end
+ Now:
- Account.user = "DHH"
- Customer.user = "Rafael"
+ 'one_two'.camelize(true)
+ # => ArgumentError: Invalid option, use either :upper or :lower.
- Before:
+ *Ricardo Díaz*
- Account.user # => "Rafael"
+* Fix modulo operations involving durations
- After:
+ Rails 5.1 introduced `ActiveSupport::Duration::Scalar` as a wrapper
+ around numeric values as a way of ensuring a duration was the outcome of
+ an expression. However, the implementation was missing support for modulo
+ operations. This support has now been added and should result in a duration
+ being returned from expressions involving modulo operations.
- Account.user # => "DHH"
+ Prior to Rails 5.1:
- *Shinichi Maeshima*
+ 5.minutes % 2.minutes
+ # => 60
-* Since weeks are no longer converted to days, add `:weeks` to the list of
- parts that `ActiveSupport::TimeWithZone` will recognize as possibly being
- of variable duration to take account of DST transitions.
+ Now:
- Fixes #26039.
+ 5.minutes % 2.minutes
+ # => 1 minute
+
+ Fixes #29603 and #29743.
+
+ *Sayan Chakraborty*, *Andrew White*
+
+* Fix division where a duration is the denominator
+
+ PR #29163 introduced a change in behavior when a duration was the denominator
+ in a calculation - this was incorrect as dividing by a duration should always
+ return a `Numeric`. The behavior of previous versions of Rails has been restored.
+
+ Fixes #29592.
*Andrew White*
-* Defines `Regexp.match?` for Ruby versions prior to 2.4. The predicate
- has the same interface, but it does not have the performance boost. Its
- purpose is to be able to write 2.4 compatible code.
+* Add purpose and expiry support to `ActiveSupport::MessageVerifier` &
+ `ActiveSupport::MessageEncryptor`.
+
+ For instance, to ensure a message is only usable for one intended purpose:
+
+ token = @verifier.generate("x", purpose: :shipping)
- *Xavier Noria*
+ @verifier.verified(token, purpose: :shipping) # => "x"
+ @verifier.verified(token) # => nil
-* Allow `MessageEncryptor` to take advantage of authenticated encryption modes.
+ Or make it expire after a set time:
- AEAD modes like `aes-256-gcm` provide both confidentiality and data
- authenticity, eliminating the need to use `MessageVerifier` to check if the
- encrypted data has been tampered with. This speeds up encryption/decryption
- and results in shorter cipher text.
+ @verifier.generate("x", expires_in: 1.month)
+ @verifier.generate("y", expires_at: Time.now.end_of_year)
- *Bart de Water*
+ Showcased with `ActiveSupport::MessageVerifier`, but works the same for
+ `ActiveSupport::MessageEncryptor`'s `encrypt_and_sign` and `decrypt_and_verify`.
-* Introduce `assert_changes` and `assert_no_changes`.
+ Pull requests: #29599, #29854
- `assert_changes` is a more general `assert_difference` that works with any
- value.
+ *Assain Jaleel*
- assert_changes 'Error.current', from: nil, to: 'ERR' do
- expected_bad_operation
- end
+* Make the order of `Hash#reverse_merge!` consistent with `HashWithIndifferentAccess`.
- Can be called with strings, to be evaluated in the binding (context) of
- the block given to the assertion, or a lambda.
+ *Erol Fornoles*
- assert_changes -> { Error.current }, from: nil, to: 'ERR' do
- expected_bad_operation
- end
+* Add `freeze_time` helper which freezes time to `Time.now` in tests.
- The `from` and `to` arguments are compared with the case operator (`===`).
+ *Prathamesh Sonpatki*
+
+* Default `ActiveSupport::MessageEncryptor` to use AES 256 GCM encryption.
+
+ On for new Rails 5.2 apps. Upgrading apps can find the config as a new
+ framework default.
+
+ *Assain Jaleel*
- assert_changes 'Error.current', from: nil, to: Error do
- expected_bad_operation
- end
+* Cache: `write_multi`
- This is pretty useful, if you need to loosely compare a value. For example,
- you need to test a token has been generated and it has that many random
- characters.
+ Rails.cache.write_multi foo: 'bar', baz: 'qux'
+
+ Plus faster fetch_multi with stores that implement `write_multi_entries`.
+ Keys that aren't found may be written to the cache store in one shot
+ instead of separate writes.
+
+ The default implementation simply calls `write_entry` for each entry.
+ Stores may override if they're capable of one-shot bulk writes, like
+ Redis `MSET`.
+
+ *Jeremy Daer*
- user = User.start_registration
- assert_changes 'user.token', to: /\w{32}/ do
- user.finish_registration
- end
+* Add default option to module and class attribute accessors.
+
+ mattr_accessor :settings, default: {}
+
+ Works for `mattr_reader`, `mattr_writer`, `cattr_accessor`, `cattr_reader`,
+ and `cattr_writer` as well.
*Genadi Samokovarov*
-* Fix `ActiveSupport::TimeZone#strptime`. Now raises `ArgumentError` when the
- given time doesn't match the format. The error is the same as the one given
- by Ruby's `Date.strptime`. Previously it raised
- `NoMethodError: undefined method empty? for nil:NilClass.` due to a bug.
+* Add `Date#prev_occurring` and `Date#next_occurring` to return specified next/previous occurring day of week.
- Fixes #25701.
+ *Shota Iguchi*
- *John Gesimondo*
+* Add default option to `class_attribute`.
-* `travel/travel_to` travel time helpers, now raise on nested calls,
- as this can lead to confusing time stubbing.
+ Before:
- Instead of:
+ class_attribute :settings
+ self.settings = {}
- travel_to 2.days.from_now do
- # 2 days from today
- travel_to 3.days.from_now do
- # 5 days from today
- end
- end
+ Now:
- preferred way to achieve above is:
+ class_attribute :settings, default: {}
- travel 2.days do
- # 2 days from today
- end
+ *DHH*
- travel 5.days do
- # 5 days from today
- end
+* `#singularize` and `#pluralize` now respect uncountables for the specified locale.
- *Vipul A M*
+ *Eilis Hamilton*
-* Support parsing JSON time in ISO8601 local time strings in
- `ActiveSupport::JSON.decode` when `parse_json_times` is enabled.
- Strings in the format of `YYYY-MM-DD hh:mm:ss` (without a `Z` at
- the end) will be parsed in the local timezone (`Time.zone`). In
- addition, date strings (`YYYY-MM-DD`) are now parsed into `Date`
- objects.
+* Add `ActiveSupport::CurrentAttributes` to provide a thread-isolated attributes singleton.
+ Primary use case is keeping all the per-request attributes easily available to the whole system.
- *Grzegorz Witek*
+ *DHH*
-* Fixed `ActiveSupport::Logger.broadcast` so that calls to `#silence` now
- properly delegate to all loggers. Silencing now properly suppresses logging
- to both the log and the console.
+* Fix implicit coercion calculations with scalars and durations
- *Kevin McPhillips*
+ Previously, calculations where the scalar is first would be converted to a duration
+ of seconds, but this causes issues with dates being converted to times, e.g:
-* Remove deprecated arguments in `assert_nothing_raised`.
+ Time.zone = "Beijing" # => Asia/Shanghai
+ date = Date.civil(2017, 5, 20) # => Mon, 20 May 2017
+ 2 * 1.day # => 172800 seconds
+ date + 2 * 1.day # => Mon, 22 May 2017 00:00:00 CST +08:00
- *Rafel Mendonça França*
+ Now, the `ActiveSupport::Duration::Scalar` calculation methods will try to maintain
+ the part structure of the duration where possible, e.g:
-* `Date.to_s` doesn't produce too many spaces. For example, `to_s(:short)`
- will now produce `01 Feb` instead of ` 1 Feb`.
+ Time.zone = "Beijing" # => Asia/Shanghai
+ date = Date.civil(2017, 5, 20) # => Mon, 20 May 2017
+ 2 * 1.day # => 2 days
+ date + 2 * 1.day # => Mon, 22 May 2017
- Fixes #25251.
+ Fixes #29160, #28970.
- *Sean Griffin*
+ *Andrew White*
-* Introduce `Module#delegate_missing_to`.
+* Add support for versioned cache entries. This enables the cache stores to recycle cache keys, greatly saving
+ on storage in cases with frequent churn. Works together with the separation of `#cache_key` and `#cache_version`
+ in Active Record and its use in Action Pack's fragment caching.
- When building a decorator, a common pattern emerges:
+ *DHH*
- class Partition
- def initialize(first_event)
- @events = [ first_event ]
- end
+* Pass gem name and deprecation horizon to deprecation notifications.
- def people
- if @events.first.detail.people.any?
- @events.collect { |e| Array(e.detail.people) }.flatten.uniq
- else
- @events.collect(&:creator).uniq
- end
- end
+ *Willem van Bergen*
- private
- def respond_to_missing?(name, include_private = false)
- @events.respond_to?(name, include_private)
- end
+* Add support for `:offset` and `:zone` to `ActiveSupport::TimeWithZone#change`
- def method_missing(method, *args, &block)
- @events.send(method, *args, &block)
- end
- end
+ *Andrew White*
- With `Module#delegate_missing_to`, the above is condensed to:
+* Add support for `:offset` to `Time#change`
- class Partition
- delegate_missing_to :@events
+ Fixes #28723.
- def initialize(first_event)
- @events = [ first_event ]
- end
+ *Andrew White*
- def people
- if @events.first.detail.people.any?
- @events.collect { |e| Array(e.detail.people) }.flatten.uniq
- else
- @events.collect(&:creator).uniq
- end
- end
- end
+* Add `fetch_values` for `HashWithIndifferentAccess`
- *Genadi Samokovarov*, *DHH*
+ The method was originally added to `Hash` in Ruby 2.3.0.
-* Rescuable: If a handler doesn't match the exception, check for handlers
- matching the exception's cause.
+ *Josh Pencheon*
- *Jeremy Daer*
-Please check [5-0-stable](https://github.com/rails/rails/blob/5-0-stable/activesupport/CHANGELOG.md) for previous changes.
+Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/activesupport/CHANGELOG.md) for previous changes.
diff --git a/activesupport/MIT-LICENSE b/activesupport/MIT-LICENSE
index 6b3cead1a7..8f769c0767 100644
--- a/activesupport/MIT-LICENSE
+++ b/activesupport/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2005-2017 David Heinemeier Hansson
+Copyright (c) 2005-2018 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/activesupport/README.rdoc b/activesupport/README.rdoc
index 14ce204303..c770324be8 100644
--- a/activesupport/README.rdoc
+++ b/activesupport/README.rdoc
@@ -21,7 +21,7 @@ Source code can be downloaded as part of the Rails project on GitHub:
Active Support is released under the MIT license:
-* http://www.opensource.org/licenses/MIT
+* https://opensource.org/licenses/MIT
== Support
@@ -30,7 +30,7 @@ API documentation is at:
* http://api.rubyonrails.org
-Bug reports can be filed for the Ruby on Rails project here:
+Bug reports for the Ruby on Rails project can be filed here:
* https://github.com/rails/rails/issues
diff --git a/activesupport/Rakefile b/activesupport/Rakefile
index 2e1c50cc3d..8672ab1542 100644
--- a/activesupport/Rakefile
+++ b/activesupport/Rakefile
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "rake/testtask"
task default: :test
diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec
index 08370cba85..db801d2da2 100644
--- a/activesupport/activesupport.gemspec
+++ b/activesupport/activesupport.gemspec
@@ -1,4 +1,6 @@
-version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip
+# frozen_string_literal: true
+
+version = File.read(File.expand_path("../RAILS_VERSION", __dir__)).strip
Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
@@ -20,6 +22,11 @@ Gem::Specification.new do |s|
s.rdoc_options.concat ["--encoding", "UTF-8"]
+ s.metadata = {
+ "source_code_uri" => "https://github.com/rails/rails/tree/v#{version}/activesupport",
+ "changelog_uri" => "https://github.com/rails/rails/blob/v#{version}/activesupport/CHANGELOG.md"
+ }
+
s.add_dependency "i18n", "~> 0.7"
s.add_dependency "tzinfo", "~> 1.1"
s.add_dependency "minitest", "~> 5.1"
diff --git a/activesupport/bin/generate_tables b/activesupport/bin/generate_tables
index aa36a01b5b..18199b2171 100755
--- a/activesupport/bin/generate_tables
+++ b/activesupport/bin/generate_tables
@@ -1,7 +1,8 @@
#!/usr/bin/env ruby
+# frozen_string_literal: true
begin
- $:.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib"))
+ $:.unshift(File.expand_path("../lib", __dir__))
require "active_support"
rescue IOError
end
diff --git a/activesupport/bin/test b/activesupport/bin/test
index a7beb14b27..c53377cc97 100755
--- a/activesupport/bin/test
+++ b/activesupport/bin/test
@@ -1,4 +1,5 @@
#!/usr/bin/env ruby
+# frozen_string_literal: true
COMPONENT_ROOT = File.expand_path("..", __dir__)
-require File.expand_path("../tools/test", COMPONENT_ROOT)
+require_relative "../../tools/test"
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index 267fa755c6..a4fb697669 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -1,5 +1,7 @@
+# frozen_string_literal: true
+
#--
-# Copyright (c) 2005-2017 David Heinemeier Hansson
+# Copyright (c) 2005-2018 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -32,6 +34,7 @@ module ActiveSupport
extend ActiveSupport::Autoload
autoload :Concern
+ autoload :CurrentAttributes
autoload :Dependencies
autoload :DescendantsTracker
autoload :ExecutionWrapper
@@ -50,6 +53,7 @@ module ActiveSupport
autoload :Callbacks
autoload :Configurable
autoload :Deprecation
+ autoload :Digest
autoload :Gzip
autoload :Inflector
autoload :JSON
@@ -79,14 +83,6 @@ module ActiveSupport
cattr_accessor :test_order # :nodoc:
- def self.halt_callback_chains_on_return_false
- Callbacks.halt_and_display_warning_on_return_false
- end
-
- def self.halt_callback_chains_on_return_false=(value)
- Callbacks.halt_and_display_warning_on_return_false = value
- end
-
def self.to_time_preserves_timezone
DateAndTime::Compatibility.preserve_timezone
end
diff --git a/activesupport/lib/active_support/all.rb b/activesupport/lib/active_support/all.rb
index 72a23075af..4adf446af8 100644
--- a/activesupport/lib/active_support/all.rb
+++ b/activesupport/lib/active_support/all.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support"
require "active_support/time"
require "active_support/core_ext"
diff --git a/activesupport/lib/active_support/array_inquirer.rb b/activesupport/lib/active_support/array_inquirer.rb
index befa1746c6..b2b9e9c0b7 100644
--- a/activesupport/lib/active_support/array_inquirer.rb
+++ b/activesupport/lib/active_support/array_inquirer.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActiveSupport
# Wrapping an array in an +ArrayInquirer+ gives a friendlier way to check
# its string-like contents:
diff --git a/activesupport/lib/active_support/backtrace_cleaner.rb b/activesupport/lib/active_support/backtrace_cleaner.rb
index e47c90597f..16dd733ddb 100644
--- a/activesupport/lib/active_support/backtrace_cleaner.rb
+++ b/activesupport/lib/active_support/backtrace_cleaner.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActiveSupport
# Backtraces often include many lines that are not relevant for the context
# under review. This makes it hard to find the signal amongst the backtrace
diff --git a/activesupport/lib/active_support/benchmarkable.rb b/activesupport/lib/active_support/benchmarkable.rb
index 70493c8da7..f481d68198 100644
--- a/activesupport/lib/active_support/benchmarkable.rb
+++ b/activesupport/lib/active_support/benchmarkable.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/benchmark"
require "active_support/core_ext/hash/keys"
diff --git a/activesupport/lib/active_support/builder.rb b/activesupport/lib/active_support/builder.rb
index 0f010c5d96..3fa7e6b26d 100644
--- a/activesupport/lib/active_support/builder.rb
+++ b/activesupport/lib/active_support/builder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
begin
require "builder"
rescue LoadError => e
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index 4ff47c261d..8301b8c7cb 100644
--- a/activesupport/lib/active_support/cache.rb
+++ b/activesupport/lib/active_support/cache.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "zlib"
require "active_support/core_ext/array/extract_options"
require "active_support/core_ext/array/wrap"
@@ -6,15 +8,15 @@ require "active_support/core_ext/numeric/bytes"
require "active_support/core_ext/numeric/time"
require "active_support/core_ext/object/to_param"
require "active_support/core_ext/string/inflections"
-require "active_support/core_ext/string/strip"
module ActiveSupport
# See ActiveSupport::Cache::Store for documentation.
module Cache
- autoload :FileStore, "active_support/cache/file_store"
- autoload :MemoryStore, "active_support/cache/memory_store"
- autoload :MemCacheStore, "active_support/cache/mem_cache_store"
- autoload :NullStore, "active_support/cache/null_store"
+ autoload :FileStore, "active_support/cache/file_store"
+ autoload :MemoryStore, "active_support/cache/memory_store"
+ autoload :MemCacheStore, "active_support/cache/mem_cache_store"
+ autoload :NullStore, "active_support/cache/null_store"
+ autoload :RedisCacheStore, "active_support/cache/redis_cache_store"
# These options mean something to all cache implementations. Individual cache
# implementations may support additional options.
@@ -76,7 +78,7 @@ module ActiveSupport
#
# The +key+ argument can also respond to +cache_key+ or +to_param+.
def expand_cache_key(key, namespace = nil)
- expanded_cache_key = namespace ? "#{namespace}/" : ""
+ expanded_cache_key = (namespace ? "#{namespace}/" : "").dup
if prefix = ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"]
expanded_cache_key << "#{prefix}/"
@@ -89,16 +91,19 @@ module ActiveSupport
private
def retrieve_cache_key(key)
case
- when key.respond_to?(:cache_key) then key.cache_key
- when key.is_a?(Array) then key.map { |element| retrieve_cache_key(element) }.to_param
- when key.respond_to?(:to_a) then retrieve_cache_key(key.to_a)
- else key.to_param
+ when key.respond_to?(:cache_key_with_version) then key.cache_key_with_version
+ when key.respond_to?(:cache_key) then key.cache_key
+ when key.is_a?(Array) then key.map { |element| retrieve_cache_key(element) }.to_param
+ when key.respond_to?(:to_a) then retrieve_cache_key(key.to_a)
+ else key.to_param
end.to_s
end
# Obtains the specified cache store class, given the name of the +store+.
# Raises an error when the store class cannot be found.
def retrieve_store_class(store)
+ # require_relative cannot be used here because the class might be
+ # provided by another gem, like redis-activesupport for example.
require "active_support/cache/#{store}"
rescue LoadError => e
raise "Could not find cache store adapter for #{store} (#{e})"
@@ -144,12 +149,11 @@ module ActiveSupport
# cache.namespace = -> { @last_mod_time } # Set the namespace to a variable
# @last_mod_time = Time.now # Invalidate the entire cache by changing namespace
#
- # Caches can also store values in a compressed format to save space and
- # reduce time spent sending data. Since there is overhead, values must be
- # large enough to warrant compression. To turn on compression either pass
- # <tt>compress: true</tt> in the initializer or as an option to +fetch+
- # or +write+. To specify the threshold at which to compress values, set the
- # <tt>:compress_threshold</tt> option. The default threshold is 16K.
+ # Cached data larger than 1kB are compressed by default. To turn off
+ # compression, pass <tt>compress: false</tt> to the initializer or to
+ # individual +fetch+ or +write+ method calls. The 1kB compression
+ # threshold is configurable with the <tt>:compress_threshold</tt> option,
+ # specified in bytes.
class Store
cattr_accessor :logger, instance_writer: true
@@ -208,8 +212,7 @@ module ActiveSupport
# ask whether you should force a cache write. Otherwise, it's clearer to
# just call <tt>Cache#write</tt>.
#
- # Setting <tt>:compress</tt> will store a large cache entry set by the call
- # in a compressed format.
+ # Setting <tt>compress: false</tt> disables compression of the cache entry.
#
# Setting <tt>:expires_in</tt> will set an expiration time on the cache.
# All caches support auto-expiring content after a specified number of
@@ -220,6 +223,10 @@ module ActiveSupport
# cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 5.minutes)
# cache.write(key, value, expires_in: 1.minute) # Set a lower value for one entry
#
+ # Setting <tt>:version</tt> verifies the cache stored under <tt>name</tt>
+ # is of the same version. nil is returned on mismatches despite contents.
+ # This feature is used to support recyclable cache keys.
+ #
# Setting <tt>:race_condition_ttl</tt> is very useful in situations where
# a cache entry is used very frequently and is under heavy load. If a
# cache expires and due to heavy load several different processes will try
@@ -288,6 +295,7 @@ module ActiveSupport
instrument(:read, name, options) do |payload|
cached_entry = read_entry(key, options) unless options[:force]
entry = handle_expired_entry(cached_entry, key, options)
+ entry = nil if entry && entry.mismatched?(normalize_version(name, options))
payload[:super_operation] = :fetch if payload
payload[:hit] = !!entry if payload
end
@@ -304,21 +312,30 @@ module ActiveSupport
end
end
- # Fetches data from the cache, using the given key. If there is data in
+ # Reads data from the cache, using the given key. If there is data in
# the cache with the given key, then that data is returned. Otherwise,
# +nil+ is returned.
#
+ # Note, if data was written with the <tt>:expires_in<tt> or <tt>:version</tt> options,
+ # both of these conditions are applied before the data is returned.
+ #
# Options are passed to the underlying cache implementation.
def read(name, options = nil)
options = merged_options(options)
- key = normalize_key(name, options)
+ key = normalize_key(name, options)
+ version = normalize_version(name, options)
+
instrument(:read, name, options) do |payload|
entry = read_entry(key, options)
+
if entry
if entry.expired?
delete_entry(key, options)
payload[:hit] = false if payload
nil
+ elsif entry.mismatched?(version)
+ payload[:hit] = false if payload
+ nil
else
payload[:hit] = true if payload
entry.value
@@ -342,11 +359,15 @@ module ActiveSupport
results = {}
names.each do |name|
- key = normalize_key(name, options)
- entry = read_entry(key, options)
+ key = normalize_key(name, options)
+ version = normalize_version(name, options)
+ entry = read_entry(key, options)
+
if entry
if entry.expired?
delete_entry(key, options)
+ elsif entry.mismatched?(version)
+ # Skip mismatched versions
else
results[name] = entry.value
end
@@ -355,6 +376,19 @@ module ActiveSupport
results
end
+ # Cache Storage API to write multiple values at once.
+ def write_multi(hash, options = nil)
+ options = merged_options(options)
+
+ instrument :write_multi, hash, options do |payload|
+ entries = hash.each_with_object({}) do |(name, value), memo|
+ memo[normalize_key(name, options)] = Entry.new(value, options.merge(version: normalize_version(name, options)))
+ end
+
+ write_multi_entries entries, options
+ end
+ end
+
# Fetches data from the cache, using the given keys. If there is data in
# the cache with the given keys, then that data is returned. Otherwise,
# the supplied block is called for each key for which there was no data,
@@ -379,14 +413,15 @@ module ActiveSupport
options = names.extract_options!
options = merged_options(options)
- results = read_multi(*names, options)
- names.each_with_object({}) do |name, memo|
- memo[name] = results.fetch(name) do
- value = yield name
- write(name, value, options)
- value
+ read_multi(*names, options).tap do |results|
+ writes = {}
+
+ (names - results.keys).each do |name|
+ results[name] = writes[name] = yield(name)
end
+
+ write_multi writes, options
end
end
@@ -397,7 +432,7 @@ module ActiveSupport
options = merged_options(options)
instrument(:write, name, options) do
- entry = Entry.new(value, options)
+ entry = Entry.new(value, options.merge(version: normalize_version(name, options)))
write_entry(normalize_key(name, options), entry, options)
end
end
@@ -421,7 +456,7 @@ module ActiveSupport
instrument(:exist?, name) do
entry = read_entry(normalize_key(name, options), options)
- (entry && !entry.expired?) || false
+ (entry && !entry.expired? && !entry.mismatched?(normalize_version(name, options))) || false
end
end
@@ -467,7 +502,7 @@ module ActiveSupport
# The options hash is passed to the underlying cache implementation.
#
# All implementations may not support this method.
- def clear
+ def clear(options = nil)
raise NotImplementedError.new("#{self.class.name} does not support clear")
end
@@ -503,6 +538,14 @@ module ActiveSupport
raise NotImplementedError.new
end
+ # Writes multiple entries to the cache implementation. Subclasses MAY
+ # implement this method.
+ def write_multi_entries(hash, options)
+ hash.each do |key, entry|
+ write_entry key, entry, options
+ end
+ end
+
# Deletes an entry from the cache implementation. Subclasses must
# implement this method.
def delete_entry(key, options)
@@ -518,6 +561,36 @@ module ActiveSupport
end
end
+ # Expands and namespaces the cache key. May be overridden by
+ # cache stores to do additional normalization.
+ def normalize_key(key, options = nil)
+ namespace_key expanded_key(key), options
+ end
+
+ # Prefix the key with a namespace string:
+ #
+ # namespace_key 'foo', namespace: 'cache'
+ # # => 'cache:foo'
+ #
+ # With a namespace block:
+ #
+ # namespace_key 'foo', namespace: -> { 'cache' }
+ # # => 'cache:foo'
+ def namespace_key(key, options = nil)
+ options = merged_options(options)
+ namespace = options[:namespace]
+
+ if namespace.respond_to?(:call)
+ namespace = namespace.call
+ end
+
+ if namespace
+ "#{namespace}:#{key}"
+ else
+ key
+ end
+ end
+
# Expands key to be a consistent string value. Invokes +cache_key+ if
# object responds to +cache_key+. Otherwise, +to_param+ method will be
# called. If the key is a Hash, then keys will be sorted alphabetically.
@@ -538,14 +611,16 @@ module ActiveSupport
key.to_param
end
- # Prefixes a key with the namespace. Namespace and key will be delimited
- # with a colon.
- def normalize_key(key, options)
- key = expanded_key(key)
- namespace = options[:namespace] if options
- prefix = namespace.is_a?(Proc) ? namespace.call : namespace
- key = "#{prefix}:#{key}" if prefix
- key
+ def normalize_version(key, options = nil)
+ (options && options[:version].try(:to_param)) || expanded_version(key)
+ end
+
+ def expanded_version(key)
+ case
+ when key.respond_to?(:cache_version) then key.cache_version.to_param
+ when key.is_a?(Array) then key.map { |element| expanded_version(element) }.compact.to_param
+ when key.respond_to?(:to_a) then expanded_version(key.to_a)
+ end
end
def instrument(operation, key, options = nil)
@@ -592,14 +667,17 @@ module ActiveSupport
end
end
- # This class is used to represent cache entries. Cache entries have a value and an optional
- # expiration time. The expiration time is used to support the :race_condition_ttl option
- # on the cache.
+ # This class is used to represent cache entries. Cache entries have a value, an optional
+ # expiration time, and an optional version. The expiration time is used to support the :race_condition_ttl option
+ # on the cache. The version is used to support the :version option on the cache for rejecting
+ # mismatches.
#
# Since cache entries in most instances will be serialized, the internals of this class are highly optimized
# using short instance variable names that are lazily defined.
class Entry # :nodoc:
- DEFAULT_COMPRESS_LIMIT = 16.kilobytes
+ attr_reader :version
+
+ DEFAULT_COMPRESS_LIMIT = 1.kilobyte
# Creates a new cache entry for the specified value. Options supported are
# +:compress+, +:compress_threshold+, and +:expires_in+.
@@ -611,6 +689,7 @@ module ActiveSupport
@value = value
end
+ @version = options[:version]
@created_at = Time.now.to_f
@expires_in = options[:expires_in]
@expires_in = @expires_in.to_f if @expires_in
@@ -620,6 +699,10 @@ module ActiveSupport
compressed? ? uncompress(@value) : @value
end
+ def mismatched?(version)
+ @version && version && @version != version
+ end
+
# Checks if the entry is expired. The +expires_in+ parameter can override
# the value set when the entry was created.
def expired?
@@ -669,8 +752,8 @@ module ActiveSupport
private
def should_compress?(value, options)
- if value && options[:compress]
- compress_threshold = options[:compress_threshold] || DEFAULT_COMPRESS_LIMIT
+ if value && options.fetch(:compress, true)
+ compress_threshold = options.fetch(:compress_threshold, DEFAULT_COMPRESS_LIMIT)
serialized_value_size = (value.is_a?(String) ? value : Marshal.dump(value)).bytesize
return true if serialized_value_size >= compress_threshold
diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb
index d5c8585816..a0f44aac0f 100644
--- a/activesupport/lib/active_support/cache/file_store.rb
+++ b/activesupport/lib/active_support/cache/file_store.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/marshal"
require "active_support/core_ext/file/atomic"
require "active_support/core_ext/string/conversions"
@@ -27,7 +29,7 @@ module ActiveSupport
# Deletes all items from the cache. In this case it deletes all the entries in the specified
# file store directory except for .keep or .gitkeep. Be careful which directory is specified in your
# config file when using +FileStore+ because everything in that directory will be deleted.
- def clear
+ def clear(options = nil)
root_dirs = exclude_from(cache_path, EXCLUDED_DIRS + GITKEEP_FILES)
FileUtils.rm_r(root_dirs.collect { |f| File.join(cache_path, f) })
rescue Errno::ENOENT
@@ -37,9 +39,8 @@ module ActiveSupport
def cleanup(options = nil)
options = merged_options(options)
search_dir(cache_path) do |fname|
- key = file_path_key(fname)
- entry = read_entry(key, options)
- delete_entry(key, options) if entry && entry.expired?
+ entry = read_entry(fname, options)
+ delete_entry(fname, options) if entry && entry.expired?
end
end
@@ -120,7 +121,7 @@ module ActiveSupport
fname = URI.encode_www_form_component(key)
if fname.size > FILEPATH_MAX_SIZE
- fname = Digest::MD5.hexdigest(key)
+ fname = ActiveSupport::Digest.hexdigest(key)
end
hash = Zlib.adler32(fname)
diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb
index e09cee3335..df8bc8e43e 100644
--- a/activesupport/lib/active_support/cache/mem_cache_store.rb
+++ b/activesupport/lib/active_support/cache/mem_cache_store.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
begin
require "dalli"
rescue LoadError => e
@@ -5,14 +7,13 @@ rescue LoadError => e
raise e
end
-require "digest/md5"
require "active_support/core_ext/marshal"
require "active_support/core_ext/array/extract_options"
module ActiveSupport
module Cache
# A cache store implementation which stores data in Memcached:
- # http://memcached.org/
+ # https://memcached.org
#
# This is currently the most popular cache store for production websites.
#
@@ -97,12 +98,18 @@ module ActiveSupport
options = merged_options(options)
keys_to_names = Hash[names.map { |name| [normalize_key(name, options), name] }]
+
raw_values = @data.get_multi(keys_to_names.keys)
values = {}
+
raw_values.each do |key, value|
entry = deserialize_entry(value)
- values[keys_to_names[key]] = entry.value unless entry.expired?
+
+ unless entry.expired? || entry.mismatched?(normalize_version(keys_to_names[key], options))
+ values[keys_to_names[key]] = entry.value
+ end
end
+
values
end
@@ -114,7 +121,7 @@ module ActiveSupport
options = merged_options(options)
instrument(:increment, name, amount: amount) do
rescue_error_with nil do
- @data.incr(normalize_key(name, options), amount)
+ @data.incr(normalize_key(name, options), amount, options[:expires_in])
end
end
end
@@ -127,7 +134,7 @@ module ActiveSupport
options = merged_options(options)
instrument(:decrement, name, amount: amount) do
rescue_error_with nil do
- @data.decr(normalize_key(name, options), amount)
+ @data.decr(normalize_key(name, options), amount, options[:expires_in])
end
end
end
@@ -175,7 +182,7 @@ module ActiveSupport
key = super.dup
key = key.force_encoding(Encoding::ASCII_8BIT)
key = key.gsub(ESCAPE_KEY_CHARS) { |match| "%#{match.getbyte(0).to_s(16).upcase}" }
- key = "#{key[0, 213]}:md5:#{Digest::MD5.hexdigest(key)}" if key.size > 250
+ key = "#{key[0, 213]}:md5:#{ActiveSupport::Digest.hexdigest(key)}" if key.size > 250
key
end
diff --git a/activesupport/lib/active_support/cache/memory_store.rb b/activesupport/lib/active_support/cache/memory_store.rb
index fea072d91c..564ac17241 100644
--- a/activesupport/lib/active_support/cache/memory_store.rb
+++ b/activesupport/lib/active_support/cache/memory_store.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "monitor"
module ActiveSupport
@@ -28,6 +30,7 @@ module ActiveSupport
@pruning = false
end
+ # Delete all data stored in a given cache store.
def clear(options = nil)
synchronize do
@data.clear
@@ -83,6 +86,7 @@ module ActiveSupport
modify_value(name, -amount, options)
end
+ # Deletes cache entries if the cache key matches a given pattern.
def delete_matched(matcher, options = nil)
options = merged_options(options)
instrument(:delete_matched, matcher.inspect) do
diff --git a/activesupport/lib/active_support/cache/null_store.rb b/activesupport/lib/active_support/cache/null_store.rb
index 550659fc56..1a5983db43 100644
--- a/activesupport/lib/active_support/cache/null_store.rb
+++ b/activesupport/lib/active_support/cache/null_store.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActiveSupport
module Cache
# A cache store implementation which doesn't actually store anything. Useful in
diff --git a/activesupport/lib/active_support/cache/redis_cache_store.rb b/activesupport/lib/active_support/cache/redis_cache_store.rb
new file mode 100644
index 0000000000..6cc45f5284
--- /dev/null
+++ b/activesupport/lib/active_support/cache/redis_cache_store.rb
@@ -0,0 +1,406 @@
+# frozen_string_literal: true
+
+begin
+ gem "redis", ">= 4.0.1"
+ require "redis"
+ require "redis/distributed"
+rescue LoadError
+ warn "The Redis cache store requires the redis gem, version 4.0.1 or later. Please add it to your Gemfile: `gem \"redis\", \"~> 4.0\"`"
+ raise
+end
+
+# Prefer the hiredis driver but don't require it.
+begin
+ require "redis/connection/hiredis"
+rescue LoadError
+end
+
+require "digest/sha2"
+require "active_support/core_ext/marshal"
+
+module ActiveSupport
+ module Cache
+ # Redis cache store.
+ #
+ # Deployment note: Take care to use a *dedicated Redis cache* rather
+ # than pointing this at your existing Redis server. It won't cope well
+ # with mixed usage patterns and it won't expire cache entries by default.
+ #
+ # Redis cache server setup guide: https://redis.io/topics/lru-cache
+ #
+ # * Supports vanilla Redis, hiredis, and Redis::Distributed.
+ # * Supports Memcached-like sharding across Redises with Redis::Distributed.
+ # * Fault tolerant. If the Redis server is unavailable, no exceptions are
+ # raised. Cache fetches are all misses and writes are dropped.
+ # * Local cache. Hot in-memory primary cache within block/middleware scope.
+ # * +read_multi+ and +write_multi+ support for Redis mget/mset. Use Redis::Distributed
+ # 4.0.1+ for distributed mget support.
+ # * +delete_matched+ support for Redis KEYS globs.
+ class RedisCacheStore < Store
+ # Keys are truncated with their own SHA2 digest if they exceed 1kB
+ MAX_KEY_BYTESIZE = 1024
+
+ DEFAULT_REDIS_OPTIONS = {
+ connect_timeout: 20,
+ read_timeout: 1,
+ write_timeout: 1,
+ reconnect_attempts: 0,
+ }
+
+ DEFAULT_ERROR_HANDLER = -> (method:, returning:, exception:) do
+ if logger
+ logger.error { "RedisCacheStore: #{method} failed, returned #{returning.inspect}: #{exception.class}: #{exception.message}" }
+ end
+ end
+
+ DELETE_GLOB_LUA = "for i, name in ipairs(redis.call('KEYS', ARGV[1])) do redis.call('DEL', name); end"
+ private_constant :DELETE_GLOB_LUA
+
+ # Support raw values in the local cache strategy.
+ module LocalCacheWithRaw # :nodoc:
+ private
+ def read_entry(key, options)
+ entry = super
+ if options[:raw] && local_cache && entry
+ entry = deserialize_entry(entry.value)
+ end
+ entry
+ end
+
+ def write_entry(key, entry, options)
+ if options[:raw] && local_cache
+ raw_entry = Entry.new(entry.value.to_s)
+ raw_entry.expires_at = entry.expires_at
+ super(key, raw_entry, options)
+ else
+ super
+ end
+ end
+
+ def write_multi_entries(entries, options)
+ if options[:raw] && local_cache
+ raw_entries = entries.map do |key, entry|
+ raw_entry = Entry.new(entry.value.to_s)
+ raw_entry.expires_at = entry.expires_at
+ end.to_h
+
+ super(raw_entries, options)
+ else
+ super
+ end
+ end
+ end
+
+ prepend Strategy::LocalCache
+ prepend LocalCacheWithRaw
+
+ class << self
+ # Factory method to create a new Redis instance.
+ #
+ # Handles four options: :redis block, :redis instance, single :url
+ # string, and multiple :url strings.
+ #
+ # Option Class Result
+ # :redis Proc -> options[:redis].call
+ # :redis Object -> options[:redis]
+ # :url String -> Redis.new(url: …)
+ # :url Array -> Redis::Distributed.new([{ url: … }, { url: … }, …])
+ #
+ def build_redis(redis: nil, url: nil, **redis_options) #:nodoc:
+ urls = Array(url)
+
+ if redis.respond_to?(:call)
+ redis.call
+ elsif redis
+ redis
+ elsif urls.size > 1
+ build_redis_distributed_client urls: urls, **redis_options
+ else
+ build_redis_client url: urls.first, **redis_options
+ end
+ end
+
+ private
+ def build_redis_distributed_client(urls:, **redis_options)
+ ::Redis::Distributed.new([], DEFAULT_REDIS_OPTIONS.merge(redis_options)).tap do |dist|
+ urls.each { |u| dist.add_node url: u }
+ end
+ end
+
+ def build_redis_client(url:, **redis_options)
+ ::Redis.new DEFAULT_REDIS_OPTIONS.merge(redis_options.merge(url: url))
+ end
+ end
+
+ attr_reader :redis_options
+ attr_reader :max_key_bytesize
+
+ # Creates a new Redis cache store.
+ #
+ # Handles three options: block provided to instantiate, single URL
+ # provided, and multiple URLs provided.
+ #
+ # :redis Proc -> options[:redis].call
+ # :url String -> Redis.new(url: …)
+ # :url Array -> Redis::Distributed.new([{ url: … }, { url: … }, …])
+ #
+ # No namespace is set by default. Provide one if the Redis cache
+ # server is shared with other apps: <tt>namespace: 'myapp-cache'<tt>.
+ #
+ # Compression is enabled by default with a 1kB threshold, so cached
+ # values larger than 1kB are automatically compressed. Disable by
+ # passing <tt>cache: false</tt> or change the threshold by passing
+ # <tt>compress_threshold: 4.kilobytes</tt>.
+ #
+ # No expiry is set on cache entries by default. Redis is expected to
+ # be configured with an eviction policy that automatically deletes
+ # least-recently or -frequently used keys when it reaches max memory.
+ # See https://redis.io/topics/lru-cache for cache server setup.
+ #
+ # Race condition TTL is not set by default. This can be used to avoid
+ # "thundering herd" cache writes when hot cache entries are expired.
+ # See <tt>ActiveSupport::Cache::Store#fetch</tt> for more.
+ def initialize(namespace: nil, compress: true, compress_threshold: 1.kilobyte, expires_in: nil, race_condition_ttl: nil, error_handler: DEFAULT_ERROR_HANDLER, **redis_options)
+ @redis_options = redis_options
+
+ @max_key_bytesize = MAX_KEY_BYTESIZE
+ @error_handler = error_handler
+
+ super namespace: namespace,
+ compress: compress, compress_threshold: compress_threshold,
+ expires_in: expires_in, race_condition_ttl: race_condition_ttl
+ end
+
+ def redis
+ @redis ||= self.class.build_redis(**redis_options)
+ end
+
+ def inspect
+ instance = @redis || @redis_options
+ "<##{self.class} options=#{options.inspect} redis=#{instance.inspect}>"
+ end
+
+ # Cache Store API implementation.
+ #
+ # Read multiple values at once. Returns a hash of requested keys ->
+ # fetched values.
+ def read_multi(*names)
+ if mget_capable?
+ read_multi_mget(*names)
+ else
+ super
+ end
+ end
+
+ # Cache Store API implementation.
+ #
+ # Supports Redis KEYS glob patterns:
+ #
+ # h?llo matches hello, hallo and hxllo
+ # h*llo matches hllo and heeeello
+ # h[ae]llo matches hello and hallo, but not hillo
+ # h[^e]llo matches hallo, hbllo, ... but not hello
+ # h[a-b]llo matches hallo and hbllo
+ #
+ # Use \ to escape special characters if you want to match them verbatim.
+ #
+ # See https://redis.io/commands/KEYS for more.
+ #
+ # Failsafe: Raises errors.
+ def delete_matched(matcher, options = nil)
+ instrument :delete_matched, matcher do
+ case matcher
+ when String
+ redis.eval DELETE_GLOB_LUA, [], [namespace_key(matcher, options)]
+ else
+ raise ArgumentError, "Only Redis glob strings are supported: #{matcher.inspect}"
+ end
+ end
+ end
+
+ # Cache Store API implementation.
+ #
+ # Increment a cached value. This method uses the Redis incr atomic
+ # operator and can only be used on values written with the :raw option.
+ # Calling it on a value not stored with :raw will initialize that value
+ # to zero.
+ #
+ # Failsafe: Raises errors.
+ def increment(name, amount = 1, options = nil)
+ instrument :increment, name, amount: amount do
+ redis.incrby normalize_key(name, options), amount
+ end
+ end
+
+ # Cache Store API implementation.
+ #
+ # Decrement a cached value. This method uses the Redis decr atomic
+ # operator and can only be used on values written with the :raw option.
+ # Calling it on a value not stored with :raw will initialize that value
+ # to zero.
+ #
+ # Failsafe: Raises errors.
+ def decrement(name, amount = 1, options = nil)
+ instrument :decrement, name, amount: amount do
+ redis.decrby normalize_key(name, options), amount
+ end
+ end
+
+ # Cache Store API implementation.
+ #
+ # Removes expired entries. Handled natively by Redis least-recently-/
+ # least-frequently-used expiry, so manual cleanup is not supported.
+ def cleanup(options = nil)
+ super
+ end
+
+ # Clear the entire cache on all Redis servers. Safe to use on
+ # shared servers if the cache is namespaced.
+ #
+ # Failsafe: Raises errors.
+ def clear(options = nil)
+ failsafe :clear do
+ if namespace = merged_options(options)[namespace]
+ delete_matched "*", namespace: namespace
+ else
+ redis.flushdb
+ end
+ end
+ end
+
+ def mget_capable? #:nodoc:
+ set_redis_capabilities unless defined? @mget_capable
+ @mget_capable
+ end
+
+ def mset_capable? #:nodoc:
+ set_redis_capabilities unless defined? @mset_capable
+ @mset_capable
+ end
+
+ private
+ def set_redis_capabilities
+ case redis
+ when Redis::Distributed
+ @mget_capable = true
+ @mset_capable = false
+ else
+ @mget_capable = true
+ @mset_capable = true
+ end
+ end
+
+ # Store provider interface:
+ # Read an entry from the cache.
+ def read_entry(key, options = nil)
+ failsafe :read_entry do
+ deserialize_entry redis.get(key)
+ end
+ end
+
+ def read_multi_mget(*names)
+ options = names.extract_options!
+ options = merged_options(options)
+
+ keys = names.map { |name| normalize_key(name, options) }
+ values = redis.mget(*keys)
+
+ names.zip(values).each_with_object({}) do |(name, value), results|
+ if value
+ entry = deserialize_entry(value)
+ unless entry.nil? || entry.expired? || entry.mismatched?(normalize_version(name, options))
+ results[name] = entry.value
+ end
+ end
+ end
+ end
+
+ # Write an entry to the cache.
+ #
+ # Requires Redis 2.6.12+ for extended SET options.
+ def write_entry(key, entry, unless_exist: false, raw: false, expires_in: nil, race_condition_ttl: nil, **options)
+ value = raw ? entry.value.to_s : serialize_entry(entry)
+
+ # If race condition TTL is in use, ensure that cache entries
+ # stick around a bit longer after they would have expired
+ # so we can purposefully serve stale entries.
+ if race_condition_ttl && expires_in && expires_in > 0 && !raw
+ expires_in += 5.minutes
+ end
+
+ failsafe :write_entry do
+ if unless_exist || expires_in
+ modifiers = {}
+ modifiers[:nx] = unless_exist
+ modifiers[:px] = (1000 * expires_in.to_f).ceil if expires_in
+
+ redis.set key, value, modifiers
+ else
+ redis.set key, value
+ end
+ end
+ end
+
+ # Delete an entry from the cache.
+ def delete_entry(key, options)
+ failsafe :delete_entry, returning: false do
+ redis.del key
+ end
+ end
+
+ # Nonstandard store provider API to write multiple values at once.
+ def write_multi_entries(entries, expires_in: nil, **options)
+ if entries.any?
+ if mset_capable? && expires_in.nil?
+ failsafe :write_multi_entries do
+ redis.mapped_mset(entries)
+ end
+ else
+ super
+ end
+ end
+ end
+
+ # Truncate keys that exceed 1kB.
+ def normalize_key(key, options)
+ truncate_key super
+ end
+
+ def truncate_key(key)
+ if key.bytesize > max_key_bytesize
+ suffix = ":sha2:#{Digest::SHA2.hexdigest(key)}"
+ truncate_at = max_key_bytesize - suffix.bytesize
+ "#{key.byteslice(0, truncate_at)}#{suffix}"
+ else
+ key
+ end
+ end
+
+ def deserialize_entry(raw_value)
+ if raw_value
+ entry = Marshal.load(raw_value) rescue raw_value
+ entry.is_a?(Entry) ? entry : Entry.new(entry)
+ end
+ end
+
+ def serialize_entry(entry)
+ Marshal.dump(entry)
+ end
+
+ def failsafe(method, returning: nil)
+ yield
+ rescue ::Redis::BaseConnectionError => e
+ handle_exception exception: e, method: method, returning: returning
+ returning
+ end
+
+ def handle_exception(exception:, method:, returning:)
+ if @error_handler
+ @error_handler.(method: method, exception: exception, returning: returning)
+ end
+ rescue => failsafe
+ warn "RedisCacheStore ignored exception in handle_exception: #{failsafe.class}: #{failsafe.message}\n #{failsafe.backtrace.join("\n ")}"
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/cache/strategy/local_cache.rb b/activesupport/lib/active_support/cache/strategy/local_cache.rb
index 672eb2bb80..aaa9638fa8 100644
--- a/activesupport/lib/active_support/cache/strategy/local_cache.rb
+++ b/activesupport/lib/active_support/cache/strategy/local_cache.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/object/duplicable"
require "active_support/core_ext/string/inflections"
require "active_support/per_thread_registry"
@@ -44,7 +46,7 @@ module ActiveSupport
yield
end
- def clear
+ def clear(options = nil)
@data.clear
end
@@ -79,15 +81,15 @@ module ActiveSupport
local_cache_key)
end
- def clear # :nodoc:
+ def clear(options = nil) # :nodoc:
return super unless cache = local_cache
- cache.clear
+ cache.clear(options)
super
end
def cleanup(options = nil) # :nodoc:
return super unless cache = local_cache
- cache.clear(options)
+ cache.clear
super
end
@@ -115,7 +117,12 @@ module ActiveSupport
end
def write_entry(key, entry, options)
- local_cache.write_entry(key, entry, options) if local_cache
+ if options[:unless_exist]
+ local_cache.delete_entry(key, options) if local_cache
+ else
+ local_cache.write_entry(key, entry, options) if local_cache
+ end
+
super
end
diff --git a/activesupport/lib/active_support/cache/strategy/local_cache_middleware.rb b/activesupport/lib/active_support/cache/strategy/local_cache_middleware.rb
index 174cb72b1e..62542bdb22 100644
--- a/activesupport/lib/active_support/cache/strategy/local_cache_middleware.rb
+++ b/activesupport/lib/active_support/cache/strategy/local_cache_middleware.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "rack/body_proxy"
require "rack/utils"
@@ -28,13 +30,13 @@ module ActiveSupport
response[2] = ::Rack::BodyProxy.new(response[2]) do
LocalCacheRegistry.set_cache_for(local_cache_key, nil)
end
+ cleanup_on_body_close = true
response
rescue Rack::Utils::InvalidParameterError
- LocalCacheRegistry.set_cache_for(local_cache_key, nil)
[400, {}, []]
- rescue Exception
- LocalCacheRegistry.set_cache_for(local_cache_key, nil)
- raise
+ ensure
+ LocalCacheRegistry.set_cache_for(local_cache_key, nil) unless
+ cleanup_on_body_close
end
end
end
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index 480291c346..0ed4681b7d 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -1,10 +1,11 @@
+# frozen_string_literal: true
+
require "active_support/concern"
require "active_support/descendants_tracker"
require "active_support/core_ext/array/extract_options"
require "active_support/core_ext/class/attribute"
require "active_support/core_ext/kernel/reporting"
require "active_support/core_ext/kernel/singleton_class"
-require "active_support/core_ext/module/attribute_accessors"
require "active_support/core_ext/string/filters"
require "active_support/deprecation"
require "thread"
@@ -63,25 +64,11 @@ module ActiveSupport
included do
extend ActiveSupport::DescendantsTracker
- class_attribute :__callbacks, instance_writer: false
- self.__callbacks ||= {}
+ class_attribute :__callbacks, instance_writer: false, default: {}
end
CALLBACK_FILTER_TYPES = [:before, :after, :around]
- def self.halt_and_display_warning_on_return_false=(value)
-
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- .halt_and_display_warning_on_return_false= is deprecated and will be removed in Rails 5.2.
- MSG
- end
-
- def self.halt_and_display_warning_on_return_false
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- .halt_and_display_warning_on_return_false is deprecated and will be removed in Rails 5.2.
- MSG
- end
-
# Runs the callbacks for the given event.
#
# Calls the before and around callbacks in the order they were set, yields
@@ -311,8 +298,8 @@ module ActiveSupport
@kind = kind
@filter = filter
@key = compute_identifier filter
- @if = Array(options[:if])
- @unless = Array(options[:unless])
+ @if = check_conditionals(Array(options[:if]))
+ @unless = check_conditionals(Array(options[:unless]))
end
def filter; @key; end
@@ -336,7 +323,7 @@ module ActiveSupport
def duplicates?(other)
case @filter
- when Symbol, String
+ when Symbol
matches?(other.kind, other.filter)
else
false
@@ -363,9 +350,21 @@ module ActiveSupport
end
private
+ def check_conditionals(conditionals)
+ if conditionals.any? { |c| c.is_a?(String) }
+ raise ArgumentError, <<-MSG.squish
+ Passing string to be evaluated in :if and :unless conditional
+ options is not supported. Pass a symbol for an instance method,
+ or a lambda, proc or block, instead.
+ MSG
+ end
+
+ conditionals
+ end
+
def compute_identifier(filter)
case filter
- when String, ::Proc
+ when ::Proc
filter.object_id
else
filter
@@ -440,7 +439,6 @@ module ActiveSupport
# Filters support:
#
# Symbols:: A method to call.
- # Strings:: Some content to evaluate.
# Procs:: A proc to call with the object.
# Objects:: An object with a <tt>before_foo</tt> method on it to call.
#
@@ -450,8 +448,6 @@ module ActiveSupport
case filter
when Symbol
new(nil, filter, [], nil)
- when String
- new(nil, :instance_exec, [:value], compile_lambda(filter))
when Conditionals::Value
new(filter, :call, [:target, :value], nil)
when ::Proc
@@ -468,10 +464,6 @@ module ActiveSupport
new(filter, method_to_call, [:target], nil)
end
end
-
- def self.compile_lambda(filter)
- eval("lambda { |value| #{filter} }")
- end
end
# Execute before and after filters in a sequence instead of
@@ -526,7 +518,6 @@ module ActiveSupport
end
end
- # An Array with a compile method.
class CallbackChain #:nodoc:#
include Enumerable
@@ -612,7 +603,7 @@ module ActiveSupport
Proc.new do |target, result_lambda|
terminate = true
catch(:abort) do
- result_lambda.call if result_lambda.is_a?(Proc)
+ result_lambda.call
terminate = false
end
terminate
@@ -665,24 +656,17 @@ module ActiveSupport
#
# ===== Options
#
- # * <tt>:if</tt> - A symbol, a string (deprecated) or an array of symbols,
- # each naming an instance method or a proc; the callback will be called
- # only when they all return a true value.
- # * <tt>:unless</tt> - A symbol, a string (deprecated) or an array of symbols,
- # each naming an instance method or a proc; the callback will be called
- # only when they all return a false value.
+ # * <tt>:if</tt> - A symbol or an array of symbols, each naming an instance
+ # method or a proc; the callback will be called only when they all return
+ # a true value.
+ # * <tt>:unless</tt> - A symbol or an array of symbols, each naming an
+ # instance method or a proc; the callback will be called only when they
+ # all return a false value.
# * <tt>:prepend</tt> - If +true+, the callback will be prepended to the
# existing chain rather than appended.
def set_callback(name, *filter_list, &block)
type, filters, options = normalize_callback_params(filter_list, block)
- if options[:if].is_a?(String) || options[:unless].is_a?(String)
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Passing string to :if and :unless conditional options is deprecated
- and will be removed in Rails 5.2 without replacement.
- MSG
- end
-
self_chain = get_callbacks name
mapped = filters.map do |filter|
Callback.build(self_chain, filter, type, options)
@@ -707,13 +691,6 @@ module ActiveSupport
def skip_callback(name, *filter_list, &block)
type, filters, options = normalize_callback_params(filter_list, block)
- if options[:if].is_a?(String) || options[:unless].is_a?(String)
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Passing string to :if and :unless conditional options is deprecated
- and will be removed in Rails 5.2 without replacement.
- MSG
- end
-
options[:raise] = true unless options.key?(:raise)
__update_callbacks(name) do |target, chain|
diff --git a/activesupport/lib/active_support/concern.rb b/activesupport/lib/active_support/concern.rb
index 0403eb70ca..b0a0d845e5 100644
--- a/activesupport/lib/active_support/concern.rb
+++ b/activesupport/lib/active_support/concern.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActiveSupport
# A typical module looks like this:
#
@@ -111,7 +113,7 @@ module ActiveSupport
def append_features(base)
if base.instance_variable_defined?(:@_dependencies)
base.instance_variable_get(:@_dependencies) << self
- return false
+ false
else
return false if base < self
@_dependencies.each { |dep| base.include(dep) }
diff --git a/activesupport/lib/active_support/concurrency/load_interlock_aware_monitor.rb b/activesupport/lib/active_support/concurrency/load_interlock_aware_monitor.rb
new file mode 100644
index 0000000000..a8455c0048
--- /dev/null
+++ b/activesupport/lib/active_support/concurrency/load_interlock_aware_monitor.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require "monitor"
+
+module ActiveSupport
+ module Concurrency
+ # A monitor that will permit dependency loading while blocked waiting for
+ # the lock.
+ class LoadInterlockAwareMonitor < Monitor
+ # Enters an exclusive section, but allows dependency loading while blocked
+ def mon_enter
+ mon_try_enter ||
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads { super }
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/concurrency/share_lock.rb b/activesupport/lib/active_support/concurrency/share_lock.rb
index 4318523b07..f18ccf1c88 100644
--- a/activesupport/lib/active_support/concurrency/share_lock.rb
+++ b/activesupport/lib/active_support/concurrency/share_lock.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "thread"
require "monitor"
diff --git a/activesupport/lib/active_support/configurable.rb b/activesupport/lib/active_support/configurable.rb
index f72a893bcc..4d6f7819bb 100644
--- a/activesupport/lib/active_support/configurable.rb
+++ b/activesupport/lib/active_support/configurable.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/concern"
require "active_support/ordered_options"
require "active_support/core_ext/array/extract_options"
diff --git a/activesupport/lib/active_support/core_ext.rb b/activesupport/lib/active_support/core_ext.rb
index f397f658f3..f590605d84 100644
--- a/activesupport/lib/active_support/core_ext.rb
+++ b/activesupport/lib/active_support/core_ext.rb
@@ -1,3 +1,5 @@
-(Dir["#{File.dirname(__FILE__)}/core_ext/*.rb"]).each do |path|
+# frozen_string_literal: true
+
+Dir.glob(File.expand_path("core_ext/*.rb", __dir__)).each do |path|
require path
end
diff --git a/activesupport/lib/active_support/core_ext/array.rb b/activesupport/lib/active_support/core_ext/array.rb
index e908386a1c..6d83b76882 100644
--- a/activesupport/lib/active_support/core_ext/array.rb
+++ b/activesupport/lib/active_support/core_ext/array.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/array/wrap"
require "active_support/core_ext/array/access"
require "active_support/core_ext/array/conversions"
diff --git a/activesupport/lib/active_support/core_ext/array/access.rb b/activesupport/lib/active_support/core_ext/array/access.rb
index fca33c9d69..b7ff7a3907 100644
--- a/activesupport/lib/active_support/core_ext/array/access.rb
+++ b/activesupport/lib/active_support/core_ext/array/access.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Array
# Returns the tail of the array from +position+.
#
@@ -33,8 +35,8 @@ class Array
# people.without "Aaron", "Todd"
# # => ["David", "Rafael"]
#
- # Note: This is an optimization of `Enumerable#without` that uses `Array#-`
- # instead of `Array#reject` for performance reasons.
+ # Note: This is an optimization of <tt>Enumerable#without</tt> that uses <tt>Array#-</tt>
+ # instead of <tt>Array#reject</tt> for performance reasons.
def without(*elements)
self - elements
end
diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb
index cac15e1100..ea688ed2ea 100644
--- a/activesupport/lib/active_support/core_ext/array/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/array/conversions.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/xml_mini"
require "active_support/core_ext/hash/keys"
require "active_support/core_ext/string/inflections"
diff --git a/activesupport/lib/active_support/core_ext/array/extract_options.rb b/activesupport/lib/active_support/core_ext/array/extract_options.rb
index 9008a0df2a..8c7cb2e780 100644
--- a/activesupport/lib/active_support/core_ext/array/extract_options.rb
+++ b/activesupport/lib/active_support/core_ext/array/extract_options.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Hash
# By default, only instances of Hash itself are extractable.
# Subclasses of Hash may implement this method and return
diff --git a/activesupport/lib/active_support/core_ext/array/grouping.rb b/activesupport/lib/active_support/core_ext/array/grouping.rb
index 0d798e5c4e..67e760bc4b 100644
--- a/activesupport/lib/active_support/core_ext/array/grouping.rb
+++ b/activesupport/lib/active_support/core_ext/array/grouping.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Array
# Splits or iterates over the array in groups of size +number+,
# padding any remaining slots with +fill_with+ unless it is +false+.
diff --git a/activesupport/lib/active_support/core_ext/array/inquiry.rb b/activesupport/lib/active_support/core_ext/array/inquiry.rb
index 66fa5fcd0c..92c61bf201 100644
--- a/activesupport/lib/active_support/core_ext/array/inquiry.rb
+++ b/activesupport/lib/active_support/core_ext/array/inquiry.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/array_inquirer"
class Array
diff --git a/activesupport/lib/active_support/core_ext/array/prepend_and_append.rb b/activesupport/lib/active_support/core_ext/array/prepend_and_append.rb
index 16a6789f8d..661971d7cd 100644
--- a/activesupport/lib/active_support/core_ext/array/prepend_and_append.rb
+++ b/activesupport/lib/active_support/core_ext/array/prepend_and_append.rb
@@ -1,7 +1,9 @@
+# frozen_string_literal: true
+
class Array
# The human way of thinking about adding stuff to the end of a list is with append.
- alias_method :append, :<<
+ alias_method :append, :push unless [].respond_to?(:append)
# The human way of thinking about adding stuff to the beginning of a list is with prepend.
- alias_method :prepend, :unshift
+ alias_method :prepend, :unshift unless [].respond_to?(:prepend)
end
diff --git a/activesupport/lib/active_support/core_ext/array/wrap.rb b/activesupport/lib/active_support/core_ext/array/wrap.rb
index b611d34c27..d62f97edbf 100644
--- a/activesupport/lib/active_support/core_ext/array/wrap.rb
+++ b/activesupport/lib/active_support/core_ext/array/wrap.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Array
# Wraps its argument in an array unless it is already an array (or array-like).
#
diff --git a/activesupport/lib/active_support/core_ext/benchmark.rb b/activesupport/lib/active_support/core_ext/benchmark.rb
index 2300953860..641b58c8b8 100644
--- a/activesupport/lib/active_support/core_ext/benchmark.rb
+++ b/activesupport/lib/active_support/core_ext/benchmark.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "benchmark"
class << Benchmark
diff --git a/activesupport/lib/active_support/core_ext/big_decimal.rb b/activesupport/lib/active_support/core_ext/big_decimal.rb
index 7b4f87f10e..9e6a9d6331 100644
--- a/activesupport/lib/active_support/core_ext/big_decimal.rb
+++ b/activesupport/lib/active_support/core_ext/big_decimal.rb
@@ -1 +1,3 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/big_decimal/conversions"
diff --git a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
index decd4e1699..52bd229416 100644
--- a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "bigdecimal"
require "bigdecimal/util"
diff --git a/activesupport/lib/active_support/core_ext/class.rb b/activesupport/lib/active_support/core_ext/class.rb
index 6a19e862d0..1c110fd07b 100644
--- a/activesupport/lib/active_support/core_ext/class.rb
+++ b/activesupport/lib/active_support/core_ext/class.rb
@@ -1,2 +1,4 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/class/attribute"
require "active_support/core_ext/class/subclasses"
diff --git a/activesupport/lib/active_support/core_ext/class/attribute.rb b/activesupport/lib/active_support/core_ext/class/attribute.rb
index ba422f9071..7928efb871 100644
--- a/activesupport/lib/active_support/core_ext/class/attribute.rb
+++ b/activesupport/lib/active_support/core_ext/class/attribute.rb
@@ -1,11 +1,23 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/kernel/singleton_class"
-require "active_support/core_ext/module/remove_method"
+require "active_support/core_ext/module/redefine_method"
require "active_support/core_ext/array/extract_options"
class Class
# Declare a class-level attribute whose value is inheritable by subclasses.
# Subclasses can change their own value and it will not impact parent class.
#
+ # ==== Options
+ #
+ # * <tt>:instance_reader</tt> - Sets the instance reader method (defaults to true).
+ # * <tt>:instance_writer</tt> - Sets the instance writer method (defaults to true).
+ # * <tt>:instance_accessor</tt> - Sets both instance methods (defaults to true).
+ # * <tt>:instance_predicate</tt> - Sets a predicate method (defaults to true).
+ # * <tt>:default</tt> - Sets a default value for the attribute (defaults to nil).
+ #
+ # ==== Examples
+ #
# class Base
# class_attribute :setting
# end
@@ -68,32 +80,35 @@ class Class
# object.setting = false # => NoMethodError
#
# To opt out of both instance methods, pass <tt>instance_accessor: false</tt>.
+ #
+ # To set a default value for the attribute, pass <tt>default:</tt>, like so:
+ #
+ # class_attribute :settings, default: {}
def class_attribute(*attrs)
options = attrs.extract_options!
- instance_reader = options.fetch(:instance_accessor, true) && options.fetch(:instance_reader, true)
- instance_writer = options.fetch(:instance_accessor, true) && options.fetch(:instance_writer, true)
+ instance_reader = options.fetch(:instance_accessor, true) && options.fetch(:instance_reader, true)
+ instance_writer = options.fetch(:instance_accessor, true) && options.fetch(:instance_writer, true)
instance_predicate = options.fetch(:instance_predicate, true)
+ default_value = options.fetch(:default, nil)
attrs.each do |name|
- remove_possible_singleton_method(name)
+ singleton_class.silence_redefinition_of_method(name)
define_singleton_method(name) { nil }
- remove_possible_singleton_method("#{name}?")
+ singleton_class.silence_redefinition_of_method("#{name}?")
define_singleton_method("#{name}?") { !!public_send(name) } if instance_predicate
ivar = "@#{name}"
- remove_possible_singleton_method("#{name}=")
+ singleton_class.silence_redefinition_of_method("#{name}=")
define_singleton_method("#{name}=") do |val|
singleton_class.class_eval do
- remove_possible_method(name)
- define_method(name) { val }
+ redefine_method(name) { val }
end
if singleton_class?
class_eval do
- remove_possible_method(name)
- define_method(name) do
+ redefine_method(name) do
if instance_variable_defined? ivar
instance_variable_get ivar
else
@@ -106,8 +121,7 @@ class Class
end
if instance_reader
- remove_possible_method name
- define_method(name) do
+ redefine_method(name) do
if instance_variable_defined?(ivar)
instance_variable_get ivar
else
@@ -115,13 +129,17 @@ class Class
end
end
- remove_possible_method "#{name}?"
- define_method("#{name}?") { !!public_send(name) } if instance_predicate
+ redefine_method("#{name}?") { !!public_send(name) } if instance_predicate
end
if instance_writer
- remove_possible_method "#{name}="
- attr_writer name
+ redefine_method("#{name}=") do |val|
+ instance_variable_set ivar, val
+ end
+ end
+
+ unless default_value.nil?
+ self.send("#{name}=", default_value)
end
end
end
diff --git a/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb
index 0f767925ed..a77354e153 100644
--- a/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb
+++ b/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# cattr_* became mattr_* aliases in 7dfbd91b0780fbd6a1dd9bfbc176e10894871d2d,
# but we keep this around for libraries that directly require it knowing they
# want cattr_*. No need to deprecate.
diff --git a/activesupport/lib/active_support/core_ext/class/subclasses.rb b/activesupport/lib/active_support/core_ext/class/subclasses.rb
index 62397d9508..75e65337b7 100644
--- a/activesupport/lib/active_support/core_ext/class/subclasses.rb
+++ b/activesupport/lib/active_support/core_ext/class/subclasses.rb
@@ -1,5 +1,4 @@
-require "active_support/core_ext/module/anonymous"
-require "active_support/core_ext/module/reachable"
+# frozen_string_literal: true
class Class
begin
diff --git a/activesupport/lib/active_support/core_ext/date.rb b/activesupport/lib/active_support/core_ext/date.rb
index 4f66804da2..cce73f2db2 100644
--- a/activesupport/lib/active_support/core_ext/date.rb
+++ b/activesupport/lib/active_support/core_ext/date.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/date/acts_like"
require "active_support/core_ext/date/blank"
require "active_support/core_ext/date/calculations"
diff --git a/activesupport/lib/active_support/core_ext/date/acts_like.rb b/activesupport/lib/active_support/core_ext/date/acts_like.rb
index 46fe99ad47..c8077f3774 100644
--- a/activesupport/lib/active_support/core_ext/date/acts_like.rb
+++ b/activesupport/lib/active_support/core_ext/date/acts_like.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/object/acts_like"
class Date
diff --git a/activesupport/lib/active_support/core_ext/date/blank.rb b/activesupport/lib/active_support/core_ext/date/blank.rb
index edd2847126..e6271c79b3 100644
--- a/activesupport/lib/active_support/core_ext/date/blank.rb
+++ b/activesupport/lib/active_support/core_ext/date/blank.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "date"
class Date #:nodoc:
diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb
index d6f60cac04..1cd7acb05d 100644
--- a/activesupport/lib/active_support/core_ext/date/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date/calculations.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "date"
require "active_support/duration"
require "active_support/core_ext/object/acts_like"
diff --git a/activesupport/lib/active_support/core_ext/date/conversions.rb b/activesupport/lib/active_support/core_ext/date/conversions.rb
index d553406dff..870119dc7f 100644
--- a/activesupport/lib/active_support/core_ext/date/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/date/conversions.rb
@@ -1,7 +1,9 @@
+# frozen_string_literal: true
+
require "date"
require "active_support/inflector/methods"
require "active_support/core_ext/date/zones"
-require "active_support/core_ext/module/remove_method"
+require "active_support/core_ext/module/redefine_method"
class Date
DATE_FORMATS = {
@@ -17,14 +19,6 @@ class Date
iso8601: lambda { |date| date.iso8601 }
}
- # Ruby 1.9 has Date#to_time which converts to localtime only.
- remove_method :to_time
-
- # Ruby 1.9 has Date#xmlschema which converts to a string without the time
- # component. This removal may generate an issue on FreeBSD, that's why we
- # need to use remove_possible_method here
- remove_possible_method :xmlschema
-
# Convert to a formatted string. See DATE_FORMATS for predefined formats.
#
# This method is aliased to <tt>to_s</tt>.
@@ -70,6 +64,8 @@ class Date
alias_method :default_inspect, :inspect
alias_method :inspect, :readable_inspect
+ silence_redefinition_of_method :to_time
+
# Converts a Date instance to a Time, where the time is set to the beginning of the day.
# The timezone can be either :local or :utc (default :local).
#
@@ -79,11 +75,16 @@ class Date
# date.to_time(:local) # => 2007-11-10 00:00:00 0800
#
# date.to_time(:utc) # => 2007-11-10 00:00:00 UTC
+ #
+ # NOTE: The :local timezone is Ruby's *process* timezone, i.e. ENV['TZ'].
+ # If the *application's* timezone is needed, then use +in_time_zone+ instead.
def to_time(form = :local)
raise ArgumentError, "Expected :local or :utc, got #{form.inspect}." unless [:local, :utc].include?(form)
::Time.send(form, year, month, day)
end
+ silence_redefinition_of_method :xmlschema
+
# Returns a string which represents the time in used time zone as DateTime
# defined by XML Schema:
#
diff --git a/activesupport/lib/active_support/core_ext/date/zones.rb b/activesupport/lib/active_support/core_ext/date/zones.rb
index da23fe4892..2dcf97cff8 100644
--- a/activesupport/lib/active_support/core_ext/date/zones.rb
+++ b/activesupport/lib/active_support/core_ext/date/zones.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "date"
require "active_support/core_ext/date_and_time/zones"
diff --git a/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb
index f2ba7fdda5..f6cb1a384c 100644
--- a/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/object/try"
module DateAndTime
@@ -18,9 +20,9 @@ module DateAndTime
advance(days: -1)
end
- # Returns a new date/time representing the previous day.
- def prev_day
- advance(days: -1)
+ # Returns a new date/time the specified number of days ago.
+ def prev_day(days = 1)
+ advance(days: -days)
end
# Returns a new date/time representing tomorrow.
@@ -28,9 +30,9 @@ module DateAndTime
advance(days: 1)
end
- # Returns a new date/time representing the next day.
- def next_day
- advance(days: 1)
+ # Returns a new date/time the specified number of days in the future.
+ def next_day(days = 1)
+ advance(days: days)
end
# Returns true if the date/time is today.
@@ -186,9 +188,9 @@ module DateAndTime
end
end
- # Short-hand for months_since(1).
- def next_month
- months_since(1)
+ # Returns a new date/time the specified number of months in the future.
+ def next_month(months = 1)
+ advance(months: months)
end
# Short-hand for months_since(3)
@@ -196,9 +198,9 @@ module DateAndTime
months_since(3)
end
- # Short-hand for years_since(1).
- def next_year
- years_since(1)
+ # Returns a new date/time the specified number of years in the future.
+ def next_year(years = 1)
+ advance(years: years)
end
# Returns a new date/time representing the given day in the previous week.
@@ -221,11 +223,15 @@ module DateAndTime
end
alias_method :last_weekday, :prev_weekday
+ # Returns a new date/time the specified number of months ago.
+ def prev_month(months = 1)
+ advance(months: -months)
+ end
+
# Short-hand for months_ago(1).
- def prev_month
+ def last_month
months_ago(1)
end
- alias_method :last_month, :prev_month
# Short-hand for months_ago(3).
def prev_quarter
@@ -233,11 +239,15 @@ module DateAndTime
end
alias_method :last_quarter, :prev_quarter
+ # Returns a new date/time the specified number of years ago.
+ def prev_year(years = 1)
+ advance(years: -years)
+ end
+
# Short-hand for years_ago(1).
- def prev_year
+ def last_year
years_ago(1)
end
- alias_method :last_year, :prev_year
# Returns the number of days to the start of the week on the given day.
# Week is assumed to start on +start_day+, default is
@@ -320,6 +330,30 @@ module DateAndTime
beginning_of_year..end_of_year
end
+ # Returns a new date/time representing the next occurrence of the specified day of week.
+ #
+ # today = Date.today # => Thu, 14 Dec 2017
+ # today.next_occurring(:monday) # => Mon, 18 Dec 2017
+ # today.next_occurring(:thursday) # => Thu, 21 Dec 2017
+ def next_occurring(day_of_week)
+ current_day_number = wday != 0 ? wday - 1 : 6
+ from_now = DAYS_INTO_WEEK.fetch(day_of_week) - current_day_number
+ from_now += 7 unless from_now > 0
+ advance(days: from_now)
+ end
+
+ # Returns a new date/time representing the previous occurrence of the specified day of week.
+ #
+ # today = Date.today # => Thu, 14 Dec 2017
+ # today.prev_occurring(:monday) # => Mon, 11 Dec 2017
+ # today.prev_occurring(:thursday) # => Thu, 07 Dec 2017
+ def prev_occurring(day_of_week)
+ current_day_number = wday != 0 ? wday - 1 : 6
+ ago = current_day_number - DAYS_INTO_WEEK.fetch(day_of_week)
+ ago += 7 unless ago > 0
+ advance(days: -ago)
+ end
+
private
def first_hour(date_or_time)
date_or_time.acts_like?(:time) ? date_or_time.beginning_of_day : date_or_time
diff --git a/activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb b/activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb
index db95ae0db5..d33c36ef73 100644
--- a/activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb
+++ b/activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/module/attribute_accessors"
module DateAndTime
@@ -9,14 +11,6 @@ module DateAndTime
# of the receiver. For backwards compatibility we're overriding
# this behavior, but new apps will have an initializer that sets
# this to true, because the new behavior is preferred.
- mattr_accessor(:preserve_timezone, instance_writer: false) { false }
-
- def to_time
- if preserve_timezone
- @_to_time_with_instance_offset ||= getlocal(utc_offset)
- else
- @_to_time_with_system_offset ||= getlocal
- end
- end
+ mattr_accessor :preserve_timezone, instance_writer: false, default: false
end
end
diff --git a/activesupport/lib/active_support/core_ext/date_and_time/zones.rb b/activesupport/lib/active_support/core_ext/date_and_time/zones.rb
index edd724f1d0..894fd9b76d 100644
--- a/activesupport/lib/active_support/core_ext/date_and_time/zones.rb
+++ b/activesupport/lib/active_support/core_ext/date_and_time/zones.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module DateAndTime
module Zones
# Returns the simultaneous time in <tt>Time.zone</tt> if a zone is given or
diff --git a/activesupport/lib/active_support/core_ext/date_time.rb b/activesupport/lib/active_support/core_ext/date_time.rb
index 6fd498f864..790dbeec1b 100644
--- a/activesupport/lib/active_support/core_ext/date_time.rb
+++ b/activesupport/lib/active_support/core_ext/date_time.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/date_time/acts_like"
require "active_support/core_ext/date_time/blank"
require "active_support/core_ext/date_time/calculations"
diff --git a/activesupport/lib/active_support/core_ext/date_time/acts_like.rb b/activesupport/lib/active_support/core_ext/date_time/acts_like.rb
index 6f50f55a53..5dccdfe219 100644
--- a/activesupport/lib/active_support/core_ext/date_time/acts_like.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/acts_like.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "date"
require "active_support/core_ext/object/acts_like"
diff --git a/activesupport/lib/active_support/core_ext/date_time/blank.rb b/activesupport/lib/active_support/core_ext/date_time/blank.rb
index b475fd926d..a52c8bc150 100644
--- a/activesupport/lib/active_support/core_ext/date_time/blank.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/blank.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "date"
class DateTime #:nodoc:
diff --git a/activesupport/lib/active_support/core_ext/date_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
index 70d5c9af8e..e61b23f842 100644
--- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "date"
class DateTime
@@ -47,13 +49,23 @@ class DateTime
# DateTime.new(2012, 8, 29, 22, 35, 0).change(year: 1981, day: 1) # => DateTime.new(1981, 8, 1, 22, 35, 0)
# DateTime.new(2012, 8, 29, 22, 35, 0).change(year: 1981, hour: 0) # => DateTime.new(1981, 8, 29, 0, 0, 0)
def change(options)
+ if new_nsec = options[:nsec]
+ raise ArgumentError, "Can't change both :nsec and :usec at the same time: #{options.inspect}" if options[:usec]
+ new_fraction = Rational(new_nsec, 1000000000)
+ else
+ new_usec = options.fetch(:usec, (options[:hour] || options[:min] || options[:sec]) ? 0 : Rational(nsec, 1000))
+ new_fraction = Rational(new_usec, 1000000)
+ end
+
+ raise ArgumentError, "argument out of range" if new_fraction >= 1
+
::DateTime.civil(
options.fetch(:year, year),
options.fetch(:month, month),
options.fetch(:day, day),
options.fetch(:hour, hour),
options.fetch(:min, options[:hour] ? 0 : min),
- options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec + sec_fraction),
+ options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec) + new_fraction,
options.fetch(:offset, offset),
options.fetch(:start, start)
)
@@ -122,7 +134,7 @@ class DateTime
# Returns a new DateTime representing the end of the day (23:59:59).
def end_of_day
- change(hour: 23, min: 59, sec: 59)
+ change(hour: 23, min: 59, sec: 59, usec: Rational(999999999, 1000))
end
alias :at_end_of_day :end_of_day
@@ -134,7 +146,7 @@ class DateTime
# Returns a new DateTime representing the end of the hour (hh:59:59).
def end_of_hour
- change(min: 59, sec: 59)
+ change(min: 59, sec: 59, usec: Rational(999999999, 1000))
end
alias :at_end_of_hour :end_of_hour
@@ -146,7 +158,7 @@ class DateTime
# Returns a new DateTime representing the end of the minute (hh:mm:59).
def end_of_minute
- change(sec: 59)
+ change(sec: 59, usec: Rational(999999999, 1000))
end
alias :at_end_of_minute :end_of_minute
diff --git a/activesupport/lib/active_support/core_ext/date_time/compatibility.rb b/activesupport/lib/active_support/core_ext/date_time/compatibility.rb
index 30bb7f4a60..2d6b49722d 100644
--- a/activesupport/lib/active_support/core_ext/date_time/compatibility.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/compatibility.rb
@@ -1,5 +1,18 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/date_and_time/compatibility"
+require "active_support/core_ext/module/redefine_method"
class DateTime
- prepend DateAndTime::Compatibility
+ include DateAndTime::Compatibility
+
+ silence_redefinition_of_method :to_time
+
+ # Either return an instance of +Time+ with the same UTC offset
+ # as +self+ or an instance of +Time+ representing the same time
+ # in the the local system timezone depending on the setting of
+ # on the setting of +ActiveSupport.to_time_preserves_timezone+.
+ def to_time
+ preserve_timezone ? getlocal(utc_offset) : getlocal
+ end
end
diff --git a/activesupport/lib/active_support/core_ext/date_time/conversions.rb b/activesupport/lib/active_support/core_ext/date_time/conversions.rb
index d9b3743858..29725c89f7 100644
--- a/activesupport/lib/active_support/core_ext/date_time/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/conversions.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "date"
require "active_support/inflector/methods"
require "active_support/core_ext/time/conversions"
diff --git a/activesupport/lib/active_support/core_ext/digest/uuid.rb b/activesupport/lib/active_support/core_ext/digest/uuid.rb
index e6d60e3267..6e949a2d72 100644
--- a/activesupport/lib/active_support/core_ext/digest/uuid.rb
+++ b/activesupport/lib/active_support/core_ext/digest/uuid.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "securerandom"
module Digest
@@ -12,7 +14,7 @@ module Digest
# Using Digest::MD5 generates version 3 UUIDs; Digest::SHA1 generates version 5 UUIDs.
# uuid_from_hash always generates the same UUID for a given name and namespace combination.
#
- # See RFC 4122 for details of UUID at: http://www.ietf.org/rfc/rfc4122.txt
+ # See RFC 4122 for details of UUID at: https://www.ietf.org/rfc/rfc4122.txt
def self.uuid_from_hash(hash_class, uuid_namespace, name)
if hash_class == Digest::MD5
version = 3
diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb
index 90d7d2947f..17733d955c 100644
--- a/activesupport/lib/active_support/core_ext/enumerable.rb
+++ b/activesupport/lib/active_support/core_ext/enumerable.rb
@@ -1,28 +1,52 @@
+# frozen_string_literal: true
+
module Enumerable
- # Calculates a sum from the elements.
- #
- # payments.sum { |p| p.price * p.tax_rate }
- # payments.sum(&:price)
- #
- # The latter is a shortcut for:
- #
- # payments.inject(0) { |sum, p| sum + p.price }
- #
- # It can also calculate the sum without the use of a block.
- #
- # [5, 15, 10].sum # => 30
- # ['foo', 'bar'].sum # => "foobar"
- # [[1, 2], [3, 1, 5]].sum # => [1, 2, 3, 1, 5]
- #
- # The default sum of an empty list is zero. You can override this default:
+ # Enumerable#sum was added in Ruby 2.4, but it only works with Numeric elements
+ # when we omit an identity.
#
- # [].sum(Payment.new(0)) { |i| i.amount } # => Payment.new(0)
- def sum(identity = nil, &block)
- if block_given?
- map(&block).sum(identity)
- else
- sum = identity ? inject(identity, :+) : inject(:+)
- sum || identity || 0
+ # We tried shimming it to attempt the fast native method, rescue TypeError,
+ # and fall back to the compatible implementation, but that's much slower than
+ # just calling the compat method in the first place.
+ if Enumerable.instance_methods(false).include?(:sum) && !((?a..?b).sum rescue false)
+ # We can't use Refinements here because Refinements with Module which will be prepended
+ # doesn't work well https://bugs.ruby-lang.org/issues/13446
+ alias :_original_sum_with_required_identity :sum
+ private :_original_sum_with_required_identity
+ # Calculates a sum from the elements.
+ #
+ # payments.sum { |p| p.price * p.tax_rate }
+ # payments.sum(&:price)
+ #
+ # The latter is a shortcut for:
+ #
+ # payments.inject(0) { |sum, p| sum + p.price }
+ #
+ # It can also calculate the sum without the use of a block.
+ #
+ # [5, 15, 10].sum # => 30
+ # ['foo', 'bar'].sum # => "foobar"
+ # [[1, 2], [3, 1, 5]].sum # => [1, 2, 3, 1, 5]
+ #
+ # The default sum of an empty list is zero. You can override this default:
+ #
+ # [].sum(Payment.new(0)) { |i| i.amount } # => Payment.new(0)
+ def sum(identity = nil, &block)
+ if identity
+ _original_sum_with_required_identity(identity, &block)
+ elsif block_given?
+ map(&block).sum(identity)
+ else
+ inject(:+) || 0
+ end
+ end
+ else
+ def sum(identity = nil, &block)
+ if block_given?
+ map(&block).sum(identity)
+ else
+ sum = identity ? inject(identity, :+) : inject(:+)
+ sum || identity || 0
+ end
end
end
diff --git a/activesupport/lib/active_support/core_ext/file.rb b/activesupport/lib/active_support/core_ext/file.rb
index 6d99bad2af..64553bfa4e 100644
--- a/activesupport/lib/active_support/core_ext/file.rb
+++ b/activesupport/lib/active_support/core_ext/file.rb
@@ -1 +1,3 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/file/atomic"
diff --git a/activesupport/lib/active_support/core_ext/file/atomic.rb b/activesupport/lib/active_support/core_ext/file/atomic.rb
index 8d6c0d3685..8e288833b6 100644
--- a/activesupport/lib/active_support/core_ext/file/atomic.rb
+++ b/activesupport/lib/active_support/core_ext/file/atomic.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "fileutils"
class File
diff --git a/activesupport/lib/active_support/core_ext/hash.rb b/activesupport/lib/active_support/core_ext/hash.rb
index c819307e8a..e19aeaa983 100644
--- a/activesupport/lib/active_support/core_ext/hash.rb
+++ b/activesupport/lib/active_support/core_ext/hash.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/hash/compact"
require "active_support/core_ext/hash/conversions"
require "active_support/core_ext/hash/deep_merge"
diff --git a/activesupport/lib/active_support/core_ext/hash/compact.rb b/activesupport/lib/active_support/core_ext/hash/compact.rb
index e357284be0..d6364dd9f3 100644
--- a/activesupport/lib/active_support/core_ext/hash/compact.rb
+++ b/activesupport/lib/active_support/core_ext/hash/compact.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Hash
unless Hash.instance_methods(false).include?(:compact)
# Returns a hash with non +nil+ values.
diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb
index 2a58a7f1ca..11d28d12a1 100644
--- a/activesupport/lib/active_support/core_ext/hash/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/xml_mini"
require "active_support/time"
require "active_support/core_ext/object/blank"
diff --git a/activesupport/lib/active_support/core_ext/hash/deep_merge.rb b/activesupport/lib/active_support/core_ext/hash/deep_merge.rb
index 9c9faf67ea..9bc50b7bc6 100644
--- a/activesupport/lib/active_support/core_ext/hash/deep_merge.rb
+++ b/activesupport/lib/active_support/core_ext/hash/deep_merge.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Hash
# Returns a new hash with +self+ and +other_hash+ merged recursively.
#
@@ -19,20 +21,14 @@ class Hash
# Same as +deep_merge+, but modifies +self+.
def deep_merge!(other_hash, &block)
- other_hash.each_pair do |current_key, other_value|
- this_value = self[current_key]
-
- self[current_key] = if this_value.is_a?(Hash) && other_value.is_a?(Hash)
- this_value.deep_merge(other_value, &block)
+ merge!(other_hash) do |key, this_val, other_val|
+ if this_val.is_a?(Hash) && other_val.is_a?(Hash)
+ this_val.deep_merge(other_val, &block)
+ elsif block_given?
+ block.call(key, this_val, other_val)
else
- if block_given? && key?(current_key)
- block.call(current_key, this_value, other_value)
- else
- other_value
- end
+ other_val
end
end
-
- self
end
end
diff --git a/activesupport/lib/active_support/core_ext/hash/except.rb b/activesupport/lib/active_support/core_ext/hash/except.rb
index 2f6d38c1f6..6258610c98 100644
--- a/activesupport/lib/active_support/core_ext/hash/except.rb
+++ b/activesupport/lib/active_support/core_ext/hash/except.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Hash
# Returns a hash that includes everything except given keys.
# hash = { a: true, b: false, c: nil }
diff --git a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb
index 3e1ccecb6c..a38f33f128 100644
--- a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb
+++ b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/hash_with_indifferent_access"
class Hash
diff --git a/activesupport/lib/active_support/core_ext/hash/keys.rb b/activesupport/lib/active_support/core_ext/hash/keys.rb
index b7089357a8..bdf196ec3d 100644
--- a/activesupport/lib/active_support/core_ext/hash/keys.rb
+++ b/activesupport/lib/active_support/core_ext/hash/keys.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Hash
# Returns a new hash with all keys converted using the +block+ operation.
#
@@ -16,7 +18,7 @@ class Hash
result[yield(key)] = self[key]
end
result
- end
+ end unless method_defined? :transform_keys
# Destructively converts all keys using the +block+ operations.
# Same as +transform_keys+ but modifies +self+.
@@ -26,7 +28,7 @@ class Hash
self[yield(key)] = delete(key)
end
self
- end
+ end unless method_defined? :transform_keys!
# Returns a new hash with all keys converted to strings.
#
diff --git a/activesupport/lib/active_support/core_ext/hash/reverse_merge.rb b/activesupport/lib/active_support/core_ext/hash/reverse_merge.rb
index efb9d1b8a0..ef8d592829 100644
--- a/activesupport/lib/active_support/core_ext/hash/reverse_merge.rb
+++ b/activesupport/lib/active_support/core_ext/hash/reverse_merge.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Hash
# Merges the caller into +other_hash+. For example,
#
@@ -12,11 +14,12 @@ class Hash
def reverse_merge(other_hash)
other_hash.merge(self)
end
+ alias_method :with_defaults, :reverse_merge
# Destructive +reverse_merge+.
def reverse_merge!(other_hash)
- # right wins if there is no left
- merge!(other_hash) { |key, left, right| left }
+ replace(reverse_merge(other_hash))
end
alias_method :reverse_update, :reverse_merge!
+ alias_method :with_defaults!, :reverse_merge!
end
diff --git a/activesupport/lib/active_support/core_ext/hash/slice.rb b/activesupport/lib/active_support/core_ext/hash/slice.rb
index 161b00dfb3..2bd0a56ea4 100644
--- a/activesupport/lib/active_support/core_ext/hash/slice.rb
+++ b/activesupport/lib/active_support/core_ext/hash/slice.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Hash
# Slices a hash to include only the given keys. Returns a hash containing
# the given keys.
@@ -19,9 +21,8 @@ class Hash
# valid_keys = [:mass, :velocity, :time]
# search(options.slice(*valid_keys))
def slice(*keys)
- keys.map! { |key| convert_key(key) } if respond_to?(:convert_key, true)
- keys.each_with_object(self.class.new) { |k, hash| hash[k] = self[k] if has_key?(k) }
- end
+ keys.each_with_object(Hash.new) { |k, hash| hash[k] = self[k] if has_key?(k) }
+ end unless method_defined?(:slice)
# Replaces the hash with only the given keys.
# Returns a hash containing the removed key/value pairs.
@@ -29,7 +30,6 @@ class Hash
# { a: 1, b: 2, c: 3, d: 4 }.slice!(:a, :b)
# # => {:c=>3, :d=>4}
def slice!(*keys)
- keys.map! { |key| convert_key(key) } if respond_to?(:convert_key, true)
omit = slice(*self.keys - keys)
hash = slice(*keys)
hash.default = default
diff --git a/activesupport/lib/active_support/core_ext/hash/transform_values.rb b/activesupport/lib/active_support/core_ext/hash/transform_values.rb
index 2f693bff0c..4b19c9fc1f 100644
--- a/activesupport/lib/active_support/core_ext/hash/transform_values.rb
+++ b/activesupport/lib/active_support/core_ext/hash/transform_values.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Hash
# Returns a new hash with the results of running +block+ once for every value.
# The keys are unchanged.
diff --git a/activesupport/lib/active_support/core_ext/integer.rb b/activesupport/lib/active_support/core_ext/integer.rb
index 8f0c55f9d3..d22701306a 100644
--- a/activesupport/lib/active_support/core_ext/integer.rb
+++ b/activesupport/lib/active_support/core_ext/integer.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/integer/multiple"
require "active_support/core_ext/integer/inflections"
require "active_support/core_ext/integer/time"
diff --git a/activesupport/lib/active_support/core_ext/integer/inflections.rb b/activesupport/lib/active_support/core_ext/integer/inflections.rb
index bc21b65533..aef3266f28 100644
--- a/activesupport/lib/active_support/core_ext/integer/inflections.rb
+++ b/activesupport/lib/active_support/core_ext/integer/inflections.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/inflector"
class Integer
diff --git a/activesupport/lib/active_support/core_ext/integer/multiple.rb b/activesupport/lib/active_support/core_ext/integer/multiple.rb
index c668c7c2eb..e7606662d3 100644
--- a/activesupport/lib/active_support/core_ext/integer/multiple.rb
+++ b/activesupport/lib/active_support/core_ext/integer/multiple.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Integer
# Check whether the integer is evenly divisible by the argument.
#
diff --git a/activesupport/lib/active_support/core_ext/integer/time.rb b/activesupport/lib/active_support/core_ext/integer/time.rb
index 74baae3639..5efb89cf9f 100644
--- a/activesupport/lib/active_support/core_ext/integer/time.rb
+++ b/activesupport/lib/active_support/core_ext/integer/time.rb
@@ -1,27 +1,20 @@
+# frozen_string_literal: true
+
require "active_support/duration"
require "active_support/core_ext/numeric/time"
class Integer
- # Enables the use of time calculations and declarations, like <tt>45.minutes +
- # 2.hours + 4.years</tt>.
- #
- # These methods use Time#advance for precise date calculations when using
- # <tt>from_now</tt>, +ago+, etc. as well as adding or subtracting their
- # results from a Time object.
- #
- # # equivalent to Time.now.advance(months: 1)
- # 1.month.from_now
+ # Returns a Duration instance matching the number of months provided.
#
- # # equivalent to Time.now.advance(years: 2)
- # 2.years.from_now
- #
- # # equivalent to Time.now.advance(months: 4, years: 5)
- # (4.months + 5.years).from_now
+ # 2.months # => 2 months
def months
ActiveSupport::Duration.months(self)
end
alias :month :months
+ # Returns a Duration instance matching the number of years provided.
+ #
+ # 2.years # => 2 years
def years
ActiveSupport::Duration.years(self)
end
diff --git a/activesupport/lib/active_support/core_ext/kernel.rb b/activesupport/lib/active_support/core_ext/kernel.rb
index 3d41ff7876..0f4356fbdd 100644
--- a/activesupport/lib/active_support/core_ext/kernel.rb
+++ b/activesupport/lib/active_support/core_ext/kernel.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/kernel/agnostics"
require "active_support/core_ext/kernel/concern"
require "active_support/core_ext/kernel/reporting"
diff --git a/activesupport/lib/active_support/core_ext/kernel/agnostics.rb b/activesupport/lib/active_support/core_ext/kernel/agnostics.rb
index 64837d87aa..403b5f31f0 100644
--- a/activesupport/lib/active_support/core_ext/kernel/agnostics.rb
+++ b/activesupport/lib/active_support/core_ext/kernel/agnostics.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Object
# Makes backticks behave (somewhat more) similarly on all platforms.
# On win32 `nonexistent_command` raises Errno::ENOENT; on Unix, the
diff --git a/activesupport/lib/active_support/core_ext/kernel/concern.rb b/activesupport/lib/active_support/core_ext/kernel/concern.rb
index 307a7f7a63..0b2baed780 100644
--- a/activesupport/lib/active_support/core_ext/kernel/concern.rb
+++ b/activesupport/lib/active_support/core_ext/kernel/concern.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/module/concerning"
module Kernel
diff --git a/activesupport/lib/active_support/core_ext/kernel/reporting.rb b/activesupport/lib/active_support/core_ext/kernel/reporting.rb
index c02618d5f3..9155bd6c10 100644
--- a/activesupport/lib/active_support/core_ext/kernel/reporting.rb
+++ b/activesupport/lib/active_support/core_ext/kernel/reporting.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Kernel
module_function
diff --git a/activesupport/lib/active_support/core_ext/kernel/singleton_class.rb b/activesupport/lib/active_support/core_ext/kernel/singleton_class.rb
index 9bbf1bbd73..6715eba80a 100644
--- a/activesupport/lib/active_support/core_ext/kernel/singleton_class.rb
+++ b/activesupport/lib/active_support/core_ext/kernel/singleton_class.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Kernel
# class_eval on an object acts like singleton_class.class_eval.
def class_eval(*args, &block)
diff --git a/activesupport/lib/active_support/core_ext/load_error.rb b/activesupport/lib/active_support/core_ext/load_error.rb
index d273487010..750f858fcc 100644
--- a/activesupport/lib/active_support/core_ext/load_error.rb
+++ b/activesupport/lib/active_support/core_ext/load_error.rb
@@ -1,11 +1,6 @@
-class LoadError
- REGEXPS = [
- /^no such file to load -- (.+)$/i,
- /^Missing \w+ (?:file\s*)?([^\s]+.rb)$/i,
- /^Missing API definition file in (.+)$/i,
- /^cannot load such file -- (.+)$/i,
- ]
+# frozen_string_literal: true
+class LoadError
# Returns true if the given path name (except perhaps for the ".rb"
# extension) is the missing file which caused the exception to be raised.
def is_missing?(location)
diff --git a/activesupport/lib/active_support/core_ext/marshal.rb b/activesupport/lib/active_support/core_ext/marshal.rb
index edfc8296fe..0c72cd7b47 100644
--- a/activesupport/lib/active_support/core_ext/marshal.rb
+++ b/activesupport/lib/active_support/core_ext/marshal.rb
@@ -1,7 +1,9 @@
+# frozen_string_literal: true
+
module ActiveSupport
module MarshalWithAutoloading # :nodoc:
- def load(source)
- super(source)
+ def load(source, proc = nil)
+ super(source, proc)
rescue ArgumentError, NameError => exc
if exc.message.match(%r|undefined class/module (.+?)(?:::)?\z|)
# try loading the class/module
diff --git a/activesupport/lib/active_support/core_ext/module.rb b/activesupport/lib/active_support/core_ext/module.rb
index 2930255557..d91e3fba6a 100644
--- a/activesupport/lib/active_support/core_ext/module.rb
+++ b/activesupport/lib/active_support/core_ext/module.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/module/aliasing"
require "active_support/core_ext/module/introspection"
require "active_support/core_ext/module/anonymous"
@@ -8,4 +10,5 @@ require "active_support/core_ext/module/attr_internal"
require "active_support/core_ext/module/concerning"
require "active_support/core_ext/module/delegation"
require "active_support/core_ext/module/deprecation"
+require "active_support/core_ext/module/redefine_method"
require "active_support/core_ext/module/remove_method"
diff --git a/activesupport/lib/active_support/core_ext/module/aliasing.rb b/activesupport/lib/active_support/core_ext/module/aliasing.rb
index c48bd3354a..6f64d11627 100644
--- a/activesupport/lib/active_support/core_ext/module/aliasing.rb
+++ b/activesupport/lib/active_support/core_ext/module/aliasing.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Module
# Allows you to make aliases for attributes, which includes
# getter, setter, and a predicate.
diff --git a/activesupport/lib/active_support/core_ext/module/anonymous.rb b/activesupport/lib/active_support/core_ext/module/anonymous.rb
index 510c9a5430..d1c86b8722 100644
--- a/activesupport/lib/active_support/core_ext/module/anonymous.rb
+++ b/activesupport/lib/active_support/core_ext/module/anonymous.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Module
# A module may or may not have a name.
#
diff --git a/activesupport/lib/active_support/core_ext/module/attr_internal.rb b/activesupport/lib/active_support/core_ext/module/attr_internal.rb
index 5081d5f7a3..7801f6d181 100644
--- a/activesupport/lib/active_support/core_ext/module/attr_internal.rb
+++ b/activesupport/lib/active_support/core_ext/module/attr_internal.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Module
# Declares an attribute reader backed by an internally-named instance variable.
def attr_internal_reader(*attrs)
diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
index 2c24081eb9..580baffa2b 100644
--- a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
+++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/array/extract_options"
require "active_support/core_ext/regexp"
@@ -38,13 +40,10 @@ class Module
#
# Person.new.hair_colors # => NoMethodError
#
- #
- # Also, you can pass a block to set up the attribute with a default value.
+ # You can set a default value for the attribute.
#
# module HairColors
- # mattr_reader :hair_colors do
- # [:brown, :black, :blonde, :red]
- # end
+ # mattr_reader :hair_colors, default: [:brown, :black, :blonde, :red]
# end
#
# class Person
@@ -52,8 +51,7 @@ class Module
# end
#
# Person.new.hair_colors # => [:brown, :black, :blonde, :red]
- def mattr_reader(*syms)
- options = syms.extract_options!
+ def mattr_reader(*syms, instance_reader: true, instance_accessor: true, default: nil)
syms.each do |sym|
raise NameError.new("invalid attribute name: #{sym}") unless /\A[_A-Za-z]\w*\z/.match?(sym)
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
@@ -64,14 +62,16 @@ class Module
end
EOS
- unless options[:instance_reader] == false || options[:instance_accessor] == false
+ if instance_reader && instance_accessor
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
def #{sym}
@@#{sym}
end
EOS
end
- class_variable_set("@@#{sym}", yield) if block_given?
+
+ sym_default_value = (block_given? && default.nil?) ? yield : default
+ class_variable_set("@@#{sym}", sym_default_value) unless sym_default_value.nil?
end
end
alias :cattr_reader :mattr_reader
@@ -107,12 +107,10 @@ class Module
#
# Person.new.hair_colors = [:blonde, :red] # => NoMethodError
#
- # Also, you can pass a block to set up the attribute with a default value.
+ # You can set a default value for the attribute.
#
# module HairColors
- # mattr_writer :hair_colors do
- # [:brown, :black, :blonde, :red]
- # end
+ # mattr_writer :hair_colors, default: [:brown, :black, :blonde, :red]
# end
#
# class Person
@@ -120,8 +118,7 @@ class Module
# end
#
# Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red]
- def mattr_writer(*syms)
- options = syms.extract_options!
+ def mattr_writer(*syms, instance_writer: true, instance_accessor: true, default: nil)
syms.each do |sym|
raise NameError.new("invalid attribute name: #{sym}") unless /\A[_A-Za-z]\w*\z/.match?(sym)
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
@@ -132,14 +129,16 @@ class Module
end
EOS
- unless options[:instance_writer] == false || options[:instance_accessor] == false
+ if instance_writer && instance_accessor
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
def #{sym}=(obj)
@@#{sym} = obj
end
EOS
end
- send("#{sym}=", yield) if block_given?
+
+ sym_default_value = (block_given? && default.nil?) ? yield : default
+ send("#{sym}=", sym_default_value) unless sym_default_value.nil?
end
end
alias :cattr_writer :mattr_writer
@@ -197,12 +196,10 @@ class Module
# Person.new.hair_colors = [:brown] # => NoMethodError
# Person.new.hair_colors # => NoMethodError
#
- # Also you can pass a block to set up the attribute with a default value.
+ # You can set a default value for the attribute.
#
# module HairColors
- # mattr_accessor :hair_colors do
- # [:brown, :black, :blonde, :red]
- # end
+ # mattr_accessor :hair_colors, default: [:brown, :black, :blonde, :red]
# end
#
# class Person
@@ -210,9 +207,9 @@ class Module
# end
#
# Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red]
- def mattr_accessor(*syms, &blk)
- mattr_reader(*syms, &blk)
- mattr_writer(*syms)
+ def mattr_accessor(*syms, instance_reader: true, instance_writer: true, instance_accessor: true, default: nil, &blk)
+ mattr_reader(*syms, instance_reader: instance_reader, instance_accessor: instance_accessor, default: default, &blk)
+ mattr_writer(*syms, instance_writer: instance_writer, instance_accessor: instance_accessor, default: default)
end
alias :cattr_accessor :mattr_accessor
end
diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb
index 1e82b4acc2..4b9b6ea9bd 100644
--- a/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb
+++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/array/extract_options"
require "active_support/core_ext/regexp"
diff --git a/activesupport/lib/active_support/core_ext/module/concerning.rb b/activesupport/lib/active_support/core_ext/module/concerning.rb
index 97b0a382ce..800bf213cc 100644
--- a/activesupport/lib/active_support/core_ext/module/concerning.rb
+++ b/activesupport/lib/active_support/core_ext/module/concerning.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/concern"
class Module
@@ -20,7 +22,7 @@ class Module
#
# == Using comments:
#
- # class Todo
+ # class Todo < ApplicationRecord
# # Other todo implementation
# # ...
#
@@ -40,7 +42,7 @@ class Module
#
# Noisy syntax.
#
- # class Todo
+ # class Todo < ApplicationRecord
# # Other todo implementation
# # ...
#
@@ -68,7 +70,7 @@ class Module
# increased overhead can be a reasonable tradeoff even if it reduces our
# at-a-glance perception of how things work.
#
- # class Todo
+ # class Todo < ApplicationRecord
# # Other todo implementation
# # ...
#
@@ -80,7 +82,7 @@ class Module
# By quieting the mix-in noise, we arrive at a natural, low-ceremony way to
# separate bite-sized concerns.
#
- # class Todo
+ # class Todo < ApplicationRecord
# # Other todo implementation
# # ...
#
@@ -99,7 +101,7 @@ class Module
# end
#
# Todo.ancestors
- # # => [Todo, Todo::EventTracking, Object]
+ # # => [Todo, Todo::EventTracking, ApplicationRecord, Object]
#
# This small step has some wonderful ripple effects. We can
# * grok the behavior of our class in one glance,
diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb
index d82758e40d..4310df3024 100644
--- a/activesupport/lib/active_support/core_ext/module/delegation.rb
+++ b/activesupport/lib/active_support/core_ext/module/delegation.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "set"
require "active_support/core_ext/regexp"
@@ -113,11 +115,8 @@ class Module
# invoice.customer_address # => 'Vimmersvej 13'
#
# If the target is +nil+ and does not respond to the delegated method a
- # +Module::DelegationError+ is raised, as with any other value. Sometimes,
- # however, it makes sense to be robust to that situation and that is the
- # purpose of the <tt>:allow_nil</tt> option: If the target is not +nil+, or it
- # is and responds to the method, everything works as usual. But if it is +nil+
- # and does not respond to the delegated method, +nil+ is returned.
+ # +Module::DelegationError+ is raised. If you wish to instead return +nil+,
+ # use the <tt>:allow_nil</tt> option.
#
# class User < ActiveRecord::Base
# has_one :profile
@@ -174,7 +173,7 @@ class Module
to = to.to_s
to = "self.#{to}" if DELEGATION_RESERVED_METHOD_NAMES.include?(to)
- methods.each do |method|
+ methods.map do |method|
# Attribute writer methods only accept one argument. Makes sure []=
# methods still accept two arguments.
definition = /[^\]]=$/.match?(method) ? "arg" : "*args, &block"
@@ -219,62 +218,68 @@ class Module
# When building decorators, a common pattern may emerge:
#
# class Partition
- # def initialize(first_event)
- # @events = [ first_event ]
+ # def initialize(event)
+ # @event = event
# end
#
- # def people
- # if @events.first.detail.people.any?
- # @events.collect { |e| Array(e.detail.people) }.flatten.uniq
- # else
- # @events.collect(&:creator).uniq
- # end
+ # def person
+ # @event.detail.person || @event.creator
# end
#
# private
# def respond_to_missing?(name, include_private = false)
- # @events.respond_to?(name, include_private)
+ # @event.respond_to?(name, include_private)
# end
#
# def method_missing(method, *args, &block)
- # @events.send(method, *args, &block)
+ # @event.send(method, *args, &block)
# end
# end
#
- # With `Module#delegate_missing_to`, the above is condensed to:
+ # With <tt>Module#delegate_missing_to</tt>, the above is condensed to:
#
# class Partition
- # delegate_missing_to :@events
+ # delegate_missing_to :@event
#
- # def initialize(first_event)
- # @events = [ first_event ]
+ # def initialize(event)
+ # @event = event
# end
#
- # def people
- # if @events.first.detail.people.any?
- # @events.collect { |e| Array(e.detail.people) }.flatten.uniq
- # else
- # @events.collect(&:creator).uniq
- # end
+ # def person
+ # @event.detail.person || @event.creator
# end
# end
#
- # The target can be anything callable within the object. E.g. instance
- # variables, methods, constants ant the likes.
+ # The target can be anything callable within the object, e.g. instance
+ # variables, methods, constants, etc.
+ #
+ # The delegated method must be public on the target, otherwise it will
+ # raise +NoMethodError+.
def delegate_missing_to(target)
target = target.to_s
target = "self.#{target}" if DELEGATION_RESERVED_METHOD_NAMES.include?(target)
module_eval <<-RUBY, __FILE__, __LINE__ + 1
def respond_to_missing?(name, include_private = false)
- #{target}.respond_to?(name, include_private)
+ # It may look like an oversight, but we deliberately do not pass
+ # +include_private+, because they do not get delegated.
+
+ #{target}.respond_to?(name) || super
end
def method_missing(method, *args, &block)
if #{target}.respond_to?(method)
#{target}.public_send(method, *args, &block)
else
- super
+ begin
+ super
+ rescue NoMethodError
+ if #{target}.nil?
+ raise DelegationError, "\#{method} delegated to #{target}, but #{target} is nil"
+ else
+ raise
+ end
+ end
end
end
RUBY
diff --git a/activesupport/lib/active_support/core_ext/module/deprecation.rb b/activesupport/lib/active_support/core_ext/module/deprecation.rb
index f3f2e7f5fc..71c42eb357 100644
--- a/activesupport/lib/active_support/core_ext/module/deprecation.rb
+++ b/activesupport/lib/active_support/core_ext/module/deprecation.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Module
# deprecate :foo
# deprecate bar: 'message'
diff --git a/activesupport/lib/active_support/core_ext/module/introspection.rb b/activesupport/lib/active_support/core_ext/module/introspection.rb
index ca20a6d4c5..c5bb598bd1 100644
--- a/activesupport/lib/active_support/core_ext/module/introspection.rb
+++ b/activesupport/lib/active_support/core_ext/module/introspection.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/inflector"
class Module
diff --git a/activesupport/lib/active_support/core_ext/module/reachable.rb b/activesupport/lib/active_support/core_ext/module/reachable.rb
index b89a38f26c..e9cbda5245 100644
--- a/activesupport/lib/active_support/core_ext/module/reachable.rb
+++ b/activesupport/lib/active_support/core_ext/module/reachable.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/module/anonymous"
require "active_support/core_ext/string/inflections"
@@ -5,4 +7,5 @@ class Module
def reachable? #:nodoc:
!anonymous? && name.safe_constantize.equal?(self)
end
+ deprecate :reachable?
end
diff --git a/activesupport/lib/active_support/core_ext/module/redefine_method.rb b/activesupport/lib/active_support/core_ext/module/redefine_method.rb
new file mode 100644
index 0000000000..a0a6622ca4
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/module/redefine_method.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+class Module
+ if RUBY_VERSION >= "2.3"
+ # Marks the named method as intended to be redefined, if it exists.
+ # Suppresses the Ruby method redefinition warning. Prefer
+ # #redefine_method where possible.
+ def silence_redefinition_of_method(method)
+ if method_defined?(method) || private_method_defined?(method)
+ # This suppresses the "method redefined" warning; the self-alias
+ # looks odd, but means we don't need to generate a unique name
+ alias_method method, method
+ end
+ end
+ else
+ def silence_redefinition_of_method(method)
+ if method_defined?(method) || private_method_defined?(method)
+ alias_method :__rails_redefine, method
+ remove_method :__rails_redefine
+ end
+ end
+ end
+
+ # Replaces the existing method definition, if there is one, with the passed
+ # block as its body.
+ def redefine_method(method, &block)
+ visibility = method_visibility(method)
+ silence_redefinition_of_method(method)
+ define_method(method, &block)
+ send(visibility, method)
+ end
+
+ # Replaces the existing singleton method definition, if there is one, with
+ # the passed block as its body.
+ def redefine_singleton_method(method, &block)
+ singleton_class.redefine_method(method, &block)
+ end
+
+ def method_visibility(method) # :nodoc:
+ case
+ when private_method_defined?(method)
+ :private
+ when protected_method_defined?(method)
+ :protected
+ else
+ :public
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/module/remove_method.rb b/activesupport/lib/active_support/core_ext/module/remove_method.rb
index d5ec16d68a..97eb5f9eca 100644
--- a/activesupport/lib/active_support/core_ext/module/remove_method.rb
+++ b/activesupport/lib/active_support/core_ext/module/remove_method.rb
@@ -1,3 +1,7 @@
+# frozen_string_literal: true
+
+require "active_support/core_ext/module/redefine_method"
+
class Module
# Removes the named method, if it exists.
def remove_possible_method(method)
@@ -8,28 +12,6 @@ class Module
# Removes the named singleton method, if it exists.
def remove_possible_singleton_method(method)
- singleton_class.instance_eval do
- remove_possible_method(method)
- end
- end
-
- # Replaces the existing method definition, if there is one, with the passed
- # block as its body.
- def redefine_method(method, &block)
- visibility = method_visibility(method)
- remove_possible_method(method)
- define_method(method, &block)
- send(visibility, method)
- end
-
- def method_visibility(method) # :nodoc:
- case
- when private_method_defined?(method)
- :private
- when protected_method_defined?(method)
- :protected
- else
- :public
- end
+ singleton_class.remove_possible_method(method)
end
end
diff --git a/activesupport/lib/active_support/core_ext/name_error.rb b/activesupport/lib/active_support/core_ext/name_error.rb
index 6b447d772b..d4f1e01140 100644
--- a/activesupport/lib/active_support/core_ext/name_error.rb
+++ b/activesupport/lib/active_support/core_ext/name_error.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class NameError
# Extract the name of the missing constant from the exception message.
#
diff --git a/activesupport/lib/active_support/core_ext/numeric.rb b/activesupport/lib/active_support/core_ext/numeric.rb
index 6062f9e3a8..0b04e359f9 100644
--- a/activesupport/lib/active_support/core_ext/numeric.rb
+++ b/activesupport/lib/active_support/core_ext/numeric.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/numeric/bytes"
require "active_support/core_ext/numeric/time"
require "active_support/core_ext/numeric/inquiry"
diff --git a/activesupport/lib/active_support/core_ext/numeric/bytes.rb b/activesupport/lib/active_support/core_ext/numeric/bytes.rb
index dfbca32474..b002eba406 100644
--- a/activesupport/lib/active_support/core_ext/numeric/bytes.rb
+++ b/activesupport/lib/active_support/core_ext/numeric/bytes.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Numeric
KILOBYTE = 1024
MEGABYTE = KILOBYTE * 1024
diff --git a/activesupport/lib/active_support/core_ext/numeric/conversions.rb b/activesupport/lib/active_support/core_ext/numeric/conversions.rb
index 4f6621693e..f6c2713986 100644
--- a/activesupport/lib/active_support/core_ext/numeric/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/numeric/conversions.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/big_decimal/conversions"
require "active_support/number_helper"
require "active_support/core_ext/module/deprecation"
@@ -106,19 +108,19 @@ module ActiveSupport::NumericWithFormat
when Integer, String
super(format)
when :phone
- return ActiveSupport::NumberHelper.number_to_phone(self, options || {})
+ ActiveSupport::NumberHelper.number_to_phone(self, options || {})
when :currency
- return ActiveSupport::NumberHelper.number_to_currency(self, options || {})
+ ActiveSupport::NumberHelper.number_to_currency(self, options || {})
when :percentage
- return ActiveSupport::NumberHelper.number_to_percentage(self, options || {})
+ ActiveSupport::NumberHelper.number_to_percentage(self, options || {})
when :delimited
- return ActiveSupport::NumberHelper.number_to_delimited(self, options || {})
+ ActiveSupport::NumberHelper.number_to_delimited(self, options || {})
when :rounded
- return ActiveSupport::NumberHelper.number_to_rounded(self, options || {})
+ ActiveSupport::NumberHelper.number_to_rounded(self, options || {})
when :human
- return ActiveSupport::NumberHelper.number_to_human(self, options || {})
+ ActiveSupport::NumberHelper.number_to_human(self, options || {})
when :human_size
- return ActiveSupport::NumberHelper.number_to_human_size(self, options || {})
+ ActiveSupport::NumberHelper.number_to_human_size(self, options || {})
when Symbol
super()
else
diff --git a/activesupport/lib/active_support/core_ext/numeric/inquiry.rb b/activesupport/lib/active_support/core_ext/numeric/inquiry.rb
index ec79701189..15334c91f1 100644
--- a/activesupport/lib/active_support/core_ext/numeric/inquiry.rb
+++ b/activesupport/lib/active_support/core_ext/numeric/inquiry.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
unless 1.respond_to?(:positive?) # TODO: Remove this file when we drop support to ruby < 2.3
class Numeric
# Returns true if the number is positive.
diff --git a/activesupport/lib/active_support/core_ext/numeric/time.rb b/activesupport/lib/active_support/core_ext/numeric/time.rb
index 2e6c70d418..bc4627f7a2 100644
--- a/activesupport/lib/active_support/core_ext/numeric/time.rb
+++ b/activesupport/lib/active_support/core_ext/numeric/time.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/duration"
require "active_support/core_ext/time/calculations"
require "active_support/core_ext/time/acts_like"
@@ -5,19 +7,9 @@ require "active_support/core_ext/date/calculations"
require "active_support/core_ext/date/acts_like"
class Numeric
- # Enables the use of time calculations and declarations, like 45.minutes + 2.hours + 4.years.
- #
- # These methods use Time#advance for precise date calculations when using from_now, ago, etc.
- # as well as adding or subtracting their results from a Time object. For example:
- #
- # # equivalent to Time.current.advance(months: 1)
- # 1.month.from_now
- #
- # # equivalent to Time.current.advance(years: 2)
- # 2.years.from_now
+ # Returns a Duration instance matching the number of seconds provided.
#
- # # equivalent to Time.current.advance(months: 4, years: 5)
- # (4.months + 5.years).from_now
+ # 2.seconds # => 2 seconds
def seconds
ActiveSupport::Duration.seconds(self)
end
@@ -64,10 +56,10 @@ class Numeric
alias :fortnight :fortnights
# Returns the number of milliseconds equivalent to the seconds provided.
- # Used with the standard time durations, like 1.hour.in_milliseconds --
- # so we can feed them to JavaScript functions like getTime().
+ # Used with the standard time durations.
#
- # 2.in_milliseconds # => 2_000
+ # 2.in_milliseconds # => 2000
+ # 1.hour.in_milliseconds # => 3600000
def in_milliseconds
self * 1000
end
diff --git a/activesupport/lib/active_support/core_ext/object.rb b/activesupport/lib/active_support/core_ext/object.rb
index 58bbf78601..efd34cc692 100644
--- a/activesupport/lib/active_support/core_ext/object.rb
+++ b/activesupport/lib/active_support/core_ext/object.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/object/acts_like"
require "active_support/core_ext/object/blank"
require "active_support/core_ext/object/duplicable"
diff --git a/activesupport/lib/active_support/core_ext/object/acts_like.rb b/activesupport/lib/active_support/core_ext/object/acts_like.rb
index 3912cc5ace..403ee20e39 100644
--- a/activesupport/lib/active_support/core_ext/object/acts_like.rb
+++ b/activesupport/lib/active_support/core_ext/object/acts_like.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Object
# A duck-type assistant method. For example, Active Support extends Date
# to define an <tt>acts_like_date?</tt> method, and extends Time to define
@@ -5,6 +7,15 @@ class Object
# <tt>x.acts_like?(:date)</tt> to do duck-type-safe comparisons, since classes that
# we want to act like Time simply need to define an <tt>acts_like_time?</tt> method.
def acts_like?(duck)
- respond_to? :"acts_like_#{duck}?"
+ case duck
+ when :time
+ respond_to? :acts_like_time?
+ when :date
+ respond_to? :acts_like_date?
+ when :string
+ respond_to? :acts_like_string?
+ else
+ respond_to? :"acts_like_#{duck}?"
+ end
end
end
diff --git a/activesupport/lib/active_support/core_ext/object/blank.rb b/activesupport/lib/active_support/core_ext/object/blank.rb
index bdb50ee291..e42ad852dd 100644
--- a/activesupport/lib/active_support/core_ext/object/blank.rb
+++ b/activesupport/lib/active_support/core_ext/object/blank.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/regexp"
class Object
diff --git a/activesupport/lib/active_support/core_ext/object/conversions.rb b/activesupport/lib/active_support/core_ext/object/conversions.rb
index 918ebcdc9f..624fb8d77c 100644
--- a/activesupport/lib/active_support/core_ext/object/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/object/conversions.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/object/to_param"
require "active_support/core_ext/object/to_query"
require "active_support/core_ext/array/conversions"
diff --git a/activesupport/lib/active_support/core_ext/object/deep_dup.rb b/activesupport/lib/active_support/core_ext/object/deep_dup.rb
index 5ac649e552..c66c5eb2d9 100644
--- a/activesupport/lib/active_support/core_ext/object/deep_dup.rb
+++ b/activesupport/lib/active_support/core_ext/object/deep_dup.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/object/duplicable"
class Object
diff --git a/activesupport/lib/active_support/core_ext/object/duplicable.rb b/activesupport/lib/active_support/core_ext/object/duplicable.rb
index ea81df2bd8..9bb99087bc 100644
--- a/activesupport/lib/active_support/core_ext/object/duplicable.rb
+++ b/activesupport/lib/active_support/core_ext/object/duplicable.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
#--
# Most objects are cloneable, but not all. For example you can't dup methods:
#
@@ -106,8 +108,8 @@ require "bigdecimal"
class BigDecimal
# BigDecimals are duplicable:
#
- # BigDecimal.new("1.2").duplicable? # => true
- # BigDecimal.new("1.2").dup # => #<BigDecimal:...,'0.12E1',18(18)>
+ # BigDecimal("1.2").duplicable? # => true
+ # BigDecimal("1.2").dup # => #<BigDecimal:...,'0.12E1',18(18)>
def duplicable?
true
end
@@ -124,21 +126,31 @@ class Method
end
class Complex
- # Complexes are not duplicable:
- #
- # Complex(1).duplicable? # => false
- # Complex(1).dup # => TypeError: can't copy Complex
- def duplicable?
- false
+ begin
+ Complex(1).dup
+ rescue TypeError
+
+ # Complexes are not duplicable:
+ #
+ # Complex(1).duplicable? # => false
+ # Complex(1).dup # => TypeError: can't copy Complex
+ def duplicable?
+ false
+ end
end
end
class Rational
- # Rationals are not duplicable:
- #
- # Rational(1).duplicable? # => false
- # Rational(1).dup # => TypeError: can't copy Rational
- def duplicable?
- false
+ begin
+ Rational(1).dup
+ rescue TypeError
+
+ # Rationals are not duplicable:
+ #
+ # Rational(1).duplicable? # => false
+ # Rational(1).dup # => TypeError: can't copy Rational
+ def duplicable?
+ false
+ end
end
end
diff --git a/activesupport/lib/active_support/core_ext/object/inclusion.rb b/activesupport/lib/active_support/core_ext/object/inclusion.rb
index 98bf820d36..6064e92f20 100644
--- a/activesupport/lib/active_support/core_ext/object/inclusion.rb
+++ b/activesupport/lib/active_support/core_ext/object/inclusion.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Object
# Returns true if this object is included in the argument. Argument must be
# any object which responds to +#include?+. Usage:
diff --git a/activesupport/lib/active_support/core_ext/object/instance_variables.rb b/activesupport/lib/active_support/core_ext/object/instance_variables.rb
index 593a7a4940..12fdf840b5 100644
--- a/activesupport/lib/active_support/core_ext/object/instance_variables.rb
+++ b/activesupport/lib/active_support/core_ext/object/instance_variables.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Object
# Returns a hash with string keys that maps instance variable names without "@" to their
# corresponding values.
diff --git a/activesupport/lib/active_support/core_ext/object/json.rb b/activesupport/lib/active_support/core_ext/object/json.rb
index 1c4d181443..f7c623fe13 100644
--- a/activesupport/lib/active_support/core_ext/object/json.rb
+++ b/activesupport/lib/active_support/core_ext/object/json.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Hack to load json gem first so we can overwrite its to_json.
require "json"
require "bigdecimal"
@@ -133,6 +135,12 @@ module Enumerable
end
end
+class IO
+ def as_json(options = nil) #:nodoc:
+ to_s
+ end
+end
+
class Range
def as_json(options = nil) #:nodoc:
to_s
diff --git a/activesupport/lib/active_support/core_ext/object/to_param.rb b/activesupport/lib/active_support/core_ext/object/to_param.rb
index 5eeaf03163..6d2bdd70f3 100644
--- a/activesupport/lib/active_support/core_ext/object/to_param.rb
+++ b/activesupport/lib/active_support/core_ext/object/to_param.rb
@@ -1 +1,3 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/object/to_query"
diff --git a/activesupport/lib/active_support/core_ext/object/to_query.rb b/activesupport/lib/active_support/core_ext/object/to_query.rb
index a3a3abacbb..abb461966a 100644
--- a/activesupport/lib/active_support/core_ext/object/to_query.rb
+++ b/activesupport/lib/active_support/core_ext/object/to_query.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cgi"
class Object
diff --git a/activesupport/lib/active_support/core_ext/object/try.rb b/activesupport/lib/active_support/core_ext/object/try.rb
index b2be619b2d..c874691629 100644
--- a/activesupport/lib/active_support/core_ext/object/try.rb
+++ b/activesupport/lib/active_support/core_ext/object/try.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "delegate"
module ActiveSupport
diff --git a/activesupport/lib/active_support/core_ext/object/with_options.rb b/activesupport/lib/active_support/core_ext/object/with_options.rb
index cf39b1d312..2838fd76be 100644
--- a/activesupport/lib/active_support/core_ext/object/with_options.rb
+++ b/activesupport/lib/active_support/core_ext/object/with_options.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/option_merger"
class Object
@@ -60,7 +62,18 @@ class Object
#
# validates :content, length: { minimum: 50 }, if: -> { content.present? }
#
- # Hence the inherited default for `if` key is ignored.
+ # Hence the inherited default for +if+ key is ignored.
+ #
+ # NOTE: You cannot call class methods implicitly inside of with_options.
+ # You can access these methods using the class name instead:
+ #
+ # class Phone < ActiveRecord::Base
+ # enum phone_number_type: [home: 0, office: 1, mobile: 2]
+ #
+ # with_options presence: true do
+ # validates :phone_number_type, inclusion: { in: Phone.phone_number_types.keys }
+ # end
+ # end
#
def with_options(options, &block)
option_merger = ActiveSupport::OptionMerger.new(self, options)
diff --git a/activesupport/lib/active_support/core_ext/range.rb b/activesupport/lib/active_support/core_ext/range.rb
index 3190e3ff76..4074e91d17 100644
--- a/activesupport/lib/active_support/core_ext/range.rb
+++ b/activesupport/lib/active_support/core_ext/range.rb
@@ -1,4 +1,7 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/range/conversions"
require "active_support/core_ext/range/include_range"
+require "active_support/core_ext/range/include_time_with_zone"
require "active_support/core_ext/range/overlaps"
require "active_support/core_ext/range/each"
diff --git a/activesupport/lib/active_support/core_ext/range/conversions.rb b/activesupport/lib/active_support/core_ext/range/conversions.rb
index 69ea046cb6..8832fbcb3c 100644
--- a/activesupport/lib/active_support/core_ext/range/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/range/conversions.rb
@@ -1,6 +1,14 @@
+# frozen_string_literal: true
+
module ActiveSupport::RangeWithFormat
RANGE_FORMATS = {
- db: Proc.new { |start, stop| "BETWEEN '#{start.to_s(:db)}' AND '#{stop.to_s(:db)}'" }
+ db: -> (start, stop) do
+ case start
+ when String then "BETWEEN '#{start}' AND '#{stop}'"
+ else
+ "BETWEEN '#{start.to_s(:db)}' AND '#{stop.to_s(:db)}'"
+ end
+ end
}
# Convert range to a formatted string. See RANGE_FORMATS for predefined formats.
diff --git a/activesupport/lib/active_support/core_ext/range/each.rb b/activesupport/lib/active_support/core_ext/range/each.rb
index dc6dad5ced..2f22cd0e92 100644
--- a/activesupport/lib/active_support/core_ext/range/each.rb
+++ b/activesupport/lib/active_support/core_ext/range/each.rb
@@ -1,3 +1,7 @@
+# frozen_string_literal: true
+
+require "active_support/time_with_zone"
+
module ActiveSupport
module EachTimeWithZone #:nodoc:
def each(&block)
@@ -13,7 +17,7 @@ module ActiveSupport
private
def ensure_iteration_allowed
- raise TypeError, "can't iterate from #{first.class}" if first.is_a?(Time)
+ raise TypeError, "can't iterate from #{first.class}" if first.is_a?(TimeWithZone)
end
end
end
diff --git a/activesupport/lib/active_support/core_ext/range/include_range.rb b/activesupport/lib/active_support/core_ext/range/include_range.rb
index c69e1e3fb9..7ba1011921 100644
--- a/activesupport/lib/active_support/core_ext/range/include_range.rb
+++ b/activesupport/lib/active_support/core_ext/range/include_range.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActiveSupport
module IncludeWithRange #:nodoc:
# Extends the default Range#include? to support range comparisons.
diff --git a/activesupport/lib/active_support/core_ext/range/include_time_with_zone.rb b/activesupport/lib/active_support/core_ext/range/include_time_with_zone.rb
new file mode 100644
index 0000000000..5f80acf68e
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/range/include_time_with_zone.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+require "active_support/time_with_zone"
+
+module ActiveSupport
+ module IncludeTimeWithZone #:nodoc:
+ # Extends the default Range#include? to support ActiveSupport::TimeWithZone.
+ #
+ # (1.hour.ago..1.hour.from_now).include?(Time.current) # => true
+ #
+ def include?(value)
+ if first.is_a?(TimeWithZone)
+ cover?(value)
+ elsif last.is_a?(TimeWithZone)
+ cover?(value)
+ else
+ super
+ end
+ end
+ end
+end
+
+Range.prepend(ActiveSupport::IncludeTimeWithZone)
diff --git a/activesupport/lib/active_support/core_ext/range/overlaps.rb b/activesupport/lib/active_support/core_ext/range/overlaps.rb
index 603657c180..f753607f8b 100644
--- a/activesupport/lib/active_support/core_ext/range/overlaps.rb
+++ b/activesupport/lib/active_support/core_ext/range/overlaps.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Range
# Compare two ranges and see if they overlap each other
# (1..5).overlaps?(4..6) # => true
diff --git a/activesupport/lib/active_support/core_ext/regexp.rb b/activesupport/lib/active_support/core_ext/regexp.rb
index d77d01bf42..efbd708aee 100644
--- a/activesupport/lib/active_support/core_ext/regexp.rb
+++ b/activesupport/lib/active_support/core_ext/regexp.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Regexp #:nodoc:
def multiline?
options & MULTILINE == MULTILINE
diff --git a/activesupport/lib/active_support/core_ext/securerandom.rb b/activesupport/lib/active_support/core_ext/securerandom.rb
index a57685bea1..b4a491f5fd 100644
--- a/activesupport/lib/active_support/core_ext/securerandom.rb
+++ b/activesupport/lib/active_support/core_ext/securerandom.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "securerandom"
module SecureRandom
diff --git a/activesupport/lib/active_support/core_ext/string.rb b/activesupport/lib/active_support/core_ext/string.rb
index 4cb3200875..757d15c51a 100644
--- a/activesupport/lib/active_support/core_ext/string.rb
+++ b/activesupport/lib/active_support/core_ext/string.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/string/conversions"
require "active_support/core_ext/string/filters"
require "active_support/core_ext/string/multibyte"
diff --git a/activesupport/lib/active_support/core_ext/string/access.rb b/activesupport/lib/active_support/core_ext/string/access.rb
index 6133826f37..58591bbaaf 100644
--- a/activesupport/lib/active_support/core_ext/string/access.rb
+++ b/activesupport/lib/active_support/core_ext/string/access.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class String
# If you pass a single integer, returns a substring of one character at that
# position. The first character of the string is at position 0, the next at
diff --git a/activesupport/lib/active_support/core_ext/string/behavior.rb b/activesupport/lib/active_support/core_ext/string/behavior.rb
index 710f1f4670..35a5aa7840 100644
--- a/activesupport/lib/active_support/core_ext/string/behavior.rb
+++ b/activesupport/lib/active_support/core_ext/string/behavior.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class String
# Enables more predictable duck-typing on String-like classes. See <tt>Object#acts_like?</tt>.
def acts_like_string?
diff --git a/activesupport/lib/active_support/core_ext/string/conversions.rb b/activesupport/lib/active_support/core_ext/string/conversions.rb
index 221b4969cc..29a88b07ad 100644
--- a/activesupport/lib/active_support/core_ext/string/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/string/conversions.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "date"
require "active_support/core_ext/time/calculations"
diff --git a/activesupport/lib/active_support/core_ext/string/exclude.rb b/activesupport/lib/active_support/core_ext/string/exclude.rb
index 0ac684f6ee..8e462689f1 100644
--- a/activesupport/lib/active_support/core_ext/string/exclude.rb
+++ b/activesupport/lib/active_support/core_ext/string/exclude.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class String
# The inverse of <tt>String#include?</tt>. Returns true if the string
# does not include the other string.
diff --git a/activesupport/lib/active_support/core_ext/string/filters.rb b/activesupport/lib/active_support/core_ext/string/filters.rb
index a9ec2eb842..66e721eea3 100644
--- a/activesupport/lib/active_support/core_ext/string/filters.rb
+++ b/activesupport/lib/active_support/core_ext/string/filters.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class String
# Returns the string, first removing all whitespace on both ends of
# the string, and then changing remaining consecutive whitespace
diff --git a/activesupport/lib/active_support/core_ext/string/indent.rb b/activesupport/lib/active_support/core_ext/string/indent.rb
index d7b58301d3..af9d181487 100644
--- a/activesupport/lib/active_support/core_ext/string/indent.rb
+++ b/activesupport/lib/active_support/core_ext/string/indent.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class String
# Same as +indent+, except it indents the receiver in-place.
#
diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb
index 7a2fc5c1b5..8af301734a 100644
--- a/activesupport/lib/active_support/core_ext/string/inflections.rb
+++ b/activesupport/lib/active_support/core_ext/string/inflections.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/inflector/methods"
require "active_support/inflector/transliterate"
@@ -92,6 +94,8 @@ class String
ActiveSupport::Inflector.camelize(self, true)
when :lower
ActiveSupport::Inflector.camelize(self, false)
+ else
+ raise ArgumentError, "Invalid option, use either :upper or :lower."
end
end
alias_method :camelcase, :camelize
@@ -100,12 +104,17 @@ class String
# a nicer looking title. +titleize+ is meant for creating pretty output. It is not
# used in the Rails internals.
#
+ # The trailing '_id','Id'.. can be kept and capitalized by setting the
+ # optional parameter +keep_id_suffix+ to true.
+ # By default, this parameter is false.
+ #
# +titleize+ is also aliased as +titlecase+.
#
- # 'man from the boondocks'.titleize # => "Man From The Boondocks"
- # 'x-men: the last stand'.titleize # => "X Men: The Last Stand"
- def titleize
- ActiveSupport::Inflector.titleize(self)
+ # 'man from the boondocks'.titleize # => "Man From The Boondocks"
+ # 'x-men: the last stand'.titleize # => "X Men: The Last Stand"
+ # 'string_ending_with_id'.titleize(keep_id_suffix: true) # => "String Ending With Id"
+ def titleize(keep_id_suffix: false)
+ ActiveSupport::Inflector.titleize(self, keep_id_suffix: keep_id_suffix)
end
alias_method :titlecase, :titleize
@@ -165,7 +174,7 @@ class String
# <%= link_to(@person.name, person_path) %>
# # => <a href="/person/1-donald-e-knuth">Donald E. Knuth</a>
#
- # To preserve the case of the characters in a string, use the `preserve_case` argument.
+ # To preserve the case of the characters in a string, use the +preserve_case+ argument.
#
# class Person
# def to_param
@@ -202,7 +211,7 @@ class String
ActiveSupport::Inflector.classify(self)
end
- # Capitalizes the first word, turns underscores into spaces, and strips a
+ # Capitalizes the first word, turns underscores into spaces, and (by default)strips a
# trailing '_id' if present.
# Like +titleize+, this is meant for creating pretty output.
#
@@ -210,12 +219,17 @@ class String
# optional parameter +capitalize+ to false.
# By default, this parameter is true.
#
- # 'employee_salary'.humanize # => "Employee salary"
- # 'author_id'.humanize # => "Author"
- # 'author_id'.humanize(capitalize: false) # => "author"
- # '_id'.humanize # => "Id"
- def humanize(options = {})
- ActiveSupport::Inflector.humanize(self, options)
+ # The trailing '_id' can be kept and capitalized by setting the
+ # optional parameter +keep_id_suffix+ to true.
+ # By default, this parameter is false.
+ #
+ # 'employee_salary'.humanize # => "Employee salary"
+ # 'author_id'.humanize # => "Author"
+ # 'author_id'.humanize(capitalize: false) # => "author"
+ # '_id'.humanize # => "Id"
+ # 'author_id'.humanize(keep_id_suffix: true) # => "Author Id"
+ def humanize(capitalize: true, keep_id_suffix: false)
+ ActiveSupport::Inflector.humanize(self, capitalize: capitalize, keep_id_suffix: keep_id_suffix)
end
# Converts just the first character to uppercase.
diff --git a/activesupport/lib/active_support/core_ext/string/inquiry.rb b/activesupport/lib/active_support/core_ext/string/inquiry.rb
index c95d83beae..a796d5fb4f 100644
--- a/activesupport/lib/active_support/core_ext/string/inquiry.rb
+++ b/activesupport/lib/active_support/core_ext/string/inquiry.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/string_inquirer"
class String
diff --git a/activesupport/lib/active_support/core_ext/string/multibyte.rb b/activesupport/lib/active_support/core_ext/string/multibyte.rb
index 1c73182259..07c0d16398 100644
--- a/activesupport/lib/active_support/core_ext/string/multibyte.rb
+++ b/activesupport/lib/active_support/core_ext/string/multibyte.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/multibyte"
class String
@@ -14,6 +16,8 @@ class String
# >> "lj".mb_chars.upcase.to_s
# => "LJ"
#
+ # NOTE: An above example is useful for pre Ruby 2.4. Ruby 2.4 supports Unicode case mappings.
+ #
# == Method chaining
#
# All the methods on the Chars proxy which normally return a string will return a Chars object. This allows
diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb
index 94ce3f6a61..f3bdc2977e 100644
--- a/activesupport/lib/active_support/core_ext/string/output_safety.rb
+++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb
@@ -1,5 +1,8 @@
+# frozen_string_literal: true
+
require "erb"
require "active_support/core_ext/kernel/singleton_class"
+require "active_support/core_ext/module/redefine_method"
require "active_support/multibyte/unicode"
class ERB
@@ -12,22 +15,18 @@ class ERB
# A utility method for escaping HTML tag characters.
# This method is also aliased as <tt>h</tt>.
#
- # In your ERB templates, use this method to escape any unsafe content. For example:
- # <%= h @person.name %>
- #
# puts html_escape('is a > 0 & a < 10?')
# # => is a &gt; 0 &amp; a &lt; 10?
def html_escape(s)
unwrapped_html_escape(s).html_safe
end
- # Aliasing twice issues a warning "discarding old...". Remove first to avoid it.
- remove_method(:h)
+ silence_redefinition_of_method :h
alias h html_escape
module_function :h
- singleton_class.send(:remove_method, :html_escape)
+ singleton_class.silence_redefinition_of_method :html_escape
module_function :html_escape
# HTML escapes strings but doesn't wrap them with an ActiveSupport::SafeBuffer.
@@ -251,7 +250,7 @@ class String
# Marks a string as trusted safe. It will be inserted into HTML with no
# additional escaping performed. It is your responsibility to ensure that the
# string contains no malicious content. This method is equivalent to the
- # `raw` helper in views. It is recommended that you use `sanitize` instead of
+ # +raw+ helper in views. It is recommended that you use +sanitize+ instead of
# this method. It should never be called on user input.
def html_safe
ActiveSupport::SafeBuffer.new(self)
diff --git a/activesupport/lib/active_support/core_ext/string/starts_ends_with.rb b/activesupport/lib/active_support/core_ext/string/starts_ends_with.rb
index 641acf62d0..919eb7a573 100644
--- a/activesupport/lib/active_support/core_ext/string/starts_ends_with.rb
+++ b/activesupport/lib/active_support/core_ext/string/starts_ends_with.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class String
alias_method :starts_with?, :start_with?
alias_method :ends_with?, :end_with?
diff --git a/activesupport/lib/active_support/core_ext/string/strip.rb b/activesupport/lib/active_support/core_ext/string/strip.rb
index bb62e6c0ba..cc26274e4a 100644
--- a/activesupport/lib/active_support/core_ext/string/strip.rb
+++ b/activesupport/lib/active_support/core_ext/string/strip.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class String
# Strips indentation in heredocs.
#
diff --git a/activesupport/lib/active_support/core_ext/string/zones.rb b/activesupport/lib/active_support/core_ext/string/zones.rb
index de5a28e4f7..55dc231464 100644
--- a/activesupport/lib/active_support/core_ext/string/zones.rb
+++ b/activesupport/lib/active_support/core_ext/string/zones.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/string/conversions"
require "active_support/core_ext/time/zones"
diff --git a/activesupport/lib/active_support/core_ext/time.rb b/activesupport/lib/active_support/core_ext/time.rb
index b1ae4a45d9..c809def05f 100644
--- a/activesupport/lib/active_support/core_ext/time.rb
+++ b/activesupport/lib/active_support/core_ext/time.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/time/acts_like"
require "active_support/core_ext/time/calculations"
require "active_support/core_ext/time/compatibility"
diff --git a/activesupport/lib/active_support/core_ext/time/acts_like.rb b/activesupport/lib/active_support/core_ext/time/acts_like.rb
index cf4b2539c5..8572b49639 100644
--- a/activesupport/lib/active_support/core_ext/time/acts_like.rb
+++ b/activesupport/lib/active_support/core_ext/time/acts_like.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/object/acts_like"
class Time
diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb
index cbdcb86d6d..120768dec5 100644
--- a/activesupport/lib/active_support/core_ext/time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/time/calculations.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/duration"
require "active_support/core_ext/time/conversions"
require "active_support/time_with_zone"
@@ -53,6 +55,29 @@ class Time
end
alias_method :at_without_coercion, :at
alias_method :at, :at_with_coercion
+
+ # Creates a +Time+ instance from an RFC 3339 string.
+ #
+ # Time.rfc3339('1999-12-31T14:00:00-10:00') # => 2000-01-01 00:00:00 -1000
+ #
+ # If the time or offset components are missing then an +ArgumentError+ will be raised.
+ #
+ # Time.rfc3339('1999-12-31') # => ArgumentError: invalid date
+ def rfc3339(str)
+ parts = Date._rfc3339(str)
+
+ raise ArgumentError, "invalid date" if parts.empty?
+
+ Time.new(
+ parts.fetch(:year),
+ parts.fetch(:mon),
+ parts.fetch(:mday),
+ parts.fetch(:hour),
+ parts.fetch(:min),
+ parts.fetch(:sec) + parts.fetch(:sec_fraction, 0),
+ parts.fetch(:offset)
+ )
+ end
end
# Returns the number of seconds since 00:00:00.
@@ -84,21 +109,22 @@ class Time
# to the +options+ parameter. The time options (<tt>:hour</tt>, <tt>:min</tt>,
# <tt>:sec</tt>, <tt>:usec</tt>, <tt>:nsec</tt>) reset cascadingly, so if only
# the hour is passed, then minute, sec, usec and nsec is set to 0. If the hour
- # and minute is passed, then sec, usec and nsec is set to 0. The +options+
- # parameter takes a hash with any of these keys: <tt>:year</tt>, <tt>:month</tt>,
- # <tt>:day</tt>, <tt>:hour</tt>, <tt>:min</tt>, <tt>:sec</tt>, <tt>:usec</tt>
- # <tt>:nsec</tt>. Pass either <tt>:usec</tt> or <tt>:nsec</tt>, not both.
+ # and minute is passed, then sec, usec and nsec is set to 0. The +options+ parameter
+ # takes a hash with any of these keys: <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>,
+ # <tt>:hour</tt>, <tt>:min</tt>, <tt>:sec</tt>, <tt>:usec</tt>, <tt>:nsec</tt>,
+ # <tt>:offset</tt>. Pass either <tt>:usec</tt> or <tt>:nsec</tt>, not both.
#
# Time.new(2012, 8, 29, 22, 35, 0).change(day: 1) # => Time.new(2012, 8, 1, 22, 35, 0)
# Time.new(2012, 8, 29, 22, 35, 0).change(year: 1981, day: 1) # => Time.new(1981, 8, 1, 22, 35, 0)
# Time.new(2012, 8, 29, 22, 35, 0).change(year: 1981, hour: 0) # => Time.new(1981, 8, 29, 0, 0, 0)
def change(options)
- new_year = options.fetch(:year, year)
- new_month = options.fetch(:month, month)
- new_day = options.fetch(:day, day)
- new_hour = options.fetch(:hour, hour)
- new_min = options.fetch(:min, options[:hour] ? 0 : min)
- new_sec = options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec)
+ new_year = options.fetch(:year, year)
+ new_month = options.fetch(:month, month)
+ new_day = options.fetch(:day, day)
+ new_hour = options.fetch(:hour, hour)
+ new_min = options.fetch(:min, options[:hour] ? 0 : min)
+ new_sec = options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec)
+ new_offset = options.fetch(:offset, nil)
if new_nsec = options[:nsec]
raise ArgumentError, "Can't change both :nsec and :usec at the same time: #{options.inspect}" if options[:usec]
@@ -107,13 +133,18 @@ class Time
new_usec = options.fetch(:usec, (options[:hour] || options[:min] || options[:sec]) ? 0 : Rational(nsec, 1000))
end
- if utc?
- ::Time.utc(new_year, new_month, new_day, new_hour, new_min, new_sec, new_usec)
+ raise ArgumentError, "argument out of range" if new_usec >= 1000000
+
+ new_sec += Rational(new_usec, 1000000)
+
+ if new_offset
+ ::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec, new_offset)
+ elsif utc?
+ ::Time.utc(new_year, new_month, new_day, new_hour, new_min, new_sec)
elsif zone
- ::Time.local(new_year, new_month, new_day, new_hour, new_min, new_sec, new_usec)
+ ::Time.local(new_year, new_month, new_day, new_hour, new_min, new_sec)
else
- raise ArgumentError, "argument out of range" if new_usec >= 1000000
- ::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec + (new_usec.to_r / 1000000), utc_offset)
+ ::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec, utc_offset)
end
end
diff --git a/activesupport/lib/active_support/core_ext/time/compatibility.rb b/activesupport/lib/active_support/core_ext/time/compatibility.rb
index ca4b9574d5..495e4f307b 100644
--- a/activesupport/lib/active_support/core_ext/time/compatibility.rb
+++ b/activesupport/lib/active_support/core_ext/time/compatibility.rb
@@ -1,5 +1,16 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/date_and_time/compatibility"
+require "active_support/core_ext/module/redefine_method"
class Time
- prepend DateAndTime::Compatibility
+ include DateAndTime::Compatibility
+
+ silence_redefinition_of_method :to_time
+
+ # Either return +self+ or the time in the local system timezone depending
+ # on the setting of +ActiveSupport.to_time_preserves_timezone+.
+ def to_time
+ preserve_timezone ? self : getlocal
+ end
end
diff --git a/activesupport/lib/active_support/core_ext/time/conversions.rb b/activesupport/lib/active_support/core_ext/time/conversions.rb
index f2bbe55aa6..345cb2832c 100644
--- a/activesupport/lib/active_support/core_ext/time/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/time/conversions.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/inflector/methods"
require "active_support/values/time_zone"
@@ -64,4 +66,7 @@ class Time
def formatted_offset(colon = true, alternate_utc_string = nil)
utc? && alternate_utc_string || ActiveSupport::TimeZone.seconds_to_utc_offset(utc_offset, colon)
end
+
+ # Aliased to +xmlschema+ for compatibility with +DateTime+
+ alias_method :rfc3339, :xmlschema
end
diff --git a/activesupport/lib/active_support/core_ext/time/zones.rb b/activesupport/lib/active_support/core_ext/time/zones.rb
index 87b5ad903a..a5588fd488 100644
--- a/activesupport/lib/active_support/core_ext/time/zones.rb
+++ b/activesupport/lib/active_support/core_ext/time/zones.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/time_with_zone"
require "active_support/core_ext/time/acts_like"
require "active_support/core_ext/date_and_time/zones"
@@ -53,10 +55,10 @@ class Time
# end
# end
#
- # NOTE: This won't affect any <tt>ActiveSupport::TimeWithZone</tt>
- # objects that have already been created, e.g. any model timestamp
- # attributes that have been read before the block will remain in
- # the application's default timezone.
+ # NOTE: This won't affect any <tt>ActiveSupport::TimeWithZone</tt>
+ # objects that have already been created, e.g. any model timestamp
+ # attributes that have been read before the block will remain in
+ # the application's default timezone.
def use_zone(time_zone)
new_zone = find_zone!(time_zone)
begin
diff --git a/activesupport/lib/active_support/core_ext/uri.rb b/activesupport/lib/active_support/core_ext/uri.rb
index 342a5fcd52..c93c0b5c2d 100644
--- a/activesupport/lib/active_support/core_ext/uri.rb
+++ b/activesupport/lib/active_support/core_ext/uri.rb
@@ -1,10 +1,13 @@
+# frozen_string_literal: true
+
require "uri"
str = "\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E" # Ni-ho-nn-go in UTF-8, means Japanese.
parser = URI::Parser.new
unless str == parser.unescape(parser.escape(str))
+ require "active_support/core_ext/module/redefine_method"
URI::Parser.class_eval do
- remove_method :unescape
+ silence_redefinition_of_method :unescape
def unescape(str, escaped = /%[a-fA-F\d]{2}/)
# TODO: Are we actually sure that ASCII == UTF-8?
# YK: My initial experiments say yes, but let's be sure please
diff --git a/activesupport/lib/active_support/current_attributes.rb b/activesupport/lib/active_support/current_attributes.rb
new file mode 100644
index 0000000000..4e6d8e4585
--- /dev/null
+++ b/activesupport/lib/active_support/current_attributes.rb
@@ -0,0 +1,195 @@
+# frozen_string_literal: true
+
+module ActiveSupport
+ # Abstract super class that provides a thread-isolated attributes singleton, which resets automatically
+ # before and after each request. This allows you to keep all the per-request attributes easily
+ # available to the whole system.
+ #
+ # The following full app-like example demonstrates how to use a Current class to
+ # facilitate easy access to the global, per-request attributes without passing them deeply
+ # around everywhere:
+ #
+ # # app/models/current.rb
+ # class Current < ActiveSupport::CurrentAttributes
+ # attribute :account, :user
+ # attribute :request_id, :user_agent, :ip_address
+ #
+ # resets { Time.zone = nil }
+ #
+ # def user=(user)
+ # super
+ # self.account = user.account
+ # Time.zone = user.time_zone
+ # end
+ # end
+ #
+ # # app/controllers/concerns/authentication.rb
+ # module Authentication
+ # extend ActiveSupport::Concern
+ #
+ # included do
+ # before_action :authenticate
+ # end
+ #
+ # private
+ # def authenticate
+ # if authenticated_user = User.find_by(id: cookies.encrypted[:user_id])
+ # Current.user = authenticated_user
+ # else
+ # redirect_to new_session_url
+ # end
+ # end
+ # end
+ #
+ # # app/controllers/concerns/set_current_request_details.rb
+ # module SetCurrentRequestDetails
+ # extend ActiveSupport::Concern
+ #
+ # included do
+ # before_action do
+ # Current.request_id = request.uuid
+ # Current.user_agent = request.user_agent
+ # Current.ip_address = request.ip
+ # end
+ # end
+ # end
+ #
+ # class ApplicationController < ActionController::Base
+ # include Authentication
+ # include SetCurrentRequestDetails
+ # end
+ #
+ # class MessagesController < ApplicationController
+ # def create
+ # Current.account.messages.create(message_params)
+ # end
+ # end
+ #
+ # class Message < ApplicationRecord
+ # belongs_to :creator, default: -> { Current.user }
+ # after_create { |message| Event.create(record: message) }
+ # end
+ #
+ # class Event < ApplicationRecord
+ # before_create do
+ # self.request_id = Current.request_id
+ # self.user_agent = Current.user_agent
+ # self.ip_address = Current.ip_address
+ # end
+ # end
+ #
+ # A word of caution: It's easy to overdo a global singleton like Current and tangle your model as a result.
+ # Current should only be used for a few, top-level globals, like account, user, and request details.
+ # The attributes stuck in Current should be used by more or less all actions on all requests. If you start
+ # sticking controller-specific attributes in there, you're going to create a mess.
+ class CurrentAttributes
+ include ActiveSupport::Callbacks
+ define_callbacks :reset
+
+ class << self
+ # Returns singleton instance for this class in this thread. If none exists, one is created.
+ def instance
+ current_instances[name] ||= new
+ end
+
+ # Declares one or more attributes that will be given both class and instance accessor methods.
+ def attribute(*names)
+ generated_attribute_methods.module_eval do
+ names.each do |name|
+ define_method(name) do
+ attributes[name.to_sym]
+ end
+
+ define_method("#{name}=") do |attribute|
+ attributes[name.to_sym] = attribute
+ end
+ end
+ end
+
+ names.each do |name|
+ define_singleton_method(name) do
+ instance.public_send(name)
+ end
+
+ define_singleton_method("#{name}=") do |attribute|
+ instance.public_send("#{name}=", attribute)
+ end
+ end
+ end
+
+ # Calls this block after #reset is called on the instance. Used for resetting external collaborators, like Time.zone.
+ def resets(&block)
+ set_callback :reset, :after, &block
+ end
+
+ delegate :set, :reset, to: :instance
+
+ def reset_all # :nodoc:
+ current_instances.each_value(&:reset)
+ end
+
+ def clear_all # :nodoc:
+ reset_all
+ current_instances.clear
+ end
+
+ private
+ def generated_attribute_methods
+ @generated_attribute_methods ||= Module.new.tap { |mod| include mod }
+ end
+
+ def current_instances
+ Thread.current[:current_attributes_instances] ||= {}
+ end
+
+ def method_missing(name, *args, &block)
+ # Caches the method definition as a singleton method of the receiver.
+ #
+ # By letting #delegate handle it, we avoid an enclosure that'll capture args.
+ singleton_class.delegate name, to: :instance
+
+ send(name, *args, &block)
+ end
+ end
+
+ attr_accessor :attributes
+
+ def initialize
+ @attributes = {}
+ end
+
+ # Expose one or more attributes within a block. Old values are returned after the block concludes.
+ # Example demonstrating the common use of needing to set Current attributes outside the request-cycle:
+ #
+ # class Chat::PublicationJob < ApplicationJob
+ # def perform(attributes, room_number, creator)
+ # Current.set(person: creator) do
+ # Chat::Publisher.publish(attributes: attributes, room_number: room_number)
+ # end
+ # end
+ # end
+ def set(set_attributes)
+ old_attributes = compute_attributes(set_attributes.keys)
+ assign_attributes(set_attributes)
+ yield
+ ensure
+ assign_attributes(old_attributes)
+ end
+
+ # Reset all attributes. Should be called before and after actions, when used as a per-request singleton.
+ def reset
+ run_callbacks :reset do
+ self.attributes = {}
+ end
+ end
+
+ private
+ def assign_attributes(new_attributes)
+ new_attributes.each { |key, value| public_send("#{key}=", value) }
+ end
+
+ def compute_attributes(keys)
+ keys.collect { |key| [ key, public_send(key) ] }.to_h
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index e125b657f2..82c10b3079 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "set"
require "thread"
require "concurrent/map"
@@ -18,8 +20,7 @@ module ActiveSupport #:nodoc:
module Dependencies #:nodoc:
extend self
- mattr_accessor :interlock
- self.interlock = Interlock.new
+ mattr_accessor :interlock, default: Interlock.new
# :doc:
@@ -46,46 +47,37 @@ module ActiveSupport #:nodoc:
# :nodoc:
# Should we turn on Ruby warnings on the first load of dependent files?
- mattr_accessor :warnings_on_first_load
- self.warnings_on_first_load = false
+ mattr_accessor :warnings_on_first_load, default: false
# All files ever loaded.
- mattr_accessor :history
- self.history = Set.new
+ mattr_accessor :history, default: Set.new
# All files currently loaded.
- mattr_accessor :loaded
- self.loaded = Set.new
+ mattr_accessor :loaded, default: Set.new
# Stack of files being loaded.
- mattr_accessor :loading
- self.loading = []
+ mattr_accessor :loading, default: []
# Should we load files or require them?
- mattr_accessor :mechanism
- self.mechanism = ENV["NO_RELOAD"] ? :require : :load
+ mattr_accessor :mechanism, default: ENV["NO_RELOAD"] ? :require : :load
# The set of directories from which we may automatically load files. Files
# under these directories will be reloaded on each request in development mode,
# unless the directory also appears in autoload_once_paths.
- mattr_accessor :autoload_paths
- self.autoload_paths = []
+ mattr_accessor :autoload_paths, default: []
# The set of directories from which automatically loaded constants are loaded
# only once. All directories in this set must also be present in +autoload_paths+.
- mattr_accessor :autoload_once_paths
- self.autoload_once_paths = []
+ mattr_accessor :autoload_once_paths, default: []
# An array of qualified constant names that have been loaded. Adding a name
# to this array will cause it to be unloaded the next time Dependencies are
# cleared.
- mattr_accessor :autoloaded_constants
- self.autoloaded_constants = []
+ mattr_accessor :autoloaded_constants, default: []
# An array of constant names that need to be unloaded on every request. Used
# to allow arbitrary constants to be marked for unloading.
- mattr_accessor :explicitly_unloadable_constants
- self.explicitly_unloadable_constants = []
+ mattr_accessor :explicitly_unloadable_constants, default: []
# The WatchStack keeps a stack of the modules being watched as files are
# loaded. If a file in the process of being loaded (parent.rb) triggers the
@@ -93,7 +85,7 @@ module ActiveSupport #:nodoc:
# handles the new constants.
#
# If child.rb is being autoloaded, its constants will be added to
- # autoloaded_constants. If it was being `require`d, they will be discarded.
+ # autoloaded_constants. If it was being required, they will be discarded.
#
# This is handled by walking back up the watch stack and adding the constants
# found by child.rb to the list of original constants in parent.rb.
@@ -175,8 +167,7 @@ module ActiveSupport #:nodoc:
end
# An internal stack used to record which constants are loaded by any block.
- mattr_accessor :constant_watch_stack
- self.constant_watch_stack = WatchStack.new
+ mattr_accessor :constant_watch_stack, default: WatchStack.new
# Module includes this module.
module ModuleConstMissing #:nodoc:
@@ -624,7 +615,7 @@ module ActiveSupport #:nodoc:
return false if desc.is_a?(Module) && desc.anonymous?
name = to_constant_name desc
return false unless qualified_const_defined?(name)
- return autoloaded_constants.include?(name)
+ autoloaded_constants.include?(name)
end
# Will the provided constant descriptor be unloaded?
diff --git a/activesupport/lib/active_support/dependencies/autoload.rb b/activesupport/lib/active_support/dependencies/autoload.rb
index 13036d521d..1cee85d98f 100644
--- a/activesupport/lib/active_support/dependencies/autoload.rb
+++ b/activesupport/lib/active_support/dependencies/autoload.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/inflector/methods"
module ActiveSupport
diff --git a/activesupport/lib/active_support/dependencies/interlock.rb b/activesupport/lib/active_support/dependencies/interlock.rb
index e4e18439c5..948be75638 100644
--- a/activesupport/lib/active_support/dependencies/interlock.rb
+++ b/activesupport/lib/active_support/dependencies/interlock.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/concurrency/share_lock"
module ActiveSupport #:nodoc:
diff --git a/activesupport/lib/active_support/deprecation.rb b/activesupport/lib/active_support/deprecation.rb
index 191e582de8..a1ad2ca465 100644
--- a/activesupport/lib/active_support/deprecation.rb
+++ b/activesupport/lib/active_support/deprecation.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "singleton"
module ActiveSupport
@@ -15,6 +17,7 @@ module ActiveSupport
require "active_support/deprecation/instance_delegator"
require "active_support/deprecation/behaviors"
require "active_support/deprecation/reporting"
+ require "active_support/deprecation/constant_accessor"
require "active_support/deprecation/method_wrappers"
require "active_support/deprecation/proxy_wrappers"
require "active_support/core_ext/module/deprecation"
@@ -29,10 +32,10 @@ module ActiveSupport
attr_accessor :deprecation_horizon
# It accepts two parameters on initialization. The first is a version of library
- # and the second is a library name
+ # and the second is a library name.
#
# ActiveSupport::Deprecation.new('2.0', 'MyLibrary')
- def initialize(deprecation_horizon = "5.2", gem_name = "Rails")
+ def initialize(deprecation_horizon = "6.0", gem_name = "Rails")
self.gem_name = gem_name
self.deprecation_horizon = deprecation_horizon
# By default, warnings are not silenced and debugging is off.
diff --git a/activesupport/lib/active_support/deprecation/behaviors.rb b/activesupport/lib/active_support/deprecation/behaviors.rb
index 1d1354c23e..581db5f449 100644
--- a/activesupport/lib/active_support/deprecation/behaviors.rb
+++ b/activesupport/lib/active_support/deprecation/behaviors.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/notifications"
module ActiveSupport
@@ -9,18 +11,18 @@ module ActiveSupport
class Deprecation
# Default warning behaviors per Rails.env.
DEFAULT_BEHAVIORS = {
- raise: ->(message, callstack) {
+ raise: ->(message, callstack, deprecation_horizon, gem_name) {
e = DeprecationException.new(message)
e.set_backtrace(callstack.map(&:to_s))
raise e
},
- stderr: ->(message, callstack) {
+ stderr: ->(message, callstack, deprecation_horizon, gem_name) {
$stderr.puts(message)
$stderr.puts callstack.join("\n ") if debug
},
- log: ->(message, callstack) {
+ log: ->(message, callstack, deprecation_horizon, gem_name) {
logger =
if defined?(Rails.logger) && Rails.logger
Rails.logger
@@ -32,12 +34,16 @@ module ActiveSupport
logger.debug callstack.join("\n ") if debug
},
- notify: ->(message, callstack) {
- ActiveSupport::Notifications.instrument("deprecation.rails",
- message: message, callstack: callstack)
+ notify: ->(message, callstack, deprecation_horizon, gem_name) {
+ notification_name = "deprecation.#{gem_name.underscore.tr('/', '_')}"
+ ActiveSupport::Notifications.instrument(notification_name,
+ message: message,
+ callstack: callstack,
+ gem_name: gem_name,
+ deprecation_horizon: deprecation_horizon)
},
- silence: ->(message, callstack) {},
+ silence: ->(message, callstack, deprecation_horizon, gem_name) {},
}
# Behavior module allows to determine how to display deprecation messages.
@@ -83,8 +89,17 @@ module ActiveSupport
# # custom stuff
# }
def behavior=(behavior)
- @behavior = Array(behavior).map { |b| DEFAULT_BEHAVIORS[b] || b }
+ @behavior = Array(behavior).map { |b| DEFAULT_BEHAVIORS[b] || arity_coerce(b) }
end
+
+ private
+ def arity_coerce(behavior)
+ if behavior.arity == 4 || behavior.arity == -1
+ behavior
+ else
+ -> message, callstack, _, _ { behavior.call(message, callstack) }
+ end
+ end
end
end
end
diff --git a/activesupport/lib/active_support/deprecation/constant_accessor.rb b/activesupport/lib/active_support/deprecation/constant_accessor.rb
new file mode 100644
index 0000000000..1ed0015812
--- /dev/null
+++ b/activesupport/lib/active_support/deprecation/constant_accessor.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+module ActiveSupport
+ class Deprecation
+ # DeprecatedConstantAccessor transforms a constant into a deprecated one by
+ # hooking +const_missing+.
+ #
+ # It takes the names of an old (deprecated) constant and of a new constant
+ # (both in string form) and optionally a deprecator. The deprecator defaults
+ # to +ActiveSupport::Deprecator+ if none is specified.
+ #
+ # The deprecated constant now returns the same object as the new one rather
+ # than a proxy object, so it can be used transparently in +rescue+ blocks
+ # etc.
+ #
+ # PLANETS = %w(mercury venus earth mars jupiter saturn uranus neptune pluto)
+ #
+ # # (In a later update, the original implementation of `PLANETS` has been removed.)
+ #
+ # PLANETS_POST_2006 = %w(mercury venus earth mars jupiter saturn uranus neptune)
+ # include ActiveSupport::Deprecation::DeprecatedConstantAccessor
+ # deprecate_constant 'PLANETS', 'PLANETS_POST_2006'
+ #
+ # PLANETS.map { |planet| planet.capitalize }
+ # # => DEPRECATION WARNING: PLANETS is deprecated! Use PLANETS_POST_2006 instead.
+ # (Backtrace information…)
+ # ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"]
+ module DeprecatedConstantAccessor
+ def self.included(base)
+ require "active_support/inflector/methods"
+
+ extension = Module.new do
+ def const_missing(missing_const_name)
+ if class_variable_defined?(:@@_deprecated_constants)
+ if (replacement = class_variable_get(:@@_deprecated_constants)[missing_const_name.to_s])
+ replacement[:deprecator].warn(replacement[:message] || "#{name}::#{missing_const_name} is deprecated! Use #{replacement[:new]} instead.", caller_locations)
+ return ActiveSupport::Inflector.constantize(replacement[:new].to_s)
+ end
+ end
+ super
+ end
+
+ def deprecate_constant(const_name, new_constant, message: nil, deprecator: ActiveSupport::Deprecation.instance)
+ class_variable_set(:@@_deprecated_constants, {}) unless class_variable_defined?(:@@_deprecated_constants)
+ class_variable_get(:@@_deprecated_constants)[const_name.to_s] = { new: new_constant, message: message, deprecator: deprecator }
+ end
+ end
+ base.singleton_class.prepend extension
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/deprecation/instance_delegator.rb b/activesupport/lib/active_support/deprecation/instance_delegator.rb
index 6d390f3b37..8beda373a2 100644
--- a/activesupport/lib/active_support/deprecation/instance_delegator.rb
+++ b/activesupport/lib/active_support/deprecation/instance_delegator.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/kernel/singleton_class"
require "active_support/core_ext/module/delegation"
diff --git a/activesupport/lib/active_support/deprecation/method_wrappers.rb b/activesupport/lib/active_support/deprecation/method_wrappers.rb
index 7655fa4f99..5be893d281 100644
--- a/activesupport/lib/active_support/deprecation/method_wrappers.rb
+++ b/activesupport/lib/active_support/deprecation/method_wrappers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/module/aliasing"
require "active_support/core_ext/array/extract_options"
@@ -6,9 +8,7 @@ module ActiveSupport
module MethodWrapper
# Declare that a method has been deprecated.
#
- # module Fred
- # extend self
- #
+ # class Fred
# def aaa; end
# def bbb; end
# def ccc; end
@@ -18,17 +18,17 @@ module ActiveSupport
#
# Using the default deprecator:
# ActiveSupport::Deprecation.deprecate_methods(Fred, :aaa, bbb: :zzz, ccc: 'use Bar#ccc instead')
- # # => [:aaa, :bbb, :ccc]
+ # # => Fred
#
- # Fred.aaa
+ # Fred.new.aaa
# # DEPRECATION WARNING: aaa is deprecated and will be removed from Rails 5.1. (called from irb_binding at (irb):10)
# # => nil
#
- # Fred.bbb
+ # Fred.new.bbb
# # DEPRECATION WARNING: bbb is deprecated and will be removed from Rails 5.1 (use zzz instead). (called from irb_binding at (irb):11)
# # => nil
#
- # Fred.ccc
+ # Fred.new.ccc
# # DEPRECATION WARNING: ccc is deprecated and will be removed from Rails 5.1 (use Bar#ccc instead). (called from irb_binding at (irb):12)
# # => nil
#
@@ -37,7 +37,7 @@ module ActiveSupport
# ActiveSupport::Deprecation.deprecate_methods(Fred, ddd: :zzz, deprecator: custom_deprecator)
# # => [:ddd]
#
- # Fred.ddd
+ # Fred.new.ddd
# DEPRECATION WARNING: ddd is deprecated and will be removed from MyGem next-release (use zzz instead). (called from irb_binding at (irb):15)
# # => nil
#
@@ -46,7 +46,7 @@ module ActiveSupport
# custom_deprecator.deprecate_methods(Fred, eee: :zzz)
# # => [:eee]
#
- # Fred.eee
+ # Fred.new.eee
# DEPRECATION WARNING: eee is deprecated and will be removed from MyGem next-release (use zzz instead). (called from irb_binding at (irb):18)
# # => nil
def deprecate_methods(target_module, *method_names)
@@ -60,6 +60,13 @@ module ActiveSupport
deprecator.deprecation_warning(method_name, options[method_name])
super(*args, &block)
end
+
+ case
+ when target_module.protected_method_defined?(method_name)
+ protected method_name
+ when target_module.private_method_defined?(method_name)
+ private method_name
+ end
end
end
diff --git a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
index ce39e9a232..896c0d2d8e 100644
--- a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
+++ b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
@@ -1,4 +1,5 @@
-require "active_support/inflector/methods"
+# frozen_string_literal: true
+
require "active_support/core_ext/regexp"
module ActiveSupport
@@ -112,7 +113,7 @@ module ActiveSupport
#
# PLANETS = %w(mercury venus earth mars jupiter saturn uranus neptune pluto)
#
- # (In a later update, the original implementation of `PLANETS` has been removed.)
+ # # (In a later update, the original implementation of `PLANETS` has been removed.)
#
# PLANETS_POST_2006 = %w(mercury venus earth mars jupiter saturn uranus neptune)
# PLANETS = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('PLANETS', 'PLANETS_POST_2006')
@@ -123,6 +124,8 @@ module ActiveSupport
# ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"]
class DeprecatedConstantProxy < DeprecationProxy
def initialize(old_const, new_const, deprecator = ActiveSupport::Deprecation.instance, message: "#{old_const} is deprecated! Use #{new_const} instead.")
+ require "active_support/inflector/methods"
+
@old_const = old_const
@new_const = new_const
@deprecator = deprecator
diff --git a/activesupport/lib/active_support/deprecation/reporting.rb b/activesupport/lib/active_support/deprecation/reporting.rb
index b8d200ba94..242e21b782 100644
--- a/activesupport/lib/active_support/deprecation/reporting.rb
+++ b/activesupport/lib/active_support/deprecation/reporting.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "rbconfig"
module ActiveSupport
@@ -18,7 +20,7 @@ module ActiveSupport
callstack ||= caller_locations(2)
deprecation_message(callstack, message).tap do |m|
- behavior.each { |b| b.call(m, callstack) }
+ behavior.each { |b| b.call(m, callstack, deprecation_horizon, gem_name) }
end
end
@@ -48,11 +50,11 @@ module ActiveSupport
private
# Outputs a deprecation warning message
#
- # ActiveSupport::Deprecation.deprecated_method_warning(:method_name)
+ # deprecated_method_warning(:method_name)
# # => "method_name is deprecated and will be removed from Rails #{deprecation_horizon}"
- # ActiveSupport::Deprecation.deprecated_method_warning(:method_name, :another_method)
+ # deprecated_method_warning(:method_name, :another_method)
# # => "method_name is deprecated and will be removed from Rails #{deprecation_horizon} (use another_method instead)"
- # ActiveSupport::Deprecation.deprecated_method_warning(:method_name, "Optional message")
+ # deprecated_method_warning(:method_name, "Optional message")
# # => "method_name is deprecated and will be removed from Rails #{deprecation_horizon} (Optional message)"
def deprecated_method_warning(method_name, message = nil)
warning = "#{method_name} is deprecated and will be removed from #{gem_name} #{deprecation_horizon}"
@@ -102,7 +104,7 @@ module ActiveSupport
end
end
- RAILS_GEM_ROOT = File.expand_path("../../../../..", __FILE__) + "/"
+ RAILS_GEM_ROOT = File.expand_path("../../../..", __dir__)
def ignored_callstack(path)
path.start_with?(RAILS_GEM_ROOT) || path.start_with?(RbConfig::CONFIG["rubylibdir"])
diff --git a/activesupport/lib/active_support/descendants_tracker.rb b/activesupport/lib/active_support/descendants_tracker.rb
index 27861e01d0..a4cee788b6 100644
--- a/activesupport/lib/active_support/descendants_tracker.rb
+++ b/activesupport/lib/active_support/descendants_tracker.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActiveSupport
# This module provides an internal implementation to track descendants
# which is faster than iterating through ObjectSpace.
diff --git a/activesupport/lib/active_support/digest.rb b/activesupport/lib/active_support/digest.rb
new file mode 100644
index 0000000000..fba10fbdcf
--- /dev/null
+++ b/activesupport/lib/active_support/digest.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module ActiveSupport
+ class Digest #:nodoc:
+ class <<self
+ def hash_digest_class
+ @hash_digest_class ||= ::Digest::MD5
+ end
+
+ def hash_digest_class=(klass)
+ raise ArgumentError, "#{klass} is expected to implement hexdigest class method" unless klass.respond_to?(:hexdigest)
+ @hash_digest_class = klass
+ end
+
+ def hexdigest(arg)
+ hash_digest_class.hexdigest(arg)[0...32]
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb
index 003f6203ef..fe1058762b 100644
--- a/activesupport/lib/active_support/duration.rb
+++ b/activesupport/lib/active_support/duration.rb
@@ -1,5 +1,10 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/array/conversions"
+require "active_support/core_ext/module/delegation"
require "active_support/core_ext/object/acts_like"
+require "active_support/core_ext/string/filters"
+require "active_support/deprecation"
module ActiveSupport
# Provides accurate date and time measurements using Date#advance and
@@ -7,6 +12,100 @@ module ActiveSupport
#
# 1.month.ago # equivalent to Time.now.advance(months: -1)
class Duration
+ class Scalar < Numeric #:nodoc:
+ attr_reader :value
+ delegate :to_i, :to_f, :to_s, to: :value
+
+ def initialize(value)
+ @value = value
+ end
+
+ def coerce(other)
+ [Scalar.new(other), self]
+ end
+
+ def -@
+ Scalar.new(-value)
+ end
+
+ def <=>(other)
+ if Scalar === other || Duration === other
+ value <=> other.value
+ elsif Numeric === other
+ value <=> other
+ else
+ nil
+ end
+ end
+
+ def +(other)
+ if Duration === other
+ seconds = value + other.parts[:seconds]
+ new_parts = other.parts.merge(seconds: seconds)
+ new_value = value + other.value
+
+ Duration.new(new_value, new_parts)
+ else
+ calculate(:+, other)
+ end
+ end
+
+ def -(other)
+ if Duration === other
+ seconds = value - other.parts[:seconds]
+ new_parts = other.parts.map { |part, other_value| [part, -other_value] }.to_h
+ new_parts = new_parts.merge(seconds: seconds)
+ new_value = value - other.value
+
+ Duration.new(new_value, new_parts)
+ else
+ calculate(:-, other)
+ end
+ end
+
+ def *(other)
+ if Duration === other
+ new_parts = other.parts.map { |part, other_value| [part, value * other_value] }.to_h
+ new_value = value * other.value
+
+ Duration.new(new_value, new_parts)
+ else
+ calculate(:*, other)
+ end
+ end
+
+ def /(other)
+ if Duration === other
+ value / other.value
+ else
+ calculate(:/, other)
+ end
+ end
+
+ def %(other)
+ if Duration === other
+ Duration.build(value % other.value)
+ else
+ calculate(:%, other)
+ end
+ end
+
+ private
+ def calculate(op, other)
+ if Scalar === other
+ Scalar.new(value.public_send(op, other.value))
+ elsif Numeric === other
+ Scalar.new(value.public_send(op, other))
+ else
+ raise_type_error(other)
+ end
+ end
+
+ def raise_type_error(other)
+ raise TypeError, "no implicit conversion of #{other.class} into #{self.class}"
+ end
+ end
+
SECONDS_PER_MINUTE = 60
SECONDS_PER_HOUR = 3600
SECONDS_PER_DAY = 86400
@@ -24,6 +123,8 @@ module ActiveSupport
years: SECONDS_PER_YEAR
}.freeze
+ PARTS = [:years, :months, :weeks, :days, :hours, :minutes, :seconds].freeze
+
attr_accessor :value, :parts
autoload :ISO8601Parser, "active_support/duration/iso8601_parser"
@@ -32,7 +133,7 @@ module ActiveSupport
class << self
# Creates a new Duration from string formatted according to ISO 8601 Duration.
#
- # See {ISO 8601}[http://en.wikipedia.org/wiki/ISO_8601#Durations] for more information.
+ # See {ISO 8601}[https://en.wikipedia.org/wiki/ISO_8601#Durations] for more information.
# This method allows negative parts to be present in pattern.
# If invalid string is provided, it will raise +ActiveSupport::Duration::ISO8601Parser::ParsingError+.
def parse(iso8601duration)
@@ -74,6 +175,29 @@ module ActiveSupport
new(value * SECONDS_PER_YEAR, [[:years, value]])
end
+ # Creates a new Duration from a seconds value that is converted
+ # to the individual parts:
+ #
+ # ActiveSupport::Duration.build(31556952).parts # => {:years=>1}
+ # ActiveSupport::Duration.build(2716146).parts # => {:months=>1, :days=>1}
+ #
+ def build(value)
+ parts = {}
+ remainder = value.to_f
+
+ PARTS.each do |part|
+ unless part == :seconds
+ part_in_seconds = PARTS_IN_SECONDS[part]
+ parts[part] = remainder.div(part_in_seconds)
+ remainder = (remainder % part_in_seconds).round(9)
+ end
+ end
+
+ parts[:seconds] = remainder
+
+ new(value, parts)
+ end
+
private
def calculate_total_seconds(parts)
@@ -86,6 +210,25 @@ module ActiveSupport
def initialize(value, parts) #:nodoc:
@value, @parts = value, parts.to_h
@parts.default = 0
+ @parts.reject! { |k, v| v.zero? }
+ end
+
+ def coerce(other) #:nodoc:
+ if Scalar === other
+ [other, self]
+ else
+ [Scalar.new(other), self]
+ end
+ end
+
+ # Compares one Duration with another or a Numeric to this Duration.
+ # Numeric values are treated as seconds.
+ def <=>(other)
+ if Duration === other
+ value <=> other.value
+ elsif Numeric === other
+ value <=> other
+ end
end
# Adds another Duration or a Numeric to this Duration. Numeric values
@@ -109,6 +252,42 @@ module ActiveSupport
self + (-other)
end
+ # Multiplies this Duration by a Numeric and returns a new Duration.
+ def *(other)
+ if Scalar === other || Duration === other
+ Duration.new(value * other.value, parts.map { |type, number| [type, number * other.value] })
+ elsif Numeric === other
+ Duration.new(value * other, parts.map { |type, number| [type, number * other] })
+ else
+ raise_type_error(other)
+ end
+ end
+
+ # Divides this Duration by a Numeric and returns a new Duration.
+ def /(other)
+ if Scalar === other
+ Duration.new(value / other.value, parts.map { |type, number| [type, number / other.value] })
+ elsif Duration === other
+ value / other.value
+ elsif Numeric === other
+ Duration.new(value / other, parts.map { |type, number| [type, number / other] })
+ else
+ raise_type_error(other)
+ end
+ end
+
+ # Returns the modulo of this Duration by another Duration or Numeric.
+ # Numeric values are treated as seconds.
+ def %(other)
+ if Duration === other || Scalar === other
+ Duration.build(value % other.value)
+ elsif Numeric === other
+ Duration.build(value % other)
+ else
+ raise_type_error(other)
+ end
+ end
+
def -@ #:nodoc:
Duration.new(-value, parts.map { |type, number| [type, -number] })
end
@@ -180,6 +359,7 @@ module ActiveSupport
sum(1, time)
end
alias :from_now :since
+ alias :after :since
# Calculates a new Time or Date that is as far in the past
# as this Duration represents.
@@ -187,11 +367,14 @@ module ActiveSupport
sum(-1, time)
end
alias :until :ago
+ alias :before :ago
def inspect #:nodoc:
+ return "0 seconds" if parts.empty?
+
parts.
reduce(::Hash.new(0)) { |h, (l, r)| h[l] += r; h }.
- sort_by { |unit, _ | [:years, :months, :weeks, :days, :hours, :minutes, :seconds].index(unit) }.
+ sort_by { |unit, _ | PARTS.index(unit) }.
map { |unit, val| "#{val} #{val == 1 ? unit.to_s.chop : unit.to_s}" }.
to_sentence(locale: ::I18n.default_locale)
end
@@ -200,18 +383,12 @@ module ActiveSupport
to_i
end
- def respond_to_missing?(method, include_private = false) #:nodoc:
- @value.respond_to?(method, include_private)
- end
-
# Build ISO 8601 Duration string for this duration.
# The +precision+ parameter can be used to limit seconds' precision of duration.
def iso8601(precision: nil)
ISO8601Serializer.new(self, precision: precision).serialize
end
- delegate :<=>, to: :value
-
private
def sum(sign, time = ::Time.current)
@@ -232,8 +409,16 @@ module ActiveSupport
end
end
+ def respond_to_missing?(method, _)
+ value.respond_to?(method)
+ end
+
def method_missing(method, *args, &block)
- value.send(method, *args, &block)
+ value.public_send(method, *args, &block)
+ end
+
+ def raise_type_error(other)
+ raise TypeError, "no implicit conversion of #{other.class} into #{self.class}"
end
end
end
diff --git a/activesupport/lib/active_support/duration/iso8601_parser.rb b/activesupport/lib/active_support/duration/iso8601_parser.rb
index e96cb8e883..1847eeaa86 100644
--- a/activesupport/lib/active_support/duration/iso8601_parser.rb
+++ b/activesupport/lib/active_support/duration/iso8601_parser.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "strscan"
require "active_support/core_ext/regexp"
@@ -5,7 +7,7 @@ module ActiveSupport
class Duration
# Parses a string formatted according to ISO 8601 Duration into the hash.
#
- # See {ISO 8601}[http://en.wikipedia.org/wiki/ISO_8601#Durations] for more information.
+ # See {ISO 8601}[https://en.wikipedia.org/wiki/ISO_8601#Durations] for more information.
#
# This parser allows negative parts to be present in pattern.
class ISO8601Parser # :nodoc:
@@ -116,7 +118,7 @@ module ActiveSupport
raise_parsing_error "(only last part can be fractional)"
end
- return true
+ true
end
end
end
diff --git a/activesupport/lib/active_support/duration/iso8601_serializer.rb b/activesupport/lib/active_support/duration/iso8601_serializer.rb
index 51d53e2f8d..bb177ae5b7 100644
--- a/activesupport/lib/active_support/duration/iso8601_serializer.rb
+++ b/activesupport/lib/active_support/duration/iso8601_serializer.rb
@@ -1,10 +1,12 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/object/blank"
require "active_support/core_ext/hash/transform_values"
module ActiveSupport
class Duration
# Serializes duration to string according to ISO 8601 Duration format.
- class ISO8601Serializer
+ class ISO8601Serializer # :nodoc:
def initialize(duration, precision: nil)
@duration = duration
@precision = precision
@@ -15,12 +17,12 @@ module ActiveSupport
parts, sign = normalize
return "PT0S".freeze if parts.empty?
- output = "P"
+ output = "P".dup
output << "#{parts[:years]}Y" if parts.key?(:years)
output << "#{parts[:months]}M" if parts.key?(:months)
output << "#{parts[:weeks]}W" if parts.key?(:weeks)
output << "#{parts[:days]}D" if parts.key?(:days)
- time = ""
+ time = "".dup
time << "#{parts[:hours]}H" if parts.key?(:hours)
time << "#{parts[:minutes]}M" if parts.key?(:minutes)
if parts.key?(:seconds)
diff --git a/activesupport/lib/active_support/encrypted_configuration.rb b/activesupport/lib/active_support/encrypted_configuration.rb
new file mode 100644
index 0000000000..dab953d5d5
--- /dev/null
+++ b/activesupport/lib/active_support/encrypted_configuration.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+require "yaml"
+require "active_support/encrypted_file"
+require "active_support/ordered_options"
+require "active_support/core_ext/object/inclusion"
+require "active_support/core_ext/module/delegation"
+
+module ActiveSupport
+ class EncryptedConfiguration < EncryptedFile
+ delegate :[], :fetch, to: :config
+ delegate_missing_to :options
+
+ def initialize(config_path:, key_path:, env_key:, raise_if_missing_key:)
+ super content_path: config_path, key_path: key_path,
+ env_key: env_key, raise_if_missing_key: raise_if_missing_key
+ end
+
+ # Allow a config to be started without a file present
+ def read
+ super
+ rescue ActiveSupport::EncryptedFile::MissingContentError
+ ""
+ end
+
+ def write(contents)
+ deserialize(contents)
+
+ super
+ end
+
+ def config
+ @config ||= deserialize(read).deep_symbolize_keys
+ end
+
+ private
+ def options
+ @options ||= ActiveSupport::InheritableOptions.new(config)
+ end
+
+ def serialize(config)
+ config.present? ? YAML.dump(config) : ""
+ end
+
+ def deserialize(config)
+ config.present? ? YAML.load(config, content_path) : {}
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/encrypted_file.rb b/activesupport/lib/active_support/encrypted_file.rb
new file mode 100644
index 0000000000..671b6b6a69
--- /dev/null
+++ b/activesupport/lib/active_support/encrypted_file.rb
@@ -0,0 +1,99 @@
+# frozen_string_literal: true
+
+require "pathname"
+require "active_support/message_encryptor"
+
+module ActiveSupport
+ class EncryptedFile
+ class MissingContentError < RuntimeError
+ def initialize(content_path)
+ super "Missing encrypted content file in #{content_path}."
+ end
+ end
+
+ class MissingKeyError < RuntimeError
+ def initialize(key_path:, env_key:)
+ super \
+ "Missing encryption key to decrypt file with. " +
+ "Ask your team for your master key and write it to #{key_path} or put it in the ENV['#{env_key}']."
+ end
+ end
+
+ CIPHER = "aes-128-gcm"
+
+ def self.generate_key
+ SecureRandom.hex(ActiveSupport::MessageEncryptor.key_len(CIPHER))
+ end
+
+
+ attr_reader :content_path, :key_path, :env_key, :raise_if_missing_key
+
+ def initialize(content_path:, key_path:, env_key:, raise_if_missing_key:)
+ @content_path, @key_path = Pathname.new(content_path), Pathname.new(key_path)
+ @env_key, @raise_if_missing_key = env_key, raise_if_missing_key
+ end
+
+ def key
+ read_env_key || read_key_file || handle_missing_key
+ end
+
+ def read
+ if !key.nil? && content_path.exist?
+ decrypt content_path.binread
+ else
+ raise MissingContentError, content_path
+ end
+ end
+
+ def write(contents)
+ IO.binwrite "#{content_path}.tmp", encrypt(contents)
+ FileUtils.mv "#{content_path}.tmp", content_path
+ end
+
+ def change(&block)
+ writing read, &block
+ end
+
+
+ private
+ def writing(contents)
+ tmp_file = "#{content_path.basename}.#{Process.pid}"
+ tmp_path = Pathname.new File.join(Dir.tmpdir, tmp_file)
+ tmp_path.binwrite contents
+
+ yield tmp_path
+
+ updated_contents = tmp_path.binread
+
+ write(updated_contents) if updated_contents != contents
+ ensure
+ FileUtils.rm(tmp_path) if tmp_path.exist?
+ end
+
+
+ def encrypt(contents)
+ encryptor.encrypt_and_sign contents
+ end
+
+ def decrypt(contents)
+ encryptor.decrypt_and_verify contents
+ end
+
+ def encryptor
+ @encryptor ||= ActiveSupport::MessageEncryptor.new([ key ].pack("H*"), cipher: CIPHER)
+ end
+
+
+ def read_env_key
+ ENV[env_key]
+ end
+
+ def read_key_file
+ key_path.binread.strip if key_path.exist?
+ end
+
+ def handle_missing_key
+ raise MissingKeyError, key_path: key_path, env_key: env_key if raise_if_missing_key
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/evented_file_update_checker.rb b/activesupport/lib/active_support/evented_file_update_checker.rb
index 8e0dc71dca..97e982eb05 100644
--- a/activesupport/lib/active_support/evented_file_update_checker.rb
+++ b/activesupport/lib/active_support/evented_file_update_checker.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "set"
require "pathname"
require "concurrent/atomic/atomic_boolean"
@@ -125,6 +127,11 @@ module ActiveSupport
dtw.compact!
dtw.uniq!
+ normalized_gem_paths = Gem.path.map { |path| File.join path, "" }
+ dtw = dtw.reject do |path|
+ normalized_gem_paths.any? { |gem_path| path.to_s.start_with?(gem_path) }
+ end
+
@ph.filter_out_descendants(dtw)
end
diff --git a/activesupport/lib/active_support/execution_wrapper.rb b/activesupport/lib/active_support/execution_wrapper.rb
index ca88e7876b..f48c586cad 100644
--- a/activesupport/lib/active_support/execution_wrapper.rb
+++ b/activesupport/lib/active_support/execution_wrapper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/callbacks"
module ActiveSupport
diff --git a/activesupport/lib/active_support/executor.rb b/activesupport/lib/active_support/executor.rb
index a6400cae0a..ce391b07ec 100644
--- a/activesupport/lib/active_support/executor.rb
+++ b/activesupport/lib/active_support/executor.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/execution_wrapper"
module ActiveSupport
diff --git a/activesupport/lib/active_support/file_update_checker.rb b/activesupport/lib/active_support/file_update_checker.rb
index 2b5e3c1350..1a0bb10815 100644
--- a/activesupport/lib/active_support/file_update_checker.rb
+++ b/activesupport/lib/active_support/file_update_checker.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/time/calculations"
module ActiveSupport
diff --git a/activesupport/lib/active_support/gem_version.rb b/activesupport/lib/active_support/gem_version.rb
index 74f2d8dd4b..1e09adbb52 100644
--- a/activesupport/lib/active_support/gem_version.rb
+++ b/activesupport/lib/active_support/gem_version.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActiveSupport
# Returns the version of the currently loaded Active Support as a <tt>Gem::Version</tt>.
def self.gem_version
@@ -6,9 +8,9 @@ module ActiveSupport
module VERSION
MAJOR = 5
- MINOR = 1
+ MINOR = 2
TINY = 0
- PRE = "alpha"
+ PRE = "beta2"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/activesupport/lib/active_support/gzip.rb b/activesupport/lib/active_support/gzip.rb
index 84eef6a623..7ffa6d90a2 100644
--- a/activesupport/lib/active_support/gzip.rb
+++ b/activesupport/lib/active_support/gzip.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "zlib"
require "stringio"
@@ -21,7 +23,7 @@ module ActiveSupport
# Decompresses a gzipped string.
def self.decompress(source)
- Zlib::GzipReader.new(StringIO.new(source)).read
+ Zlib::GzipReader.wrap(StringIO.new(source), &:read)
end
# Compresses a string using gzip.
diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb
index 79e7feaf47..2e2ed8a25d 100644
--- a/activesupport/lib/active_support/hash_with_indifferent_access.rb
+++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/hash/keys"
require "active_support/core_ext/hash/reverse_merge"
@@ -74,16 +76,6 @@ module ActiveSupport
end
end
- def default(*args)
- arg_key = args.first
-
- if include?(key = convert_key(arg_key))
- self[key]
- else
- super
- end
- end
-
def self.[](*args)
new.merge!(Hash[*args])
end
@@ -185,6 +177,36 @@ module ActiveSupport
super(convert_key(key), *extras)
end
+ if Hash.new.respond_to?(:dig)
+ # Same as <tt>Hash#dig</tt> where the key passed as argument can be
+ # either a string or a symbol:
+ #
+ # counters = ActiveSupport::HashWithIndifferentAccess.new
+ # counters[:foo] = { bar: 1 }
+ #
+ # counters.dig('foo', 'bar') # => 1
+ # counters.dig(:foo, :bar) # => 1
+ # counters.dig(:zoo) # => nil
+ def dig(*args)
+ args[0] = convert_key(args[0]) if args.size > 0
+ super(*args)
+ end
+ end
+
+ # Same as <tt>Hash#default</tt> where the key passed as argument can be
+ # either a string or a symbol:
+ #
+ # hash = ActiveSupport::HashWithIndifferentAccess.new(1)
+ # hash.default # => 1
+ #
+ # hash = ActiveSupport::HashWithIndifferentAccess.new { |hash, key| key }
+ # hash.default # => nil
+ # hash.default('foo') # => 'foo'
+ # hash.default(:foo) # => 'foo'
+ def default(*args)
+ super(*args.map { |arg| convert_key(arg) })
+ end
+
# Returns an array of the values at the specified indices:
#
# hash = ActiveSupport::HashWithIndifferentAccess.new
@@ -195,13 +217,26 @@ module ActiveSupport
indices.collect { |key| self[convert_key(key)] }
end
+ # Returns an array of the values at the specified indices, but also
+ # raises an exception when one of the keys can't be found.
+ #
+ # hash = ActiveSupport::HashWithIndifferentAccess.new
+ # hash[:a] = 'x'
+ # hash[:b] = 'y'
+ # hash.fetch_values('a', 'b') # => ["x", "y"]
+ # hash.fetch_values('a', 'c') { |key| 'z' } # => ["x", "z"]
+ # hash.fetch_values('a', 'c') # => KeyError: key not found: "c"
+ def fetch_values(*indices, &block)
+ indices.collect { |key| fetch(key, &block) }
+ end if Hash.method_defined?(:fetch_values)
+
# Returns a shallow copy of the hash.
#
# hash = ActiveSupport::HashWithIndifferentAccess.new({ a: { b: 'b' } })
# dup = hash.dup
# dup[:a][:c] = 'c'
#
- # hash[:a][:c] # => nil
+ # hash[:a][:c] # => "c"
# dup[:a][:c] # => "c"
def dup
self.class.new(self).tap do |new_hash|
@@ -225,11 +260,13 @@ module ActiveSupport
def reverse_merge(other_hash)
super(self.class.new(other_hash))
end
+ alias_method :with_defaults, :reverse_merge
# Same semantics as +reverse_merge+ but modifies the receiver in-place.
def reverse_merge!(other_hash)
- replace(reverse_merge(other_hash))
+ super(self.class.new(other_hash))
end
+ alias_method :with_defaults!, :reverse_merge!
# Replaces the contents of this hash with other_hash.
#
@@ -269,8 +306,23 @@ module ActiveSupport
dup.tap { |hash| hash.transform_values!(*args, &block) }
end
+ def transform_keys(*args, &block)
+ return to_enum(:transform_keys) unless block_given?
+ dup.tap { |hash| hash.transform_keys!(*args, &block) }
+ end
+
+ def slice(*keys)
+ keys.map! { |key| convert_key(key) }
+ self.class.new(super)
+ end
+
+ def slice!(*keys)
+ keys.map! { |key| convert_key(key) }
+ super
+ end
+
def compact
- dup.compact!
+ dup.tap(&:compact!)
end
# Convert to a regular hash with string keys.
@@ -316,4 +368,6 @@ module ActiveSupport
end
end
+# :stopdoc:
+
HashWithIndifferentAccess = ActiveSupport::HashWithIndifferentAccess
diff --git a/activesupport/lib/active_support/i18n.rb b/activesupport/lib/active_support/i18n.rb
index f0408f429c..d60b3eff30 100644
--- a/activesupport/lib/active_support/i18n.rb
+++ b/activesupport/lib/active_support/i18n.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/hash/deep_merge"
require "active_support/core_ext/hash/except"
require "active_support/core_ext/hash/slice"
@@ -10,4 +12,4 @@ end
require "active_support/lazy_load_hooks"
ActiveSupport.run_load_hooks(:i18n)
-I18n.load_path << "#{File.dirname(__FILE__)}/locale/en.yml"
+I18n.load_path << File.expand_path("locale/en.yml", __dir__)
diff --git a/activesupport/lib/active_support/i18n_railtie.rb b/activesupport/lib/active_support/i18n_railtie.rb
index b94368df14..ce8bfbfd8c 100644
--- a/activesupport/lib/active_support/i18n_railtie.rb
+++ b/activesupport/lib/active_support/i18n_railtie.rb
@@ -1,7 +1,11 @@
+# frozen_string_literal: true
+
require "active_support"
require "active_support/file_update_checker"
require "active_support/core_ext/array/wrap"
+# :enddoc:
+
module I18n
class Railtie < Rails::Railtie
config.i18n = ActiveSupport::OrderedOptions.new
@@ -40,7 +44,7 @@ module I18n
case setting
when :railties_load_path
reloadable_paths = value
- app.config.i18n.load_path.unshift(*value.map(&:existent).flatten)
+ app.config.i18n.load_path.unshift(*value.flat_map(&:existent))
when :load_path
I18n.load_path += value
else
@@ -56,7 +60,7 @@ module I18n
directories = watched_dirs_with_extensions(reloadable_paths)
reloader = app.config.file_watcher.new(I18n.load_path.dup, directories) do
I18n.load_path.keep_if { |p| File.exist?(p) }
- I18n.load_path |= reloadable_paths.map(&:existent).flatten
+ I18n.load_path |= reloadable_paths.flat_map(&:existent)
I18n.reload!
end
@@ -64,10 +68,6 @@ module I18n
app.reloaders << reloader
app.reloader.to_run do
reloader.execute_if_updated { require_unload_lock! }
- # TODO: remove the following line as soon as the return value of
- # callbacks is ignored, that is, returning `false` does not
- # display a deprecation warning or halts the callback chain.
- true
end
reloader.execute
diff --git a/activesupport/lib/active_support/inflections.rb b/activesupport/lib/active_support/inflections.rb
index afa7d1f325..baf1cb3038 100644
--- a/activesupport/lib/active_support/inflections.rb
+++ b/activesupport/lib/active_support/inflections.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/inflector/inflections"
#--
diff --git a/activesupport/lib/active_support/inflector.rb b/activesupport/lib/active_support/inflector.rb
index 48631b16a8..d77f04c9c5 100644
--- a/activesupport/lib/active_support/inflector.rb
+++ b/activesupport/lib/active_support/inflector.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# in case active_support/inflector is required without the rest of active_support
require "active_support/inflector/inflections"
require "active_support/inflector/transliterate"
diff --git a/activesupport/lib/active_support/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb
index c47a2e34e1..0450a4be4c 100644
--- a/activesupport/lib/active_support/inflector/inflections.rb
+++ b/activesupport/lib/active_support/inflector/inflections.rb
@@ -1,7 +1,10 @@
+# frozen_string_literal: true
+
require "concurrent/map"
require "active_support/core_ext/array/prepend_and_append"
require "active_support/core_ext/regexp"
require "active_support/i18n"
+require "active_support/deprecation"
module ActiveSupport
module Inflector
@@ -65,16 +68,21 @@ module ActiveSupport
end
attr_reader :plurals, :singulars, :uncountables, :humans, :acronyms, :acronym_regex
+ deprecate :acronym_regex
+
+ attr_reader :acronyms_camelize_regex, :acronyms_underscore_regex # :nodoc:
def initialize
- @plurals, @singulars, @uncountables, @humans, @acronyms, @acronym_regex = [], [], Uncountables.new, [], {}, /(?=a)b/
+ @plurals, @singulars, @uncountables, @humans, @acronyms = [], [], Uncountables.new, [], {}
+ define_acronym_regex_patterns
end
# Private, for the test suite.
def initialize_dup(orig) # :nodoc:
- %w(plurals singulars uncountables humans acronyms acronym_regex).each do |scope|
+ %w(plurals singulars uncountables humans acronyms).each do |scope|
instance_variable_set("@#{scope}", orig.send(scope).dup)
end
+ define_acronym_regex_patterns
end
# Specifies a new acronym. An acronym must be specified as it will appear
@@ -128,7 +136,7 @@ module ActiveSupport
# camelize 'mcdonald' # => 'McDonald'
def acronym(word)
@acronyms[word.downcase] = word
- @acronym_regex = /#{@acronyms.values.join("|")}/
+ define_acronym_regex_patterns
end
# Specifies a new pluralization rule and its replacement. The rule can
@@ -223,6 +231,14 @@ module ActiveSupport
instance_variable_set "@#{scope}", []
end
end
+
+ private
+
+ def define_acronym_regex_patterns
+ @acronym_regex = @acronyms.empty? ? /(?=a)b/ : /#{@acronyms.values.join("|")}/
+ @acronyms_camelize_regex = /^(?:#{@acronym_regex}(?=\b|[A-Z_])|\w)/
+ @acronyms_underscore_regex = /(?:(?<=([A-Za-z\d]))|\b)(#{@acronym_regex})(?=\b|[^a-z])/
+ end
end
# Yields a singleton instance of Inflector::Inflections so you can specify
diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
index 8ccb735c6d..60eeaa77cb 100644
--- a/activesupport/lib/active_support/inflector/methods.rb
+++ b/activesupport/lib/active_support/inflector/methods.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/inflections"
require "active_support/core_ext/regexp"
@@ -28,7 +30,7 @@ module ActiveSupport
# pluralize('CamelOctopus') # => "CamelOctopi"
# pluralize('ley', :es) # => "leyes"
def pluralize(word, locale = :en)
- apply_inflections(word, inflections(locale).plurals)
+ apply_inflections(word, inflections(locale).plurals, locale)
end
# The reverse of #pluralize, returns the singular form of a word in a
@@ -45,7 +47,7 @@ module ActiveSupport
# singularize('CamelOctopi') # => "CamelOctopus"
# singularize('leyes', :es) # => "ley"
def singularize(word, locale = :en)
- apply_inflections(word, inflections(locale).singulars)
+ apply_inflections(word, inflections(locale).singulars, locale)
end
# Converts strings to UpperCamelCase.
@@ -69,7 +71,7 @@ module ActiveSupport
if uppercase_first_letter
string = string.sub(/^[a-z\d]*/) { |match| inflections.acronyms[match] || match.capitalize }
else
- string = string.sub(/^(?:#{inflections.acronym_regex}(?=\b|[A-Z_])|\w)/) { |match| match.downcase }
+ string = string.sub(inflections.acronyms_camelize_regex) { |match| match.downcase }
end
string.gsub!(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{inflections.acronyms[$2] || $2.capitalize}" }
string.gsub!("/".freeze, "::".freeze)
@@ -90,7 +92,7 @@ module ActiveSupport
def underscore(camel_cased_word)
return camel_cased_word unless /[A-Z-]|::/.match?(camel_cased_word)
word = camel_cased_word.to_s.gsub("::".freeze, "/".freeze)
- word.gsub!(/(?:(?<=([A-Za-z\d]))|\b)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1 && '_'.freeze }#{$2.downcase}" }
+ word.gsub!(inflections.acronyms_underscore_regex) { "#{$1 && '_'.freeze }#{$2.downcase}" }
word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2'.freeze)
word.gsub!(/([a-z\d])([A-Z])/, '\1_\2'.freeze)
word.tr!("-".freeze, "_".freeze)
@@ -108,33 +110,38 @@ module ActiveSupport
# * Replaces underscores with spaces, if any.
# * Downcases all words except acronyms.
# * Capitalizes the first word.
- #
# The capitalization of the first word can be turned off by setting the
# +:capitalize+ option to false (default is true).
#
- # humanize('employee_salary') # => "Employee salary"
- # humanize('author_id') # => "Author"
- # humanize('author_id', capitalize: false) # => "author"
- # humanize('_id') # => "Id"
+ # The trailing '_id' can be kept and capitalized by setting the
+ # optional parameter +keep_id_suffix+ to true (default is false).
+ #
+ # humanize('employee_salary') # => "Employee salary"
+ # humanize('author_id') # => "Author"
+ # humanize('author_id', capitalize: false) # => "author"
+ # humanize('_id') # => "Id"
+ # humanize('author_id', keep_id_suffix: true) # => "Author Id"
#
# If "SSL" was defined to be an acronym:
#
# humanize('ssl_error') # => "SSL error"
#
- def humanize(lower_case_and_underscored_word, options = {})
+ def humanize(lower_case_and_underscored_word, capitalize: true, keep_id_suffix: false)
result = lower_case_and_underscored_word.to_s.dup
inflections.humans.each { |(rule, replacement)| break if result.sub!(rule, replacement) }
result.sub!(/\A_+/, "".freeze)
- result.sub!(/_id\z/, "".freeze)
+ unless keep_id_suffix
+ result.sub!(/_id\z/, "".freeze)
+ end
result.tr!("_".freeze, " ".freeze)
result.gsub!(/([a-z\d]*)/i) do |match|
- "#{inflections.acronyms[match] || match.downcase}"
+ "#{inflections.acronyms[match.downcase] || match.downcase}"
end
- if options.fetch(:capitalize, true)
+ if capitalize
result.sub!(/\A\w/) { |match| match.upcase }
end
@@ -154,14 +161,21 @@ module ActiveSupport
# create a nicer looking title. +titleize+ is meant for creating pretty
# output. It is not used in the Rails internals.
#
+ # The trailing '_id','Id'.. can be kept and capitalized by setting the
+ # optional parameter +keep_id_suffix+ to true.
+ # By default, this parameter is false.
+ #
# +titleize+ is also aliased as +titlecase+.
#
- # titleize('man from the boondocks') # => "Man From The Boondocks"
- # titleize('x-men: the last stand') # => "X Men: The Last Stand"
- # titleize('TheManWithoutAPast') # => "The Man Without A Past"
- # titleize('raiders_of_the_lost_ark') # => "Raiders Of The Lost Ark"
- def titleize(word)
- humanize(underscore(word)).gsub(/\b(?<!['’`])[a-z]/) { |match| match.capitalize }
+ # titleize('man from the boondocks') # => "Man From The Boondocks"
+ # titleize('x-men: the last stand') # => "X Men: The Last Stand"
+ # titleize('TheManWithoutAPast') # => "The Man Without A Past"
+ # titleize('raiders_of_the_lost_ark') # => "Raiders Of The Lost Ark"
+ # titleize('string_ending_with_id', keep_id_suffix: true) # => "String Ending With Id"
+ def titleize(word, keep_id_suffix: false)
+ humanize(underscore(word), keep_id_suffix: keep_id_suffix).gsub(/\b(?<!\w['’`])[a-z]/) do |match|
+ match.capitalize
+ end
end
# Creates the name of a table like Rails does for models to table names.
@@ -375,12 +389,15 @@ module ActiveSupport
# Applies inflection rules for +singularize+ and +pluralize+.
#
- # apply_inflections('post', inflections.plurals) # => "posts"
- # apply_inflections('posts', inflections.singulars) # => "post"
- def apply_inflections(word, rules)
+ # If passed an optional +locale+ parameter, the uncountables will be
+ # found for that locale.
+ #
+ # apply_inflections('post', inflections.plurals, :en) # => "posts"
+ # apply_inflections('posts', inflections.singulars, :en) # => "post"
+ def apply_inflections(word, rules, locale = :en)
result = word.to_s.dup
- if word.empty? || inflections.uncountables.uncountable?(result)
+ if word.empty? || inflections(locale).uncountables.uncountable?(result)
result
else
rules.each { |(rule, replacement)| break if result.sub!(rule, replacement) }
diff --git a/activesupport/lib/active_support/inflector/transliterate.rb b/activesupport/lib/active_support/inflector/transliterate.rb
index de6dd2720b..6f2ca4999c 100644
--- a/activesupport/lib/active_support/inflector/transliterate.rb
+++ b/activesupport/lib/active_support/inflector/transliterate.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/string/multibyte"
require "active_support/i18n"
@@ -59,26 +61,33 @@ module ActiveSupport
def transliterate(string, replacement = "?".freeze)
raise ArgumentError, "Can only transliterate strings. Received #{string.class.name}" unless string.is_a?(String)
- I18n.transliterate(ActiveSupport::Multibyte::Unicode.normalize(
- ActiveSupport::Multibyte::Unicode.tidy_bytes(string), :c),
- replacement: replacement)
+ I18n.transliterate(
+ ActiveSupport::Multibyte::Unicode.normalize(
+ ActiveSupport::Multibyte::Unicode.tidy_bytes(string), :c),
+ replacement: replacement)
end
# Replaces special characters in a string so that it may be used as part of
# a 'pretty' URL.
#
# parameterize("Donald E. Knuth") # => "donald-e-knuth"
- # parameterize("^trés|Jolie-- ") # => "tres-jolie"
+ # parameterize("^très|Jolie-- ") # => "tres-jolie"
#
- # To use a custom separator, override the `separator` argument.
+ # To use a custom separator, override the +separator+ argument.
#
# parameterize("Donald E. Knuth", separator: '_') # => "donald_e_knuth"
- # parameterize("^trés|Jolie-- ", separator: '_') # => "tres_jolie"
+ # parameterize("^très|Jolie__ ", separator: '_') # => "tres_jolie"
#
- # To preserve the case of the characters in a string, use the `preserve_case` argument.
+ # To preserve the case of the characters in a string, use the +preserve_case+ argument.
#
# parameterize("Donald E. Knuth", preserve_case: true) # => "Donald-E-Knuth"
- # parameterize("^trés|Jolie-- ", preserve_case: true) # => "tres-Jolie"
+ # parameterize("^très|Jolie-- ", preserve_case: true) # => "tres-Jolie"
+ #
+ # It preserves dashes and underscores unless they are used as separators:
+ #
+ # parameterize("^très|Jolie__ ") # => "tres-jolie__"
+ # parameterize("^très|Jolie-- ", separator: "_") # => "tres_jolie--"
+ # parameterize("^très_Jolie-- ", separator: ".") # => "tres_jolie--"
#
def parameterize(string, separator: "-", preserve_case: false)
# Replace accented chars with their ASCII equivalents.
diff --git a/activesupport/lib/active_support/json.rb b/activesupport/lib/active_support/json.rb
index da938d1555..d7887175c0 100644
--- a/activesupport/lib/active_support/json.rb
+++ b/activesupport/lib/active_support/json.rb
@@ -1,2 +1,4 @@
+# frozen_string_literal: true
+
require "active_support/json/decoding"
require "active_support/json/encoding"
diff --git a/activesupport/lib/active_support/json/decoding.rb b/activesupport/lib/active_support/json/decoding.rb
index f487fa0c65..8c0e016dc5 100644
--- a/activesupport/lib/active_support/json/decoding.rb
+++ b/activesupport/lib/active_support/json/decoding.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/module/attribute_accessors"
require "active_support/core_ext/module/delegation"
require "json"
diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb
index defaf3f395..1339c75ffe 100644
--- a/activesupport/lib/active_support/json/encoding.rb
+++ b/activesupport/lib/active_support/json/encoding.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/object/json"
require "active_support/core_ext/module/delegation"
diff --git a/activesupport/lib/active_support/key_generator.rb b/activesupport/lib/active_support/key_generator.rb
index 23ab804eb1..78f7d7ca8d 100644
--- a/activesupport/lib/active_support/key_generator.rb
+++ b/activesupport/lib/active_support/key_generator.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "concurrent/map"
require "openssl"
@@ -57,7 +59,7 @@ module ActiveSupport
if secret.blank?
raise ArgumentError, "A secret is required to generate an integrity hash " \
"for cookie session data. Set a secret_key_base of at least " \
- "#{SECRET_MIN_LENGTH} characters in config/secrets.yml."
+ "#{SECRET_MIN_LENGTH} characters in via `bin/rails credentials:edit`."
end
if secret.length < SECRET_MIN_LENGTH
diff --git a/activesupport/lib/active_support/lazy_load_hooks.rb b/activesupport/lib/active_support/lazy_load_hooks.rb
index 720ed47331..dc8080c469 100644
--- a/activesupport/lib/active_support/lazy_load_hooks.rb
+++ b/activesupport/lib/active_support/lazy_load_hooks.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActiveSupport
# lazy_load_hooks allows Rails to lazily load a lot of components and thus
# making the app boot faster. Because of this feature now there is no need to
@@ -25,33 +27,51 @@ module ActiveSupport
base.class_eval do
@load_hooks = Hash.new { |h, k| h[k] = [] }
@loaded = Hash.new { |h, k| h[k] = [] }
+ @run_once = Hash.new { |h, k| h[k] = [] }
end
end
# Declares a block that will be executed when a Rails component is fully
# loaded.
+ #
+ # Options:
+ #
+ # * <tt>:yield</tt> - Yields the object that run_load_hooks to +block+.
+ # * <tt>:run_once</tt> - Given +block+ will run only once.
def on_load(name, options = {}, &block)
@loaded[name].each do |base|
- execute_hook(base, options, block)
+ execute_hook(name, base, options, block)
end
@load_hooks[name] << [block, options]
end
- def execute_hook(base, options, block)
- if options[:yield]
- block.call(base)
- else
- base.instance_eval(&block)
- end
- end
-
def run_load_hooks(name, base = Object)
@loaded[name] << base
@load_hooks[name].each do |hook, options|
- execute_hook(base, options, hook)
+ execute_hook(name, base, options, hook)
end
end
+
+ private
+
+ def with_execution_control(name, block, once)
+ unless @run_once[name].include?(block)
+ @run_once[name] << block if once
+
+ yield
+ end
+ end
+
+ def execute_hook(name, base, options, block)
+ with_execution_control(name, block, options[:run_once]) do
+ if options[:yield]
+ block.call(base)
+ else
+ base.instance_eval(&block)
+ end
+ end
+ end
end
extend LazyLoadHooks
diff --git a/activesupport/lib/active_support/log_subscriber.rb b/activesupport/lib/active_support/log_subscriber.rb
index e2c4f33565..0f7be06c8e 100644
--- a/activesupport/lib/active_support/log_subscriber.rb
+++ b/activesupport/lib/active_support/log_subscriber.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/module/attribute_accessors"
require "active_support/core_ext/class/attribute"
require "active_support/subscriber"
@@ -49,8 +51,7 @@ module ActiveSupport
CYAN = "\e[36m"
WHITE = "\e[37m"
- mattr_accessor :colorize_logging
- self.colorize_logging = true
+ mattr_accessor :colorize_logging, default: true
class << self
def logger
@@ -81,8 +82,10 @@ module ActiveSupport
def finish(name, id, payload)
super if logger
- rescue Exception => e
- logger.error "Could not log #{name.inspect} event. #{e.class}: #{e.message} #{e.backtrace}"
+ rescue => e
+ if logger
+ logger.error "Could not log #{name.inspect} event. #{e.class}: #{e.message} #{e.backtrace}"
+ end
end
private
diff --git a/activesupport/lib/active_support/log_subscriber/test_helper.rb b/activesupport/lib/active_support/log_subscriber/test_helper.rb
index 953ee77c2a..3f19ef5009 100644
--- a/activesupport/lib/active_support/log_subscriber/test_helper.rb
+++ b/activesupport/lib/active_support/log_subscriber/test_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/log_subscriber"
require "active_support/logger"
require "active_support/notifications"
diff --git a/activesupport/lib/active_support/logger.rb b/activesupport/lib/active_support/logger.rb
index ea09d7d2df..8152a182b4 100644
--- a/activesupport/lib/active_support/logger.rb
+++ b/activesupport/lib/active_support/logger.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/logger_silence"
require "active_support/logger_thread_safe_level"
require "logger"
diff --git a/activesupport/lib/active_support/logger_silence.rb b/activesupport/lib/active_support/logger_silence.rb
index 632994cf50..89f32b6782 100644
--- a/activesupport/lib/active_support/logger_silence.rb
+++ b/activesupport/lib/active_support/logger_silence.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/concern"
require "active_support/core_ext/module/attribute_accessors"
require "concurrent"
@@ -6,8 +8,7 @@ module LoggerSilence
extend ActiveSupport::Concern
included do
- cattr_accessor :silencer
- self.silencer = true
+ cattr_accessor :silencer, default: true
end
# Silences the logger for the duration of the block.
diff --git a/activesupport/lib/active_support/logger_thread_safe_level.rb b/activesupport/lib/active_support/logger_thread_safe_level.rb
index 7fb175dea6..ba32813d3d 100644
--- a/activesupport/lib/active_support/logger_thread_safe_level.rb
+++ b/activesupport/lib/active_support/logger_thread_safe_level.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/concern"
module ActiveSupport
diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb
index 0671469788..27fd061947 100644
--- a/activesupport/lib/active_support/message_encryptor.rb
+++ b/activesupport/lib/active_support/message_encryptor.rb
@@ -1,7 +1,10 @@
+# frozen_string_literal: true
+
require "openssl"
require "base64"
require "active_support/core_ext/array/extract_options"
require "active_support/message_verifier"
+require "active_support/messages/metadata"
module ActiveSupport
# MessageEncryptor is a simple way to encrypt values which get stored
@@ -13,13 +16,82 @@ module ActiveSupport
# This can be used in situations similar to the <tt>MessageVerifier</tt>, but
# where you don't want users to be able to determine the value of the payload.
#
- # salt = SecureRandom.random_bytes(64)
- # key = ActiveSupport::KeyGenerator.new('password').generate_key(salt, 32) # => "\x89\xE0\x156\xAC..."
- # crypt = ActiveSupport::MessageEncryptor.new(key) # => #<ActiveSupport::MessageEncryptor ...>
- # encrypted_data = crypt.encrypt_and_sign('my secret data') # => "NlFBTTMwOUV5UlA1QlNEN2xkY2d6eThYWWh..."
- # crypt.decrypt_and_verify(encrypted_data) # => "my secret data"
+ # len = ActiveSupport::MessageEncryptor.key_len
+ # salt = SecureRandom.random_bytes(len)
+ # key = ActiveSupport::KeyGenerator.new('password').generate_key(salt, len) # => "\x89\xE0\x156\xAC..."
+ # crypt = ActiveSupport::MessageEncryptor.new(key) # => #<ActiveSupport::MessageEncryptor ...>
+ # encrypted_data = crypt.encrypt_and_sign('my secret data') # => "NlFBTTMwOUV5UlA1QlNEN2xkY2d6eThYWWh..."
+ # crypt.decrypt_and_verify(encrypted_data) # => "my secret data"
+ #
+ # === Confining messages to a specific purpose
+ #
+ # By default any message can be used throughout your app. But they can also be
+ # confined to a specific +:purpose+.
+ #
+ # token = crypt.encrypt_and_sign("this is the chair", purpose: :login)
+ #
+ # Then that same purpose must be passed when verifying to get the data back out:
+ #
+ # crypt.decrypt_and_verify(token, purpose: :login) # => "this is the chair"
+ # crypt.decrypt_and_verify(token, purpose: :shipping) # => nil
+ # crypt.decrypt_and_verify(token) # => nil
+ #
+ # Likewise, if a message has no purpose it won't be returned when verifying with
+ # a specific purpose.
+ #
+ # token = crypt.encrypt_and_sign("the conversation is lively")
+ # crypt.decrypt_and_verify(token, purpose: :scare_tactics) # => nil
+ # crypt.decrypt_and_verify(token) # => "the conversation is lively"
+ #
+ # === Making messages expire
+ #
+ # By default messages last forever and verifying one year from now will still
+ # return the original value. But messages can be set to expire at a given
+ # time with +:expires_in+ or +:expires_at+.
+ #
+ # crypt.encrypt_and_sign(parcel, expires_in: 1.month)
+ # crypt.encrypt_and_sign(doowad, expires_at: Time.now.end_of_year)
+ #
+ # Then the messages can be verified and returned upto the expire time.
+ # Thereafter, verifying returns +nil+.
+ #
+ # === Rotating keys
+ #
+ # MessageEncryptor also supports rotating out old configurations by falling
+ # back to a stack of encryptors. Call +rotate+ to build and add an encryptor
+ # so +decrypt_and_verify+ will also try the fallback.
+ #
+ # By default any rotated encryptors use the values of the primary
+ # encryptor unless specified otherwise.
+ #
+ # You'd give your encryptor the new defaults:
+ #
+ # crypt = ActiveSupport::MessageEncryptor.new(@secret, cipher: "aes-256-gcm")
+ #
+ # Then gradually rotate the old values out by adding them as fallbacks. Any message
+ # generated with the old values will then work until the rotation is removed.
+ #
+ # crypt.rotate old_secret # Fallback to an old secret instead of @secret.
+ # crypt.rotate cipher: "aes-256-cbc" # Fallback to an old cipher instead of aes-256-gcm.
+ #
+ # Though if both the secret and the cipher was changed at the same time,
+ # the above should be combined into:
+ #
+ # crypt.rotate old_secret, cipher: "aes-256-cbc"
class MessageEncryptor
- DEFAULT_CIPHER = "aes-256-cbc"
+ prepend Messages::Rotator::Encryptor
+
+ class << self
+ attr_accessor :use_authenticated_message_encryption #:nodoc:
+
+ def default_cipher #:nodoc:
+ if use_authenticated_message_encryption
+ "aes-256-gcm"
+ else
+ "aes-256-cbc"
+ end
+ end
+ end
module NullSerializer #:nodoc:
def self.load(value)
@@ -45,14 +117,19 @@ module ActiveSupport
OpenSSLCipherError = OpenSSL::Cipher::CipherError
# Initialize a new MessageEncryptor. +secret+ must be at least as long as
- # the cipher key size. For the default 'aes-256-cbc' cipher, this is 256
+ # the cipher key size. For the default 'aes-256-gcm' cipher, this is 256
# bits. If you are using a user-entered secret, you can generate a suitable
# key by using <tt>ActiveSupport::KeyGenerator</tt> or a similar key
# derivation function.
#
+ # First additional parameter is used as the signature key for +MessageVerifier+.
+ # This allows you to specify keys to encrypt and sign data.
+ #
+ # ActiveSupport::MessageEncryptor.new('secret', 'signature_secret')
+ #
# Options:
# * <tt>:cipher</tt> - Cipher to use. Can be any cipher returned by
- # <tt>OpenSSL::Cipher.ciphers</tt>. Default is 'aes-256-cbc'.
+ # <tt>OpenSSL::Cipher.ciphers</tt>. Default is 'aes-256-gcm'.
# * <tt>:digest</tt> - String of digest to use for signing. Default is
# +SHA1+. Ignored when using an AEAD cipher like 'aes-256-gcm'.
# * <tt>:serializer</tt> - Object serializer to use. Default is +Marshal+.
@@ -61,32 +138,31 @@ module ActiveSupport
sign_secret = signature_key_or_options.first
@secret = secret
@sign_secret = sign_secret
- @cipher = options[:cipher] || "aes-256-cbc"
+ @cipher = options[:cipher] || self.class.default_cipher
@digest = options[:digest] || "SHA1" unless aead_mode?
@verifier = resolve_verifier
@serializer = options[:serializer] || Marshal
end
# Encrypt and sign a message. We need to sign the message in order to avoid
- # padding attacks. Reference: http://www.limited-entropy.com/padding-oracle-attacks.
- def encrypt_and_sign(value)
- verifier.generate(_encrypt(value))
+ # padding attacks. Reference: https://www.limited-entropy.com/padding-oracle-attacks/.
+ def encrypt_and_sign(value, expires_at: nil, expires_in: nil, purpose: nil)
+ verifier.generate(_encrypt(value, expires_at: expires_at, expires_in: expires_in, purpose: purpose))
end
# Decrypt and verify a message. We need to verify the message in order to
- # avoid padding attacks. Reference: http://www.limited-entropy.com/padding-oracle-attacks.
- def decrypt_and_verify(value)
- _decrypt(verifier.verify(value))
+ # avoid padding attacks. Reference: https://www.limited-entropy.com/padding-oracle-attacks/.
+ def decrypt_and_verify(data, purpose: nil, **)
+ _decrypt(verifier.verify(data), purpose)
end
# Given a cipher, returns the key length of the cipher to help generate the key of desired size
- def self.key_len(cipher = DEFAULT_CIPHER)
+ def self.key_len(cipher = default_cipher)
OpenSSL::Cipher.new(cipher).key_len
end
private
-
- def _encrypt(value)
+ def _encrypt(value, **metadata_options)
cipher = new_cipher
cipher.encrypt
cipher.key = @secret
@@ -95,22 +171,22 @@ module ActiveSupport
iv = cipher.random_iv
cipher.auth_data = "" if aead_mode?
- encrypted_data = cipher.update(@serializer.dump(value))
+ encrypted_data = cipher.update(Messages::Metadata.wrap(@serializer.dump(value), metadata_options))
encrypted_data << cipher.final
blob = "#{::Base64.strict_encode64 encrypted_data}--#{::Base64.strict_encode64 iv}"
- blob << "--#{::Base64.strict_encode64 cipher.auth_tag}" if aead_mode?
+ blob = "#{blob}--#{::Base64.strict_encode64 cipher.auth_tag}" if aead_mode?
blob
end
- def _decrypt(encrypted_message)
+ def _decrypt(encrypted_message, purpose)
cipher = new_cipher
encrypted_data, iv, auth_tag = encrypted_message.split("--".freeze).map { |v| ::Base64.strict_decode64(v) }
# Currently the OpenSSL bindings do not raise an error if auth_tag is
# truncated, which would allow an attacker to easily forge it. See
# https://github.com/ruby/openssl/issues/63
- raise InvalidMessage if aead_mode? && auth_tag.bytes.length != 16
+ raise InvalidMessage if aead_mode? && (auth_tag.nil? || auth_tag.bytes.length != 16)
cipher.decrypt
cipher.key = @secret
@@ -123,7 +199,8 @@ module ActiveSupport
decrypted_data = cipher.update(encrypted_data)
decrypted_data << cipher.final
- @serializer.load(decrypted_data)
+ message = Messages::Metadata.verify(decrypted_data, purpose)
+ @serializer.load(message) if message
rescue OpenSSLCipherError, TypeError, ArgumentError
raise InvalidMessage
end
diff --git a/activesupport/lib/active_support/message_verifier.rb b/activesupport/lib/active_support/message_verifier.rb
index 8419e858c6..83c39c0a86 100644
--- a/activesupport/lib/active_support/message_verifier.rb
+++ b/activesupport/lib/active_support/message_verifier.rb
@@ -1,6 +1,10 @@
+# frozen_string_literal: true
+
require "base64"
require "active_support/core_ext/object/blank"
require "active_support/security_utils"
+require "active_support/messages/metadata"
+require "active_support/messages/rotator"
module ActiveSupport
# +MessageVerifier+ makes it easy to generate and verify messages which are
@@ -27,10 +31,76 @@ module ActiveSupport
#
# +MessageVerifier+ creates HMAC signatures using SHA1 hash algorithm by default.
# If you want to use a different hash algorithm, you can change it by providing
- # `:digest` key as an option while initializing the verifier:
+ # +:digest+ key as an option while initializing the verifier:
#
# @verifier = ActiveSupport::MessageVerifier.new('s3Krit', digest: 'SHA256')
+ #
+ # === Confining messages to a specific purpose
+ #
+ # By default any message can be used throughout your app. But they can also be
+ # confined to a specific +:purpose+.
+ #
+ # token = @verifier.generate("this is the chair", purpose: :login)
+ #
+ # Then that same purpose must be passed when verifying to get the data back out:
+ #
+ # @verifier.verified(token, purpose: :login) # => "this is the chair"
+ # @verifier.verified(token, purpose: :shipping) # => nil
+ # @verifier.verified(token) # => nil
+ #
+ # @verifier.verify(token, purpose: :login) # => "this is the chair"
+ # @verifier.verify(token, purpose: :shipping) # => ActiveSupport::MessageVerifier::InvalidSignature
+ # @verifier.verify(token) # => ActiveSupport::MessageVerifier::InvalidSignature
+ #
+ # Likewise, if a message has no purpose it won't be returned when verifying with
+ # a specific purpose.
+ #
+ # token = @verifier.generate("the conversation is lively")
+ # @verifier.verified(token, purpose: :scare_tactics) # => nil
+ # @verifier.verified(token) # => "the conversation is lively"
+ #
+ # @verifier.verify(token, purpose: :scare_tactics) # => ActiveSupport::MessageVerifier::InvalidSignature
+ # @verifier.verify(token) # => "the conversation is lively"
+ #
+ # === Making messages expire
+ #
+ # By default messages last forever and verifying one year from now will still
+ # return the original value. But messages can be set to expire at a given
+ # time with +:expires_in+ or +:expires_at+.
+ #
+ # @verifier.generate(parcel, expires_in: 1.month)
+ # @verifier.generate(doowad, expires_at: Time.now.end_of_year)
+ #
+ # Then the messages can be verified and returned upto the expire time.
+ # Thereafter, the +verified+ method returns +nil+ while +verify+ raises
+ # <tt>ActiveSupport::MessageVerifier::InvalidSignature</tt>.
+ #
+ # === Rotating keys
+ #
+ # MessageVerifier also supports rotating out old configurations by falling
+ # back to a stack of verifiers. Call +rotate+ to build and add a verifier to
+ # so either +verified+ or +verify+ will also try verifying with the fallback.
+ #
+ # By default any rotated verifiers use the values of the primary
+ # verifier unless specified otherwise.
+ #
+ # You'd give your verifier the new defaults:
+ #
+ # verifier = ActiveSupport::MessageVerifier.new(@secret, digest: "SHA512", serializer: JSON)
+ #
+ # Then gradually rotate the old values out by adding them as fallbacks. Any message
+ # generated with the old values will then work until the rotation is removed.
+ #
+ # verifier.rotate old_secret # Fallback to an old secret instead of @secret.
+ # verifier.rotate digest: "SHA256" # Fallback to an old digest instead of SHA512.
+ # verifier.rotate serializer: Marshal # Fallback to an old serializer instead of JSON.
+ #
+ # Though the above would most likely be combined into one rotation:
+ #
+ # verifier.rotate old_secret, digest: "SHA256", serializer: Marshal
class MessageVerifier
+ prepend Messages::Rotator::Verifier
+
class InvalidSignature < StandardError; end
def initialize(secret, options = {})
@@ -77,11 +147,12 @@ module ActiveSupport
#
# incompatible_message = "test--dad7b06c94abba8d46a15fafaef56c327665d5ff"
# verifier.verified(incompatible_message) # => TypeError: incompatible marshal file format
- def verified(signed_message)
+ def verified(signed_message, purpose: nil, **)
if valid_message?(signed_message)
begin
data = signed_message.split("--".freeze)[0]
- @serializer.load(decode(data))
+ message = Messages::Metadata.verify(decode(data), purpose)
+ @serializer.load(message) if message
rescue ArgumentError => argument_error
return if argument_error.message.include?("invalid base64")
raise
@@ -101,8 +172,8 @@ module ActiveSupport
#
# other_verifier = ActiveSupport::MessageVerifier.new 'd1ff3r3nt-s3Krit'
# other_verifier.verify(signed_message) # => ActiveSupport::MessageVerifier::InvalidSignature
- def verify(signed_message)
- verified(signed_message) || raise(InvalidSignature)
+ def verify(*args)
+ verified(*args) || raise(InvalidSignature)
end
# Generates a signed message for the provided value.
@@ -112,8 +183,8 @@ module ActiveSupport
#
# verifier = ActiveSupport::MessageVerifier.new 's3Krit'
# verifier.generate 'a private message' # => "BAhJIhRwcml2YXRlLW1lc3NhZ2UGOgZFVA==--e2d724331ebdee96a10fb99b089508d1c72bd772"
- def generate(value)
- data = encode(@serializer.dump(value))
+ def generate(value, expires_at: nil, expires_in: nil, purpose: nil)
+ data = encode(Messages::Metadata.wrap(@serializer.dump(value), expires_at: expires_at, expires_in: expires_in, purpose: purpose))
"#{data}--#{generate_digest(data)}"
end
diff --git a/activesupport/lib/active_support/messages/metadata.rb b/activesupport/lib/active_support/messages/metadata.rb
new file mode 100644
index 0000000000..e97caac766
--- /dev/null
+++ b/activesupport/lib/active_support/messages/metadata.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+require "time"
+
+module ActiveSupport
+ module Messages #:nodoc:
+ class Metadata #:nodoc:
+ def initialize(message, expires_at = nil, purpose = nil)
+ @message, @expires_at, @purpose = message, expires_at, purpose
+ end
+
+ def as_json(options = {})
+ { _rails: { message: @message, exp: @expires_at, pur: @purpose } }
+ end
+
+ class << self
+ def wrap(message, expires_at: nil, expires_in: nil, purpose: nil)
+ if expires_at || expires_in || purpose
+ JSON.encode new(encode(message), pick_expiry(expires_at, expires_in), purpose)
+ else
+ message
+ end
+ end
+
+ def verify(message, purpose)
+ extract_metadata(message).verify(purpose)
+ end
+
+ private
+ def pick_expiry(expires_at, expires_in)
+ if expires_at
+ expires_at.utc.iso8601(3)
+ elsif expires_in
+ Time.now.utc.advance(seconds: expires_in).iso8601(3)
+ end
+ end
+
+ def extract_metadata(message)
+ data = JSON.decode(message) rescue nil
+
+ if data.is_a?(Hash) && data.key?("_rails")
+ new(decode(data["_rails"]["message"]), data["_rails"]["exp"], data["_rails"]["pur"])
+ else
+ new(message)
+ end
+ end
+
+ def encode(message)
+ ::Base64.strict_encode64(message)
+ end
+
+ def decode(message)
+ ::Base64.strict_decode64(message)
+ end
+ end
+
+ def verify(purpose)
+ @message if match?(purpose) && fresh?
+ end
+
+ private
+ def match?(purpose)
+ @purpose.to_s == purpose.to_s
+ end
+
+ def fresh?
+ @expires_at.nil? || Time.now.utc < Time.iso8601(@expires_at)
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/messages/rotation_configuration.rb b/activesupport/lib/active_support/messages/rotation_configuration.rb
new file mode 100644
index 0000000000..bd50d6d348
--- /dev/null
+++ b/activesupport/lib/active_support/messages/rotation_configuration.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module ActiveSupport
+ module Messages
+ class RotationConfiguration # :nodoc:
+ attr_reader :signed, :encrypted
+
+ def initialize
+ @signed, @encrypted = [], []
+ end
+
+ def rotate(kind, *args)
+ case kind
+ when :signed
+ @signed << args
+ when :encrypted
+ @encrypted << args
+ end
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/messages/rotator.rb b/activesupport/lib/active_support/messages/rotator.rb
new file mode 100644
index 0000000000..823a399d67
--- /dev/null
+++ b/activesupport/lib/active_support/messages/rotator.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+module ActiveSupport
+ module Messages
+ module Rotator # :nodoc:
+ def initialize(*, **options)
+ super
+
+ @options = options
+ @rotations = []
+ end
+
+ def rotate(*secrets, **options)
+ @rotations << build_rotation(*secrets, @options.merge(options))
+ end
+
+ module Encryptor
+ include Rotator
+
+ def decrypt_and_verify(*args, on_rotation: nil, **options)
+ super
+ rescue MessageEncryptor::InvalidMessage, MessageVerifier::InvalidSignature
+ run_rotations(on_rotation) { |encryptor| encryptor.decrypt_and_verify(*args, options) } || raise
+ end
+
+ private
+ def build_rotation(secret = @secret, sign_secret = @sign_secret, options)
+ self.class.new(secret, sign_secret, options)
+ end
+ end
+
+ module Verifier
+ include Rotator
+
+ def verified(*args, on_rotation: nil, **options)
+ super || run_rotations(on_rotation) { |verifier| verifier.verified(*args, options) }
+ end
+
+ private
+ def build_rotation(secret = @secret, options)
+ self.class.new(secret, options)
+ end
+ end
+
+ private
+ def run_rotations(on_rotation)
+ @rotations.find do |rotation|
+ if message = yield(rotation) rescue next
+ on_rotation.call if on_rotation
+ return message
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/multibyte.rb b/activesupport/lib/active_support/multibyte.rb
index f7c7befee0..3fe3a05e93 100644
--- a/activesupport/lib/active_support/multibyte.rb
+++ b/activesupport/lib/active_support/multibyte.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActiveSupport #:nodoc:
module Multibyte
autoload :Chars, "active_support/multibyte/chars"
diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb
index 65d6259a06..8152b8fd22 100644
--- a/activesupport/lib/active_support/multibyte/chars.rb
+++ b/activesupport/lib/active_support/multibyte/chars.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/json"
require "active_support/core_ext/string/access"
require "active_support/core_ext/string/behavior"
@@ -16,7 +18,8 @@ module ActiveSupport #:nodoc:
# through the +mb_chars+ method. Methods which would normally return a
# String object now return a Chars object so methods can be chained.
#
- # 'The Perfect String '.mb_chars.downcase.strip.normalize # => "the perfect string"
+ # 'The Perfect String '.mb_chars.downcase.strip.normalize
+ # # => #<ActiveSupport::Multibyte::Chars:0x007fdc434ccc10 @wrapped_string="the perfect string">
#
# Chars objects are perfectly interchangeable with String objects as long as
# no explicit class checks are made. If certain methods do explicitly check
@@ -134,7 +137,7 @@ module ActiveSupport #:nodoc:
# Converts characters in the string to the opposite case.
#
- # 'El Cañón".mb_chars.swapcase.to_s # => "eL cAÑÓN"
+ # 'El Cañón'.mb_chars.swapcase.to_s # => "eL cAÑÓN"
def swapcase
chars Unicode.swapcase(@wrapped_string)
end
@@ -148,8 +151,8 @@ module ActiveSupport #:nodoc:
# Capitalizes the first letter of every word, when possible.
#
- # "ÉL QUE SE ENTERÓ".mb_chars.titleize # => "Él Que Se Enteró"
- # "日本語".mb_chars.titleize # => "日本語"
+ # "ÉL QUE SE ENTERÓ".mb_chars.titleize.to_s # => "Él Que Se Enteró"
+ # "日本語".mb_chars.titleize.to_s # => "日本語"
def titleize
chars(downcase.to_s.gsub(/\b('?\S)/u) { Unicode.upcase($1) })
end
diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb
index 0912912aba..a64223c0e0 100644
--- a/activesupport/lib/active_support/multibyte/unicode.rb
+++ b/activesupport/lib/active_support/multibyte/unicode.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActiveSupport
module Multibyte
module Unicode
@@ -357,7 +359,7 @@ module ActiveSupport
# Returns the directory in which the data files are stored.
def self.dirname
- File.dirname(__FILE__) + "/../values/"
+ File.expand_path("../values", __dir__)
end
# Returns the filename for the data file for this version.
diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb
index 2df819e554..6207de8094 100644
--- a/activesupport/lib/active_support/notifications.rb
+++ b/activesupport/lib/active_support/notifications.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/notifications/instrumenter"
require "active_support/notifications/fanout"
require "active_support/per_thread_registry"
@@ -64,6 +66,8 @@ module ActiveSupport
# If an exception happens during that particular instrumentation the payload will
# have a key <tt>:exception</tt> with an array of two elements as value: a string with
# the name of the exception class, and the exception message.
+ # The <tt>:exception_object</tt> key of the payload will have the exception
+ # itself as the value.
#
# As the previous example depicts, the class <tt>ActiveSupport::Notifications::Event</tt>
# is able to take the arguments as they come and provide an object-oriented
diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb
index 9da115f552..25aab175b4 100644
--- a/activesupport/lib/active_support/notifications/fanout.rb
+++ b/activesupport/lib/active_support/notifications/fanout.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "mutex_m"
require "concurrent/map"
diff --git a/activesupport/lib/active_support/notifications/instrumenter.rb b/activesupport/lib/active_support/notifications/instrumenter.rb
index e11e2e0689..e99f5ee688 100644
--- a/activesupport/lib/active_support/notifications/instrumenter.rb
+++ b/activesupport/lib/active_support/notifications/instrumenter.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "securerandom"
module ActiveSupport
diff --git a/activesupport/lib/active_support/number_helper.rb b/activesupport/lib/active_support/number_helper.rb
index 880340ca86..8fd6e932f1 100644
--- a/activesupport/lib/active_support/number_helper.rb
+++ b/activesupport/lib/active_support/number_helper.rb
@@ -1,9 +1,12 @@
+# frozen_string_literal: true
+
module ActiveSupport
module NumberHelper
extend ActiveSupport::Autoload
eager_autoload do
autoload :NumberConverter
+ autoload :RoundingHelper
autoload :NumberToRoundedConverter
autoload :NumberToDelimitedConverter
autoload :NumberToHumanConverter
diff --git a/activesupport/lib/active_support/number_helper/number_converter.rb b/activesupport/lib/active_support/number_helper/number_converter.rb
index ce363287cf..06ba797a13 100644
--- a/activesupport/lib/active_support/number_helper/number_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_converter.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/big_decimal/conversions"
require "active_support/core_ext/object/blank"
require "active_support/core_ext/hash/keys"
diff --git a/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb b/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb
index 0f9dce722f..3f037c73ed 100644
--- a/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/numeric/inquiry"
module ActiveSupport
diff --git a/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb b/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb
index e3b35531b1..d5b5706705 100644
--- a/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActiveSupport
module NumberHelper
class NumberToDelimitedConverter < NumberConverter #:nodoc:
diff --git a/activesupport/lib/active_support/number_helper/number_to_human_converter.rb b/activesupport/lib/active_support/number_helper/number_to_human_converter.rb
index 56185ddf4b..03eb6671ec 100644
--- a/activesupport/lib/active_support/number_helper/number_to_human_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_human_converter.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActiveSupport
module NumberHelper
class NumberToHumanConverter < NumberConverter # :nodoc:
@@ -9,6 +11,7 @@ module ActiveSupport
self.validate_float = true
def convert # :nodoc:
+ @number = RoundingHelper.new(options).round(number)
@number = Float(number)
# for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
@@ -20,10 +23,7 @@ module ActiveSupport
exponent = calculate_exponent(units)
@number = number / (10**exponent)
- until (rounded_number = NumberToRoundedConverter.convert(number, options)) != NumberToRoundedConverter.convert(1000, options)
- @number = number / 1000.0
- exponent += 3
- end
+ rounded_number = NumberToRoundedConverter.convert(number, options)
unit = determine_unit(units, exponent)
format.gsub("%n".freeze, rounded_number).gsub("%u".freeze, unit).strip
end
diff --git a/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb b/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb
index f263dbe9f8..842f2fc8df 100644
--- a/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActiveSupport
module NumberHelper
class NumberToHumanSizeConverter < NumberConverter #:nodoc:
diff --git a/activesupport/lib/active_support/number_helper/number_to_percentage_converter.rb b/activesupport/lib/active_support/number_helper/number_to_percentage_converter.rb
index ac647ca9b7..4dcdad2e2c 100644
--- a/activesupport/lib/active_support/number_helper/number_to_percentage_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_percentage_converter.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActiveSupport
module NumberHelper
class NumberToPercentageConverter < NumberConverter # :nodoc:
diff --git a/activesupport/lib/active_support/number_helper/number_to_phone_converter.rb b/activesupport/lib/active_support/number_helper/number_to_phone_converter.rb
index 1de9f50f34..96410f4995 100644
--- a/activesupport/lib/active_support/number_helper/number_to_phone_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_phone_converter.rb
@@ -1,8 +1,10 @@
+# frozen_string_literal: true
+
module ActiveSupport
module NumberHelper
class NumberToPhoneConverter < NumberConverter #:nodoc:
def convert
- str = country_code(opts[:country_code])
+ str = country_code(opts[:country_code]).dup
str << convert_to_phone_number(number.to_s.strip)
str << phone_ext(opts[:extension])
end
diff --git a/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb b/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb
index 1f013990ea..eb528a0583 100644
--- a/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActiveSupport
module NumberHelper
class NumberToRoundedConverter < NumberConverter # :nodoc:
@@ -5,26 +7,14 @@ module ActiveSupport
self.validate_float = true
def convert
- precision = options.delete :precision
+ helper = RoundingHelper.new(options)
+ rounded_number = helper.round(number)
- if precision
- case number
- when Float, String
- @number = BigDecimal(number.to_s)
- when Rational
- @number = BigDecimal(number, digit_count(number.to_i) + precision)
- else
- @number = number.to_d
- end
-
- if options.delete(:significant) && precision > 0
- digits, rounded_number = digits_and_rounded_number(precision)
+ if precision = options[:precision]
+ if options[:significant] && precision > 0
+ digits = helper.digit_count(rounded_number)
precision -= digits
precision = 0 if precision < 0 # don't let it be negative
- else
- rounded_number = number.round(precision)
- rounded_number = rounded_number.to_i if precision == 0 && rounded_number.finite?
- rounded_number = rounded_number.abs if rounded_number.zero? # prevent showing negative zeros
end
formatted_string =
@@ -38,7 +28,7 @@ module ActiveSupport
"%00.#{precision}f" % rounded_number
end
else
- formatted_string = number
+ formatted_string = rounded_number
end
delimited_number = NumberToDelimitedConverter.convert(formatted_string, options)
@@ -47,26 +37,6 @@ module ActiveSupport
private
- def digits_and_rounded_number(precision)
- if zero?
- [1, 0]
- else
- digits = digit_count(number)
- multiplier = 10**(digits - precision)
- rounded_number = calculate_rounded_number(multiplier)
- digits = digit_count(rounded_number) # After rounding, the number of digits may have changed
- [digits, rounded_number]
- end
- end
-
- def calculate_rounded_number(multiplier)
- (number / BigDecimal.new(multiplier.to_f.to_s)).round * multiplier
- end
-
- def digit_count(number)
- number.zero? ? 1 : (Math.log10(absolute_number(number)) + 1).floor
- end
-
def strip_insignificant_zeros
options[:strip_insignificant_zeros]
end
@@ -79,14 +49,6 @@ module ActiveSupport
number
end
end
-
- def absolute_number(number)
- number.respond_to?(:abs) ? number.abs : number.to_d.abs
- end
-
- def zero?
- number.respond_to?(:zero?) ? number.zero? : number.to_d.zero?
- end
end
end
end
diff --git a/activesupport/lib/active_support/number_helper/rounding_helper.rb b/activesupport/lib/active_support/number_helper/rounding_helper.rb
new file mode 100644
index 0000000000..2ad8d49c4e
--- /dev/null
+++ b/activesupport/lib/active_support/number_helper/rounding_helper.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+module ActiveSupport
+ module NumberHelper
+ class RoundingHelper # :nodoc:
+ attr_reader :options
+
+ def initialize(options)
+ @options = options
+ end
+
+ def round(number)
+ return number unless precision
+ number = convert_to_decimal(number)
+ if significant && precision > 0
+ round_significant(number)
+ else
+ round_without_significant(number)
+ end
+ end
+
+ def digit_count(number)
+ return 1 if number.zero?
+ (Math.log10(absolute_number(number)) + 1).floor
+ end
+
+ private
+ def round_without_significant(number)
+ number = number.round(precision)
+ number = number.to_i if precision == 0 && number.finite?
+ number = number.abs if number.zero? # prevent showing negative zeros
+ number
+ end
+
+ def round_significant(number)
+ return 0 if number.zero?
+ digits = digit_count(number)
+ multiplier = 10**(digits - precision)
+ (number / BigDecimal(multiplier.to_f.to_s)).round * multiplier
+ end
+
+ def convert_to_decimal(number)
+ case number
+ when Float, String
+ BigDecimal(number.to_s)
+ when Rational
+ BigDecimal(number, digit_count(number.to_i) + precision)
+ else
+ number.to_d
+ end
+ end
+
+ def precision
+ options[:precision]
+ end
+
+ def significant
+ options[:significant]
+ end
+
+ def absolute_number(number)
+ number.respond_to?(:abs) ? number.abs : number.to_d.abs
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/option_merger.rb b/activesupport/lib/active_support/option_merger.rb
index 0f2caa98f2..ab9ca727f6 100644
--- a/activesupport/lib/active_support/option_merger.rb
+++ b/activesupport/lib/active_support/option_merger.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/hash/deep_merge"
module ActiveSupport
diff --git a/activesupport/lib/active_support/ordered_hash.rb b/activesupport/lib/active_support/ordered_hash.rb
index 3aa0a14f04..5758513021 100644
--- a/activesupport/lib/active_support/ordered_hash.rb
+++ b/activesupport/lib/active_support/ordered_hash.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "yaml"
YAML.add_builtin_type("omap") do |type, val|
diff --git a/activesupport/lib/active_support/ordered_options.rb b/activesupport/lib/active_support/ordered_options.rb
index 04d6dfaf9c..c4e419f546 100644
--- a/activesupport/lib/active_support/ordered_options.rb
+++ b/activesupport/lib/active_support/ordered_options.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/object/blank"
module ActiveSupport
@@ -22,7 +24,7 @@ module ActiveSupport
# To raise an exception when the value is blank, append a
# bang to the key name, like:
#
- # h.dog! # => raises KeyError: key not found: :dog
+ # h.dog! # => raises KeyError: :dog is blank
#
class OrderedOptions < Hash
alias_method :_get, :[] # preserve the original #[] method
@@ -44,7 +46,7 @@ module ActiveSupport
bangs = name_string.chomp!("!")
if bangs
- fetch(name_string.to_sym).presence || raise(KeyError.new("#{name_string} is blank."))
+ self[name_string].presence || raise(KeyError.new(":#{name_string} is blank"))
else
self[name_string]
end
diff --git a/activesupport/lib/active_support/per_thread_registry.rb b/activesupport/lib/active_support/per_thread_registry.rb
index 02431704d3..eb92fb4371 100644
--- a/activesupport/lib/active_support/per_thread_registry.rb
+++ b/activesupport/lib/active_support/per_thread_registry.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/module/delegation"
module ActiveSupport
diff --git a/activesupport/lib/active_support/proxy_object.rb b/activesupport/lib/active_support/proxy_object.rb
index 20a0fd8e62..0965fcd2d9 100644
--- a/activesupport/lib/active_support/proxy_object.rb
+++ b/activesupport/lib/active_support/proxy_object.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActiveSupport
# A class with no predefined methods that behaves similarly to Builder's
# BlankSlate. Used for proxy classes.
diff --git a/activesupport/lib/active_support/rails.rb b/activesupport/lib/active_support/rails.rb
index f6b018f0d3..5c34a0abb3 100644
--- a/activesupport/lib/active_support/rails.rb
+++ b/activesupport/lib/active_support/rails.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# This is private interface.
#
# Rails components cherry pick from Active Support as needed, but there are a
diff --git a/activesupport/lib/active_support/railtie.rb b/activesupport/lib/active_support/railtie.rb
index b875875afe..91872e29c8 100644
--- a/activesupport/lib/active_support/railtie.rb
+++ b/activesupport/lib/active_support/railtie.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support"
require "active_support/i18n_railtie"
@@ -7,6 +9,19 @@ module ActiveSupport
config.eager_load_namespaces << ActiveSupport
+ initializer "active_support.set_authenticated_message_encryption" do |app|
+ if app.config.active_support.respond_to?(:use_authenticated_message_encryption)
+ ActiveSupport::MessageEncryptor.use_authenticated_message_encryption =
+ app.config.active_support.use_authenticated_message_encryption
+ end
+ end
+
+ initializer "active_support.reset_all_current_attributes_instances" do |app|
+ app.reloader.before_class_unload { ActiveSupport::CurrentAttributes.clear_all }
+ app.executor.to_run { ActiveSupport::CurrentAttributes.reset_all }
+ app.executor.to_complete { ActiveSupport::CurrentAttributes.reset_all }
+ end
+
initializer "active_support.deprecation_behavior" do |app|
if deprecation = app.config.active_support.deprecation
ActiveSupport::Deprecation.behavior = deprecation
@@ -22,14 +37,7 @@ module ActiveSupport
raise e.exception "tzinfo-data is not present. Please add gem 'tzinfo-data' to your Gemfile and run bundle install"
end
require "active_support/core_ext/time/zones"
- zone_default = Time.find_zone!(app.config.time_zone)
-
- unless zone_default
- raise "Value assigned to config.time_zone not recognized. " \
- 'Run "rake time:zones:all" for a time zone names list.'
- end
-
- Time.zone_default = zone_default
+ Time.zone_default = Time.find_zone!(app.config.time_zone)
end
# Sets the default week start
@@ -41,11 +49,29 @@ module ActiveSupport
Date.beginning_of_week_default = beginning_of_week_default
end
+ initializer "active_support.require_master_key" do |app|
+ if app.config.respond_to?(:require_master_key) && app.config.require_master_key
+ begin
+ app.credentials.key
+ rescue ActiveSupport::EncryptedFile::MissingKeyError => error
+ $stderr.puts error.message
+ exit 1
+ end
+ end
+ end
+
initializer "active_support.set_configs" do |app|
app.config.active_support.each do |k, v|
k = "#{k}="
ActiveSupport.send(k, v) if ActiveSupport.respond_to? k
end
end
+
+ initializer "active_support.set_hash_digest_class" do |app|
+ if app.config.active_support.respond_to?(:hash_digest_class) && app.config.active_support.hash_digest_class
+ ActiveSupport::Digest.hash_digest_class =
+ app.config.active_support.hash_digest_class
+ end
+ end
end
end
diff --git a/activesupport/lib/active_support/reloader.rb b/activesupport/lib/active_support/reloader.rb
index 121c621751..b26d9c3665 100644
--- a/activesupport/lib/active_support/reloader.rb
+++ b/activesupport/lib/active_support/reloader.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/execution_wrapper"
module ActiveSupport
@@ -26,14 +28,17 @@ module ActiveSupport
define_callbacks :class_unload
+ # Registers a callback that will run once at application startup and every time the code is reloaded.
def self.to_prepare(*args, &block)
set_callback(:prepare, *args, &block)
end
+ # Registers a callback that will run immediately before the classes are unloaded.
def self.before_class_unload(*args, &block)
set_callback(:class_unload, *args, &block)
end
+ # Registers a callback that will run immediately after the classes are unloaded.
def self.after_class_unload(*args, &block)
set_callback(:class_unload, :after, *args, &block)
end
@@ -69,11 +74,8 @@ module ActiveSupport
end
end
- class_attribute :executor
- class_attribute :check
-
- self.executor = Executor
- self.check = lambda { false }
+ class_attribute :executor, default: Executor
+ class_attribute :check, default: lambda { false }
def self.check! # :nodoc:
@should_reload ||= check.call
diff --git a/activesupport/lib/active_support/rescuable.rb b/activesupport/lib/active_support/rescuable.rb
index ee6592fb5a..e0fa29cacb 100644
--- a/activesupport/lib/active_support/rescuable.rb
+++ b/activesupport/lib/active_support/rescuable.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/concern"
require "active_support/core_ext/class/attribute"
require "active_support/core_ext/string/inflections"
@@ -8,8 +10,7 @@ module ActiveSupport
extend Concern
included do
- class_attribute :rescue_handlers
- self.rescue_handlers = []
+ class_attribute :rescue_handlers, default: []
end
module ClassMethods
@@ -84,12 +85,18 @@ module ActiveSupport
# end
#
# Returns the exception if it was handled and +nil+ if it was not.
- def rescue_with_handler(exception, object: self)
+ def rescue_with_handler(exception, object: self, visited_exceptions: [])
+ visited_exceptions << exception
+
if handler = handler_for_rescue(exception, object: object)
handler.call exception
exception
elsif exception
- rescue_with_handler(exception.cause, object: object)
+ if visited_exceptions.include?(exception.cause)
+ nil
+ else
+ rescue_with_handler(exception.cause, object: object, visited_exceptions: visited_exceptions)
+ end
end
end
diff --git a/activesupport/lib/active_support/security_utils.rb b/activesupport/lib/active_support/security_utils.rb
index b655d64449..20b6b9cd3f 100644
--- a/activesupport/lib/active_support/security_utils.rb
+++ b/activesupport/lib/active_support/security_utils.rb
@@ -1,15 +1,15 @@
-require "digest"
+# frozen_string_literal: true
+
+require "digest/sha2"
module ActiveSupport
module SecurityUtils
- # Constant time string comparison.
+ # Constant time string comparison, for fixed length strings.
#
# The values compared should be of fixed length, such as strings
- # that have already been processed by HMAC. This should not be used
- # on variable length plaintext strings because it could leak length info
- # via timing attacks.
- def secure_compare(a, b)
- return false unless a.bytesize == b.bytesize
+ # that have already been processed by HMAC. Raises in case of length mismatch.
+ def fixed_length_secure_compare(a, b)
+ raise ArgumentError, "string length mismatch." unless a.bytesize == b.bytesize
l = a.unpack "C#{a.bytesize}"
@@ -17,11 +17,15 @@ module ActiveSupport
b.each_byte { |byte| res |= byte ^ l.shift }
res == 0
end
- module_function :secure_compare
+ module_function :fixed_length_secure_compare
- def variable_size_secure_compare(a, b) # :nodoc:
- secure_compare(::Digest::SHA256.hexdigest(a), ::Digest::SHA256.hexdigest(b))
+ # Constant time string comparison, for variable length strings.
+ #
+ # The values are first processed by SHA256, so that we don't leak length info
+ # via timing attacks.
+ def secure_compare(a, b)
+ fixed_length_secure_compare(::Digest::SHA256.hexdigest(a), ::Digest::SHA256.hexdigest(b)) && a == b
end
- module_function :variable_size_secure_compare
+ module_function :secure_compare
end
end
diff --git a/activesupport/lib/active_support/string_inquirer.rb b/activesupport/lib/active_support/string_inquirer.rb
index 90eac89c9e..a3af36720e 100644
--- a/activesupport/lib/active_support/string_inquirer.rb
+++ b/activesupport/lib/active_support/string_inquirer.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActiveSupport
# Wrapping a string in this class gives you a prettier way to test
# for equality. The value returned by <tt>Rails.env</tt> is wrapped
diff --git a/activesupport/lib/active_support/subscriber.rb b/activesupport/lib/active_support/subscriber.rb
index 2924139755..d6dd5474d0 100644
--- a/activesupport/lib/active_support/subscriber.rb
+++ b/activesupport/lib/active_support/subscriber.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/per_thread_registry"
require "active_support/notifications"
diff --git a/activesupport/lib/active_support/tagged_logging.rb b/activesupport/lib/active_support/tagged_logging.rb
index ad134c49b6..8561cba9f1 100644
--- a/activesupport/lib/active_support/tagged_logging.rb
+++ b/activesupport/lib/active_support/tagged_logging.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/module/delegation"
require "active_support/core_ext/object/blank"
require "logger"
diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb
index 3de4ccc1da..d1f7e6ea09 100644
--- a/activesupport/lib/active_support/test_case.rb
+++ b/activesupport/lib/active_support/test_case.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
gem "minitest" # make sure we get the gem, not stdlib
require "minitest"
require "active_support/testing/tagged_logging"
@@ -9,7 +11,6 @@ require "active_support/testing/isolation"
require "active_support/testing/constant_lookup"
require "active_support/testing/time_helpers"
require "active_support/testing/file_fixtures"
-require "active_support/core_ext/kernel/reporting"
module ActiveSupport
class TestCase < ::Minitest::Test
diff --git a/activesupport/lib/active_support/testing/assertions.rb b/activesupport/lib/active_support/testing/assertions.rb
index 28cf2953bf..b24aa36ede 100644
--- a/activesupport/lib/active_support/testing/assertions.rb
+++ b/activesupport/lib/active_support/testing/assertions.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActiveSupport
module Testing
module Assertions
@@ -155,9 +157,9 @@ module ActiveSupport
after = exp.call
if to == UNTRACKED
- error = "#{expression.inspect} didn't changed"
+ error = "#{expression.inspect} didn't change"
error = "#{message}.\n#{error}" if message
- assert_not_equal before, after, error
+ assert before != after, error
else
error = "#{expression.inspect} didn't change to #{to}"
error = "#{message}.\n#{error}" if message
@@ -167,7 +169,7 @@ module ActiveSupport
retval
end
- # Assertion that the result of evaluating an expression is changed before
+ # Assertion that the result of evaluating an expression is not changed before
# and after invoking the passed in block.
#
# assert_no_changes 'Status.all_good?' do
@@ -188,7 +190,7 @@ module ActiveSupport
error = "#{expression.inspect} did change to #{after}"
error = "#{message}.\n#{error}" if message
- assert_equal before, after, error
+ assert before == after, error
retval
end
diff --git a/activesupport/lib/active_support/testing/autorun.rb b/activesupport/lib/active_support/testing/autorun.rb
index 3108e3e549..889b41659a 100644
--- a/activesupport/lib/active_support/testing/autorun.rb
+++ b/activesupport/lib/active_support/testing/autorun.rb
@@ -1,9 +1,7 @@
+# frozen_string_literal: true
+
gem "minitest"
require "minitest"
-if Minitest.respond_to?(:run_via) && !Minitest.run_via[:rails]
- Minitest.run_via[:ruby] = true
-end
-
Minitest.autorun
diff --git a/activesupport/lib/active_support/testing/constant_lookup.rb b/activesupport/lib/active_support/testing/constant_lookup.rb
index 647395d2b3..51167e9237 100644
--- a/activesupport/lib/active_support/testing/constant_lookup.rb
+++ b/activesupport/lib/active_support/testing/constant_lookup.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/concern"
require "active_support/inflector"
diff --git a/activesupport/lib/active_support/testing/declarative.rb b/activesupport/lib/active_support/testing/declarative.rb
index 53ab3ebf78..7c3403684d 100644
--- a/activesupport/lib/active_support/testing/declarative.rb
+++ b/activesupport/lib/active_support/testing/declarative.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActiveSupport
module Testing
module Declarative
diff --git a/activesupport/lib/active_support/testing/deprecation.rb b/activesupport/lib/active_support/testing/deprecation.rb
index 58911570e8..f655435729 100644
--- a/activesupport/lib/active_support/testing/deprecation.rb
+++ b/activesupport/lib/active_support/testing/deprecation.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/deprecation"
require "active_support/core_ext/regexp"
diff --git a/activesupport/lib/active_support/testing/file_fixtures.rb b/activesupport/lib/active_support/testing/file_fixtures.rb
index affb84cda5..ad923d1aab 100644
--- a/activesupport/lib/active_support/testing/file_fixtures.rb
+++ b/activesupport/lib/active_support/testing/file_fixtures.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActiveSupport
module Testing
# Adds simple access to sample files called file fixtures.
diff --git a/activesupport/lib/active_support/testing/isolation.rb b/activesupport/lib/active_support/testing/isolation.rb
index 54c3263efa..fa9bebb181 100644
--- a/activesupport/lib/active_support/testing/isolation.rb
+++ b/activesupport/lib/active_support/testing/isolation.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActiveSupport
module Testing
module Isolation
@@ -53,7 +55,7 @@ module ActiveSupport
write.close
result = read.read
Process.wait2(pid)
- return result.unpack("m")[0]
+ result.unpack("m")[0]
end
end
diff --git a/activesupport/lib/active_support/testing/method_call_assertions.rb b/activesupport/lib/active_support/testing/method_call_assertions.rb
index 6b07416fdc..c6358002ea 100644
--- a/activesupport/lib/active_support/testing/method_call_assertions.rb
+++ b/activesupport/lib/active_support/testing/method_call_assertions.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "minitest/mock"
module ActiveSupport
diff --git a/activesupport/lib/active_support/testing/setup_and_teardown.rb b/activesupport/lib/active_support/testing/setup_and_teardown.rb
index 358c79c321..1dbf3c5da0 100644
--- a/activesupport/lib/active_support/testing/setup_and_teardown.rb
+++ b/activesupport/lib/active_support/testing/setup_and_teardown.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/concern"
require "active_support/callbacks"
diff --git a/activesupport/lib/active_support/testing/stream.rb b/activesupport/lib/active_support/testing/stream.rb
index 1d06b94559..d070a1793d 100644
--- a/activesupport/lib/active_support/testing/stream.rb
+++ b/activesupport/lib/active_support/testing/stream.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActiveSupport
module Testing
module Stream #:nodoc:
diff --git a/activesupport/lib/active_support/testing/tagged_logging.rb b/activesupport/lib/active_support/testing/tagged_logging.rb
index afdff87b45..9ca50c7918 100644
--- a/activesupport/lib/active_support/testing/tagged_logging.rb
+++ b/activesupport/lib/active_support/testing/tagged_logging.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActiveSupport
module Testing
# Logs a "PostsControllerTest: test name" heading before each test to
diff --git a/activesupport/lib/active_support/testing/time_helpers.rb b/activesupport/lib/active_support/testing/time_helpers.rb
index 07c9be0604..998a51a34c 100644
--- a/activesupport/lib/active_support/testing/time_helpers.rb
+++ b/activesupport/lib/active_support/testing/time_helpers.rb
@@ -1,4 +1,8 @@
+# frozen_string_literal: true
+
+require "active_support/core_ext/module/redefine_method"
require "active_support/core_ext/string/strip" # for strip_heredoc
+require "active_support/core_ext/time/calculations"
require "concurrent/map"
module ActiveSupport
@@ -40,7 +44,7 @@ module ActiveSupport
def unstub_object(stub)
singleton_class = stub.object.singleton_class
- singleton_class.send :undef_method, stub.method_name
+ singleton_class.send :silence_redefinition_of_method, stub.method_name
singleton_class.send :alias_method, stub.method_name, stub.original_method
singleton_class.send :undef_method, stub.original_method
end
@@ -48,8 +52,14 @@ module ActiveSupport
# Contains helpers that help you test passage of time.
module TimeHelpers
+ def after_teardown
+ travel_back
+ super
+ end
+
# Changes current time to the time in the future or in the past by a given time difference by
- # stubbing +Time.now+, +Date.today+, and +DateTime.now+.
+ # stubbing +Time.now+, +Date.today+, and +DateTime.now+. The stubs are automatically removed
+ # at the end of the test.
#
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
# travel 1.day
@@ -71,6 +81,7 @@ module ActiveSupport
# Changes current time to the given time by stubbing +Time.now+,
# +Date.today+, and +DateTime.now+ to return the time or date passed into this method.
+ # The stubs are automatically removed at the end of the test.
#
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
# travel_to Time.zone.local(2004, 11, 24, 01, 04, 44)
@@ -148,7 +159,7 @@ module ActiveSupport
end
# Returns the current time back to its original state, by removing the stubs added by
- # `travel` and `travel_to`.
+ # +travel+ and +travel_to+.
#
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
# travel_to Time.zone.local(2004, 11, 24, 01, 04, 44)
@@ -159,6 +170,26 @@ module ActiveSupport
simple_stubs.unstub_all!
end
+ # Calls +travel_to+ with +Time.now+.
+ #
+ # Time.current # => Sun, 09 Jul 2017 15:34:49 EST -05:00
+ # freeze_time
+ # sleep(1)
+ # Time.current # => Sun, 09 Jul 2017 15:34:49 EST -05:00
+ #
+ # This method also accepts a block, which will return the current time back to its original
+ # state at the end of the block:
+ #
+ # Time.current # => Sun, 09 Jul 2017 15:34:49 EST -05:00
+ # freeze_time do
+ # sleep(1)
+ # User.create.created_at # => Sun, 09 Jul 2017 15:34:49 EST -05:00
+ # end
+ # Time.current # => Sun, 09 Jul 2017 15:34:50 EST -05:00
+ def freeze_time(&block)
+ travel_to Time.now, &block
+ end
+
private
def simple_stubs
diff --git a/activesupport/lib/active_support/time.rb b/activesupport/lib/active_support/time.rb
index 7658228ca6..51854675bf 100644
--- a/activesupport/lib/active_support/time.rb
+++ b/activesupport/lib/active_support/time.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActiveSupport
autoload :Duration, "active_support/duration"
autoload :TimeWithZone, "active_support/time_with_zone"
diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index 889f71c4f3..20650ce714 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/duration"
require "active_support/values/time_zone"
require "active_support/core_ext/object/acts_like"
@@ -148,6 +150,7 @@ module ActiveSupport
"#{time.strftime(PRECISIONS[fraction_digits.to_i])}#{formatted_offset(true, 'Z'.freeze)}"
end
alias_method :iso8601, :xmlschema
+ alias_method :rfc3339, :xmlschema
# Coerces time to a string for JSON encoding. The default format is ISO 8601.
# You can get %Y/%m/%d %H:%M:%S +offset style by setting
@@ -329,6 +332,42 @@ module ActiveSupport
since(-other)
end
+ # Returns a new +ActiveSupport::TimeWithZone+ where one or more of the elements have
+ # been changed according to the +options+ parameter. The time options (<tt>:hour</tt>,
+ # <tt>:min</tt>, <tt>:sec</tt>, <tt>:usec</tt>, <tt>:nsec</tt>) reset cascadingly,
+ # so if only the hour is passed, then minute, sec, usec and nsec is set to 0. If the
+ # hour and minute is passed, then sec, usec and nsec is set to 0. The +options+
+ # parameter takes a hash with any of these keys: <tt>:year</tt>, <tt>:month</tt>,
+ # <tt>:day</tt>, <tt>:hour</tt>, <tt>:min</tt>, <tt>:sec</tt>, <tt>:usec</tt>,
+ # <tt>:nsec</tt>, <tt>:offset</tt>, <tt>:zone</tt>. Pass either <tt>:usec</tt>
+ # or <tt>:nsec</tt>, not both. Similarly, pass either <tt>:zone</tt> or
+ # <tt>:offset</tt>, not both.
+ #
+ # t = Time.zone.now # => Fri, 14 Apr 2017 11:45:15 EST -05:00
+ # t.change(year: 2020) # => Tue, 14 Apr 2020 11:45:15 EST -05:00
+ # t.change(hour: 12) # => Fri, 14 Apr 2017 12:00:00 EST -05:00
+ # t.change(min: 30) # => Fri, 14 Apr 2017 11:30:00 EST -05:00
+ # t.change(offset: "-10:00") # => Fri, 14 Apr 2017 11:45:15 HST -10:00
+ # t.change(zone: "Hawaii") # => Fri, 14 Apr 2017 11:45:15 HST -10:00
+ def change(options)
+ if options[:zone] && options[:offset]
+ raise ArgumentError, "Can't change both :offset and :zone at the same time: #{options.inspect}"
+ end
+
+ new_time = time.change(options)
+
+ if options[:zone]
+ new_zone = ::Time.find_zone(options[:zone])
+ elsif options[:offset]
+ new_zone = ::Time.find_zone(new_time.utc_offset)
+ end
+
+ new_zone ||= time_zone
+ periods = new_zone.periods_for_local(new_time)
+
+ self.class.new(nil, new_zone, new_time, periods.include?(period) ? period : nil)
+ end
+
# Uses Date to provide precise Time calculations for years, months, and days
# according to the proleptic Gregorian calendar. The result is returned as a
# new TimeWithZone object.
@@ -410,6 +449,17 @@ module ActiveSupport
@to_datetime ||= utc.to_datetime.new_offset(Rational(utc_offset, 86_400))
end
+ # Returns an instance of +Time+, either with the same UTC offset
+ # as +self+ or in the local system timezone depending on the setting
+ # of +ActiveSupport.to_time_preserves_timezone+.
+ def to_time
+ if preserve_timezone
+ @to_time_with_instance_offset ||= getlocal(utc_offset)
+ else
+ @to_time_with_system_offset ||= getlocal
+ end
+ end
+
# So that +self+ <tt>acts_like?(:time)</tt>.
def acts_like_time?
true
@@ -427,7 +477,8 @@ module ActiveSupport
end
def freeze
- period; utc; time # preload instance variables before freezing
+ # preload instance variables before freezing
+ period; utc; time; to_datetime; to_time
super
end
diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb
index 09cb9cbbe1..4d81ac939e 100644
--- a/activesupport/lib/active_support/values/time_zone.rb
+++ b/activesupport/lib/active_support/values/time_zone.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "tzinfo"
require "concurrent/map"
require "active_support/core_ext/object/blank"
@@ -6,7 +8,7 @@ module ActiveSupport
# The TimeZone class serves as a wrapper around TZInfo::Timezone instances.
# It allows us to do the following:
#
- # * Limit the set of zones provided by TZInfo to a meaningful subset of 146
+ # * Limit the set of zones provided by TZInfo to a meaningful subset of 134
# zones.
# * Retrieve and display zones with a friendlier name
# (e.g., "Eastern Time (US & Canada)" instead of "America/New_York").
@@ -28,7 +30,7 @@ module ActiveSupport
class TimeZone
# Keys are Rails TimeZone names, values are TZInfo identifiers.
MAPPING = {
- "International Date Line West" => "Pacific/Midway",
+ "International Date Line West" => "Etc/GMT+12",
"Midway Island" => "Pacific/Midway",
"American Samoa" => "Pacific/Pago_Pago",
"Hawaii" => "Pacific/Honolulu",
@@ -59,6 +61,7 @@ module ActiveSupport
"Buenos Aires" => "America/Argentina/Buenos_Aires",
"Montevideo" => "America/Montevideo",
"Georgetown" => "America/Guyana",
+ "Puerto Rico" => "America/Puerto_Rico",
"Greenland" => "America/Godthab",
"Mid-Atlantic" => "Atlantic/South_Georgia",
"Azores" => "Atlantic/Azores",
@@ -250,18 +253,31 @@ module ActiveSupport
# for time zones in the country specified by its ISO 3166-1 Alpha2 code.
def country_zones(country_code)
code = country_code.to_s.upcase
- @country_zones[code] ||=
- TZInfo::Country.get(code).zone_identifiers.map do |tz_id|
- name = MAPPING.key(tz_id)
- name && self[name]
- end.compact.sort!
+ @country_zones[code] ||= load_country_zones(code)
+ end
+
+ def clear #:nodoc:
+ @lazy_zones_map = Concurrent::Map.new
+ @country_zones = Concurrent::Map.new
+ @zones = nil
+ @zones_map = nil
end
private
+ def load_country_zones(code)
+ country = TZInfo::Country.get(code)
+ country.zone_identifiers.map do |tz_id|
+ if MAPPING.value?(tz_id)
+ self[MAPPING.key(tz_id)]
+ else
+ create(tz_id, nil, TZInfo::Timezone.new(tz_id))
+ end
+ end.sort!
+ end
+
def zones_map
- @zones_map ||= begin
- MAPPING.each_key { |place| self[place] } # load all the zones
- @lazy_zones_map
+ @zones_map ||= MAPPING.each_with_object({}) do |(name, _), zones|
+ zones[name] = self[name]
end
end
end
@@ -340,6 +356,41 @@ module ActiveSupport
end
# Method for creating new ActiveSupport::TimeWithZone instance in time zone
+ # of +self+ from an ISO 8601 string.
+ #
+ # Time.zone = 'Hawaii' # => "Hawaii"
+ # Time.zone.iso8601('1999-12-31T14:00:00') # => Fri, 31 Dec 1999 14:00:00 HST -10:00
+ #
+ # If the time components are missing then they will be set to zero.
+ #
+ # Time.zone = 'Hawaii' # => "Hawaii"
+ # Time.zone.iso8601('1999-12-31') # => Fri, 31 Dec 1999 00:00:00 HST -10:00
+ #
+ # If the string is invalid then an +ArgumentError+ will be raised unlike +parse+
+ # which usually returns +nil+ when given an invalid date string.
+ def iso8601(str)
+ parts = Date._iso8601(str)
+
+ raise ArgumentError, "invalid date" if parts.empty?
+
+ time = Time.new(
+ parts.fetch(:year),
+ parts.fetch(:mon),
+ parts.fetch(:mday),
+ parts.fetch(:hour, 0),
+ parts.fetch(:min, 0),
+ parts.fetch(:sec, 0) + parts.fetch(:sec_fraction, 0),
+ parts.fetch(:offset, 0)
+ )
+
+ if parts[:offset]
+ TimeWithZone.new(time.utc, self)
+ else
+ TimeWithZone.new(nil, self, time)
+ end
+ end
+
+ # Method for creating new ActiveSupport::TimeWithZone instance in time zone
# of +self+ from parsed string.
#
# Time.zone = 'Hawaii' # => "Hawaii"
@@ -355,10 +406,42 @@ module ActiveSupport
# components are supplied, then the day of the month defaults to 1:
#
# Time.zone.parse('Mar 2000') # => Wed, 01 Mar 2000 00:00:00 HST -10:00
+ #
+ # If the string is invalid then an +ArgumentError+ could be raised.
def parse(str, now = now())
parts_to_time(Date._parse(str, false), now)
end
+ # Method for creating new ActiveSupport::TimeWithZone instance in time zone
+ # of +self+ from an RFC 3339 string.
+ #
+ # Time.zone = 'Hawaii' # => "Hawaii"
+ # Time.zone.rfc3339('2000-01-01T00:00:00Z') # => Fri, 31 Dec 1999 14:00:00 HST -10:00
+ #
+ # If the time or zone components are missing then an +ArgumentError+ will
+ # be raised. This is much stricter than either +parse+ or +iso8601+ which
+ # allow for missing components.
+ #
+ # Time.zone = 'Hawaii' # => "Hawaii"
+ # Time.zone.rfc3339('1999-12-31') # => ArgumentError: invalid date
+ def rfc3339(str)
+ parts = Date._rfc3339(str)
+
+ raise ArgumentError, "invalid date" if parts.empty?
+
+ time = Time.new(
+ parts.fetch(:year),
+ parts.fetch(:mon),
+ parts.fetch(:mday),
+ parts.fetch(:hour),
+ parts.fetch(:min),
+ parts.fetch(:sec) + parts.fetch(:sec_fraction, 0),
+ parts.fetch(:offset)
+ )
+
+ TimeWithZone.new(time.utc, self)
+ end
+
# Parses +str+ according to +format+ and returns an ActiveSupport::TimeWithZone.
#
# Assumes that +str+ is a time in the time zone +self+,
@@ -429,7 +512,7 @@ module ActiveSupport
# Available so that TimeZone instances respond like TZInfo::Timezone
# instances.
def period_for_local(time, dst = true)
- tzinfo.period_for_local(time, dst)
+ tzinfo.period_for_local(time, dst) { |periods| periods.last }
end
def periods_for_local(time) #:nodoc:
diff --git a/activesupport/lib/active_support/version.rb b/activesupport/lib/active_support/version.rb
index 20b91ac911..928838c837 100644
--- a/activesupport/lib/active_support/version.rb
+++ b/activesupport/lib/active_support/version.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require_relative "gem_version"
module ActiveSupport
diff --git a/activesupport/lib/active_support/xml_mini.rb b/activesupport/lib/active_support/xml_mini.rb
index 782fb41288..ee2048cb54 100644
--- a/activesupport/lib/active_support/xml_mini.rb
+++ b/activesupport/lib/active_support/xml_mini.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "time"
require "base64"
require "bigdecimal"
diff --git a/activesupport/lib/active_support/xml_mini/jdom.rb b/activesupport/lib/active_support/xml_mini/jdom.rb
index a7939b3185..7f94a64016 100644
--- a/activesupport/lib/active_support/xml_mini/jdom.rb
+++ b/activesupport/lib/active_support/xml_mini/jdom.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
raise "JRuby is required to use the JDOM backend for XmlMini" unless RUBY_PLATFORM.include?("java")
require "jruby"
@@ -38,7 +40,7 @@ module ActiveSupport
else
@dbf = DocumentBuilderFactory.new_instance
# secure processing of java xml
- # http://www.ibm.com/developerworks/xml/library/x-tipcfsx/index.html
+ # https://archive.is/9xcQQ
@dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false)
@dbf.setFeature("http://xml.org/sax/features/external-general-entities", false)
@dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false)
@@ -167,7 +169,7 @@ module ActiveSupport
# element::
# XML element to be checked.
def empty_content?(element)
- text = ""
+ text = "".dup
child_nodes = element.child_nodes
(0...child_nodes.length).each do |i|
item = child_nodes.item(i)
diff --git a/activesupport/lib/active_support/xml_mini/libxml.rb b/activesupport/lib/active_support/xml_mini/libxml.rb
index 44b0bdb7dc..0b000fea60 100644
--- a/activesupport/lib/active_support/xml_mini/libxml.rb
+++ b/activesupport/lib/active_support/xml_mini/libxml.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "libxml"
require "active_support/core_ext/object/blank"
require "stringio"
@@ -14,11 +16,9 @@ module ActiveSupport
data = StringIO.new(data || "")
end
- char = data.getc
- if char.nil?
+ if data.eof?
{}
else
- data.ungetc(char)
LibXML::XML::Parser.io(data).parse.to_hash
end
end
@@ -55,7 +55,7 @@ module LibXML #:nodoc:
if c.element?
c.to_hash(node_hash)
elsif c.text? || c.cdata?
- node_hash[CONTENT_ROOT] ||= ""
+ node_hash[CONTENT_ROOT] ||= "".dup
node_hash[CONTENT_ROOT] << c.content
end
end
@@ -74,5 +74,7 @@ module LibXML #:nodoc:
end
end
+# :enddoc:
+
LibXML::XML::Document.include(LibXML::Conversions::Document)
LibXML::XML::Node.include(LibXML::Conversions::Node)
diff --git a/activesupport/lib/active_support/xml_mini/libxmlsax.rb b/activesupport/lib/active_support/xml_mini/libxmlsax.rb
index 8a43b05b17..dcf16e6084 100644
--- a/activesupport/lib/active_support/xml_mini/libxmlsax.rb
+++ b/activesupport/lib/active_support/xml_mini/libxmlsax.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "libxml"
require "active_support/core_ext/object/blank"
require "stringio"
@@ -21,7 +23,7 @@ module ActiveSupport
end
def on_start_document
- @hash = { CONTENT_KEY => "" }
+ @hash = { CONTENT_KEY => "".dup }
@hash_stack = [@hash]
end
@@ -31,7 +33,7 @@ module ActiveSupport
end
def on_start_element(name, attrs = {})
- new_hash = { CONTENT_KEY => "" }.merge!(attrs)
+ new_hash = { CONTENT_KEY => "".dup }.merge!(attrs)
new_hash[HASH_SIZE_KEY] = new_hash.size + 1
case current_hash[name]
@@ -65,12 +67,9 @@ module ActiveSupport
data = StringIO.new(data || "")
end
- char = data.getc
- if char.nil?
+ if data.eof?
{}
else
- data.ungetc(char)
-
LibXML::XML::Error.set_handler(&LibXML::XML::Error::QUIET_HANDLER)
parser = LibXML::XML::SaxParser.io(data)
document = document_class.new
diff --git a/activesupport/lib/active_support/xml_mini/nokogiri.rb b/activesupport/lib/active_support/xml_mini/nokogiri.rb
index 4c2be3f3ec..5ee6fc8159 100644
--- a/activesupport/lib/active_support/xml_mini/nokogiri.rb
+++ b/activesupport/lib/active_support/xml_mini/nokogiri.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
begin
require "nokogiri"
rescue LoadError => e
@@ -19,11 +21,9 @@ module ActiveSupport
data = StringIO.new(data || "")
end
- char = data.getc
- if char.nil?
+ if data.eof?
{}
else
- data.ungetc(char)
doc = Nokogiri::XML(data)
raise doc.errors.first if doc.errors.length > 0
doc.to_hash
@@ -59,7 +59,7 @@ module ActiveSupport
if c.element?
c.to_hash(node_hash)
elsif c.text? || c.cdata?
- node_hash[CONTENT_ROOT] ||= ""
+ node_hash[CONTENT_ROOT] ||= "".dup
node_hash[CONTENT_ROOT] << c.content
end
end
diff --git a/activesupport/lib/active_support/xml_mini/nokogirisax.rb b/activesupport/lib/active_support/xml_mini/nokogirisax.rb
index 7388bea5d8..b01ed00a14 100644
--- a/activesupport/lib/active_support/xml_mini/nokogirisax.rb
+++ b/activesupport/lib/active_support/xml_mini/nokogirisax.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
begin
require "nokogiri"
rescue LoadError => e
@@ -37,7 +39,7 @@ module ActiveSupport
end
def start_element(name, attrs = [])
- new_hash = { CONTENT_KEY => "" }.merge!(Hash[attrs])
+ new_hash = { CONTENT_KEY => "".dup }.merge!(Hash[attrs])
new_hash[HASH_SIZE_KEY] = new_hash.size + 1
case current_hash[name]
@@ -71,11 +73,9 @@ module ActiveSupport
data = StringIO.new(data || "")
end
- char = data.getc
- if char.nil?
+ if data.eof?
{}
else
- data.ungetc(char)
document = document_class.new
parser = Nokogiri::XML::SAX::Parser.new(document)
parser.parse(data)
diff --git a/activesupport/lib/active_support/xml_mini/rexml.rb b/activesupport/lib/active_support/xml_mini/rexml.rb
index 03fa910fa5..32458d5b0d 100644
--- a/activesupport/lib/active_support/xml_mini/rexml.rb
+++ b/activesupport/lib/active_support/xml_mini/rexml.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/kernel/reporting"
require "active_support/core_ext/object/blank"
require "stringio"
@@ -74,7 +76,7 @@ module ActiveSupport
hash
else
# must use value to prevent double-escaping
- texts = ""
+ texts = "".dup
element.texts.each { |t| texts << t.value }
merge!(hash, CONTENT_KEY, texts)
end
diff --git a/activesupport/test/abstract_unit.rb b/activesupport/test/abstract_unit.rb
index c4f34c0abf..f214898145 100644
--- a/activesupport/test/abstract_unit.rb
+++ b/activesupport/test/abstract_unit.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
ORIG_ARGV = ARGV.dup
require "active_support/core_ext/kernel/reporting"
@@ -36,4 +38,8 @@ class ActiveSupport::TestCase
private def jruby_skip(message = "")
skip message if defined?(JRUBY_VERSION)
end
+
+ def frozen_error_class
+ Object.const_defined?(:FrozenError) ? FrozenError : RuntimeError
+ end
end
diff --git a/activesupport/test/array_inquirer_test.rb b/activesupport/test/array_inquirer_test.rb
index 5b2bc82905..d5419b862d 100644
--- a/activesupport/test/array_inquirer_test.rb
+++ b/activesupport/test/array_inquirer_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/core_ext/array"
diff --git a/activesupport/test/autoload_test.rb b/activesupport/test/autoload_test.rb
index 6c8aa3e055..216b069420 100644
--- a/activesupport/test/autoload_test.rb
+++ b/activesupport/test/autoload_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
class TestAutoloadModule < ActiveSupport::TestCase
diff --git a/activesupport/test/autoloading_fixtures/a/b.rb b/activesupport/test/autoloading_fixtures/a/b.rb
index 0dbbbbd181..27baaea08c 100644
--- a/activesupport/test/autoloading_fixtures/a/b.rb
+++ b/activesupport/test/autoloading_fixtures/a/b.rb
@@ -1,2 +1,4 @@
+# frozen_string_literal: true
+
class A::B
end
diff --git a/activesupport/test/autoloading_fixtures/a/c/d.rb b/activesupport/test/autoloading_fixtures/a/c/d.rb
index 2c0ec5f182..f07128673f 100644
--- a/activesupport/test/autoloading_fixtures/a/c/d.rb
+++ b/activesupport/test/autoloading_fixtures/a/c/d.rb
@@ -1,2 +1,4 @@
+# frozen_string_literal: true
+
class A::C::D
end
diff --git a/activesupport/test/autoloading_fixtures/a/c/em/f.rb b/activesupport/test/autoloading_fixtures/a/c/em/f.rb
index 3ff1b7efa0..78c96cf45f 100644
--- a/activesupport/test/autoloading_fixtures/a/c/em/f.rb
+++ b/activesupport/test/autoloading_fixtures/a/c/em/f.rb
@@ -1,2 +1,4 @@
+# frozen_string_literal: true
+
class A::C::EM::F
end
diff --git a/activesupport/test/autoloading_fixtures/application.rb b/activesupport/test/autoloading_fixtures/application.rb
index d7d3096dcb..971cbe1b17 100644
--- a/activesupport/test/autoloading_fixtures/application.rb
+++ b/activesupport/test/autoloading_fixtures/application.rb
@@ -1 +1,3 @@
+# frozen_string_literal: true
+
ApplicationController = 10
diff --git a/activesupport/test/autoloading_fixtures/circular1.rb b/activesupport/test/autoloading_fixtures/circular1.rb
index a45761f066..7f891b5eb1 100644
--- a/activesupport/test/autoloading_fixtures/circular1.rb
+++ b/activesupport/test/autoloading_fixtures/circular1.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
silence_warnings do
Circular2
end
diff --git a/activesupport/test/autoloading_fixtures/circular2.rb b/activesupport/test/autoloading_fixtures/circular2.rb
index c847fa5001..1fdb4c261f 100644
--- a/activesupport/test/autoloading_fixtures/circular2.rb
+++ b/activesupport/test/autoloading_fixtures/circular2.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
Circular1
class Circular2
diff --git a/activesupport/test/autoloading_fixtures/class_folder.rb b/activesupport/test/autoloading_fixtures/class_folder.rb
index 6ee8182214..ff0826c298 100644
--- a/activesupport/test/autoloading_fixtures/class_folder.rb
+++ b/activesupport/test/autoloading_fixtures/class_folder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ClassFolder
ConstantInClassFolder = "indeed"
end
diff --git a/activesupport/test/autoloading_fixtures/class_folder/class_folder_subclass.rb b/activesupport/test/autoloading_fixtures/class_folder/class_folder_subclass.rb
index 4df069cab6..cd901e9d71 100644
--- a/activesupport/test/autoloading_fixtures/class_folder/class_folder_subclass.rb
+++ b/activesupport/test/autoloading_fixtures/class_folder/class_folder_subclass.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ClassFolder::ClassFolderSubclass < ClassFolder
ConstantInClassFolder = "indeed"
end
diff --git a/activesupport/test/autoloading_fixtures/class_folder/inline_class.rb b/activesupport/test/autoloading_fixtures/class_folder/inline_class.rb
index 8235e90724..960bfcbc70 100644
--- a/activesupport/test/autoloading_fixtures/class_folder/inline_class.rb
+++ b/activesupport/test/autoloading_fixtures/class_folder/inline_class.rb
@@ -1,2 +1,4 @@
+# frozen_string_literal: true
+
class ClassFolder::InlineClass
end
diff --git a/activesupport/test/autoloading_fixtures/class_folder/nested_class.rb b/activesupport/test/autoloading_fixtures/class_folder/nested_class.rb
index 57a13d89ea..98426b797d 100644
--- a/activesupport/test/autoloading_fixtures/class_folder/nested_class.rb
+++ b/activesupport/test/autoloading_fixtures/class_folder/nested_class.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ClassFolder
class NestedClass
end
diff --git a/activesupport/test/autoloading_fixtures/conflict.rb b/activesupport/test/autoloading_fixtures/conflict.rb
index d7f42b5d5f..c5d3f6bdc0 100644
--- a/activesupport/test/autoloading_fixtures/conflict.rb
+++ b/activesupport/test/autoloading_fixtures/conflict.rb
@@ -1 +1,3 @@
+# frozen_string_literal: true
+
Conflict = 2
diff --git a/activesupport/test/autoloading_fixtures/counting_loader.rb b/activesupport/test/autoloading_fixtures/counting_loader.rb
index 4225c4412c..6ac3a9828d 100644
--- a/activesupport/test/autoloading_fixtures/counting_loader.rb
+++ b/activesupport/test/autoloading_fixtures/counting_loader.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
$counting_loaded_times ||= 0
$counting_loaded_times += 1
diff --git a/activesupport/test/autoloading_fixtures/cross_site_dependency.rb b/activesupport/test/autoloading_fixtures/cross_site_dependency.rb
index de941bf271..8a18dcff10 100644
--- a/activesupport/test/autoloading_fixtures/cross_site_dependency.rb
+++ b/activesupport/test/autoloading_fixtures/cross_site_dependency.rb
@@ -1,2 +1,4 @@
+# frozen_string_literal: true
+
class CrossSiteDependency
end
diff --git a/activesupport/test/autoloading_fixtures/d.rb b/activesupport/test/autoloading_fixtures/d.rb
index 52850e1e1a..72752d878e 100644
--- a/activesupport/test/autoloading_fixtures/d.rb
+++ b/activesupport/test/autoloading_fixtures/d.rb
@@ -1,2 +1,4 @@
+# frozen_string_literal: true
+
class D
end
diff --git a/activesupport/test/autoloading_fixtures/em.rb b/activesupport/test/autoloading_fixtures/em.rb
index e47024999e..2e0ac9a6f9 100644
--- a/activesupport/test/autoloading_fixtures/em.rb
+++ b/activesupport/test/autoloading_fixtures/em.rb
@@ -1,2 +1,4 @@
+# frozen_string_literal: true
+
class EM
end
diff --git a/activesupport/test/autoloading_fixtures/html/some_class.rb b/activesupport/test/autoloading_fixtures/html/some_class.rb
index b43d15d891..fbbfd4a214 100644
--- a/activesupport/test/autoloading_fixtures/html/some_class.rb
+++ b/activesupport/test/autoloading_fixtures/html/some_class.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module HTML
class SomeClass
end
diff --git a/activesupport/test/autoloading_fixtures/load_path/loaded_constant.rb b/activesupport/test/autoloading_fixtures/load_path/loaded_constant.rb
index d2c4f6b0c5..8735ce87e1 100644
--- a/activesupport/test/autoloading_fixtures/load_path/loaded_constant.rb
+++ b/activesupport/test/autoloading_fixtures/load_path/loaded_constant.rb
@@ -1,2 +1,4 @@
+# frozen_string_literal: true
+
module LoadedConstant
end
diff --git a/activesupport/test/autoloading_fixtures/loads_constant.rb b/activesupport/test/autoloading_fixtures/loads_constant.rb
index 0b30dc8bca..0bb434a956 100644
--- a/activesupport/test/autoloading_fixtures/loads_constant.rb
+++ b/activesupport/test/autoloading_fixtures/loads_constant.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module LoadsConstant
end
diff --git a/activesupport/test/autoloading_fixtures/module_folder/inline_class.rb b/activesupport/test/autoloading_fixtures/module_folder/inline_class.rb
index ca83437046..c11246b528 100644
--- a/activesupport/test/autoloading_fixtures/module_folder/inline_class.rb
+++ b/activesupport/test/autoloading_fixtures/module_folder/inline_class.rb
@@ -1,2 +1,4 @@
+# frozen_string_literal: true
+
class ModuleFolder::InlineClass
end
diff --git a/activesupport/test/autoloading_fixtures/module_folder/nested_class.rb b/activesupport/test/autoloading_fixtures/module_folder/nested_class.rb
index fc4076bd0a..69226b405c 100644
--- a/activesupport/test/autoloading_fixtures/module_folder/nested_class.rb
+++ b/activesupport/test/autoloading_fixtures/module_folder/nested_class.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ModuleFolder
class NestedClass
end
diff --git a/activesupport/test/autoloading_fixtures/module_folder/nested_sibling.rb b/activesupport/test/autoloading_fixtures/module_folder/nested_sibling.rb
index 04c426833e..30de83af11 100644
--- a/activesupport/test/autoloading_fixtures/module_folder/nested_sibling.rb
+++ b/activesupport/test/autoloading_fixtures/module_folder/nested_sibling.rb
@@ -1,2 +1,4 @@
+# frozen_string_literal: true
+
class ModuleFolder::NestedSibling
end
diff --git a/activesupport/test/autoloading_fixtures/module_with_custom_const_missing/a/b.rb b/activesupport/test/autoloading_fixtures/module_with_custom_const_missing/a/b.rb
index 4f2020c503..f688c1ef35 100644
--- a/activesupport/test/autoloading_fixtures/module_with_custom_const_missing/a/b.rb
+++ b/activesupport/test/autoloading_fixtures/module_with_custom_const_missing/a/b.rb
@@ -1 +1,3 @@
+# frozen_string_literal: true
+
ModuleWithCustomConstMissing::A::B = "10"
diff --git a/activesupport/test/autoloading_fixtures/multiple_constant_file.rb b/activesupport/test/autoloading_fixtures/multiple_constant_file.rb
index a9ff4eb89c..1da26e6c2c 100644
--- a/activesupport/test/autoloading_fixtures/multiple_constant_file.rb
+++ b/activesupport/test/autoloading_fixtures/multiple_constant_file.rb
@@ -1,2 +1,4 @@
+# frozen_string_literal: true
+
MultipleConstantFile = 10
SiblingConstant = MultipleConstantFile * 2
diff --git a/activesupport/test/autoloading_fixtures/prepend.rb b/activesupport/test/autoloading_fixtures/prepend.rb
index 3134d1df2b..bf9e36e12c 100644
--- a/activesupport/test/autoloading_fixtures/prepend.rb
+++ b/activesupport/test/autoloading_fixtures/prepend.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class SubClassConflict
end
diff --git a/activesupport/test/autoloading_fixtures/prepend/sub_class_conflict.rb b/activesupport/test/autoloading_fixtures/prepend/sub_class_conflict.rb
index 090dda3043..506c3c5920 100644
--- a/activesupport/test/autoloading_fixtures/prepend/sub_class_conflict.rb
+++ b/activesupport/test/autoloading_fixtures/prepend/sub_class_conflict.rb
@@ -1,2 +1,4 @@
+# frozen_string_literal: true
+
class Prepend::SubClassConflict
end
diff --git a/activesupport/test/autoloading_fixtures/raises_arbitrary_exception.rb b/activesupport/test/autoloading_fixtures/raises_arbitrary_exception.rb
index e477ab21d0..118ee6bdd1 100644
--- a/activesupport/test/autoloading_fixtures/raises_arbitrary_exception.rb
+++ b/activesupport/test/autoloading_fixtures/raises_arbitrary_exception.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RaisesArbitraryException = 1
_ = A::B # Autoloading recursion, also expected to be watched and discarded.
diff --git a/activesupport/test/autoloading_fixtures/raises_name_error.rb b/activesupport/test/autoloading_fixtures/raises_name_error.rb
index a49960abf0..c23afb7b12 100644
--- a/activesupport/test/autoloading_fixtures/raises_name_error.rb
+++ b/activesupport/test/autoloading_fixtures/raises_name_error.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class RaisesNameError
FooBarBaz
end
diff --git a/activesupport/test/autoloading_fixtures/raises_no_method_error.rb b/activesupport/test/autoloading_fixtures/raises_no_method_error.rb
index e1b8fce24a..1000ce1cf5 100644
--- a/activesupport/test/autoloading_fixtures/raises_no_method_error.rb
+++ b/activesupport/test/autoloading_fixtures/raises_no_method_error.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class RaisesNoMethodError
self.foobar_method_doesnt_exist
end
diff --git a/activesupport/test/autoloading_fixtures/requires_constant.rb b/activesupport/test/autoloading_fixtures/requires_constant.rb
index f04dcc4091..6e51998949 100644
--- a/activesupport/test/autoloading_fixtures/requires_constant.rb
+++ b/activesupport/test/autoloading_fixtures/requires_constant.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "loaded_constant"
module RequiresConstant
diff --git a/activesupport/test/autoloading_fixtures/should_not_be_required.rb b/activesupport/test/autoloading_fixtures/should_not_be_required.rb
index 1fcf170cc5..8deffa1816 100644
--- a/activesupport/test/autoloading_fixtures/should_not_be_required.rb
+++ b/activesupport/test/autoloading_fixtures/should_not_be_required.rb
@@ -1 +1,3 @@
+# frozen_string_literal: true
+
ShouldNotBeAutoloaded = 0
diff --git a/activesupport/test/autoloading_fixtures/throws.rb b/activesupport/test/autoloading_fixtures/throws.rb
index e1d96cc512..b6fb391032 100644
--- a/activesupport/test/autoloading_fixtures/throws.rb
+++ b/activesupport/test/autoloading_fixtures/throws.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
Throws = 1
_ = A::B # Autoloading recursion, expected to be discarded.
diff --git a/activesupport/test/autoloading_fixtures/typo.rb b/activesupport/test/autoloading_fixtures/typo.rb
index 0ffe07c2b3..d45cddbcf5 100644
--- a/activesupport/test/autoloading_fixtures/typo.rb
+++ b/activesupport/test/autoloading_fixtures/typo.rb
@@ -1 +1,3 @@
+# frozen_string_literal: true
+
TypO = 1
diff --git a/activesupport/test/benchmarkable_test.rb b/activesupport/test/benchmarkable_test.rb
index 210b9cb9fd..424da0a52c 100644
--- a/activesupport/test/benchmarkable_test.rb
+++ b/activesupport/test/benchmarkable_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
class BenchmarkableTest < ActiveSupport::TestCase
diff --git a/activesupport/test/broadcast_logger_test.rb b/activesupport/test/broadcast_logger_test.rb
index 184d0ebddd..181113e70a 100644
--- a/activesupport/test/broadcast_logger_test.rb
+++ b/activesupport/test/broadcast_logger_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
module ActiveSupport
diff --git a/activesupport/test/cache/behaviors.rb b/activesupport/test/cache/behaviors.rb
new file mode 100644
index 0000000000..cb08a10bba
--- /dev/null
+++ b/activesupport/test/cache/behaviors.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require_relative "behaviors/autoloading_cache_behavior"
+require_relative "behaviors/cache_delete_matched_behavior"
+require_relative "behaviors/cache_increment_decrement_behavior"
+require_relative "behaviors/cache_store_behavior"
+require_relative "behaviors/cache_store_version_behavior"
+require_relative "behaviors/encoded_key_cache_behavior"
+require_relative "behaviors/local_cache_behavior"
diff --git a/activesupport/test/cache/behaviors/autoloading_cache_behavior.rb b/activesupport/test/cache/behaviors/autoloading_cache_behavior.rb
new file mode 100644
index 0000000000..b340eb6c48
--- /dev/null
+++ b/activesupport/test/cache/behaviors/autoloading_cache_behavior.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require "dependencies_test_helpers"
+
+module AutoloadingCacheBehavior
+ include DependenciesTestHelpers
+
+ def test_simple_autoloading
+ with_autoloading_fixtures do
+ @cache.write("foo", EM.new)
+ end
+
+ remove_constants(:EM)
+ ActiveSupport::Dependencies.clear
+
+ with_autoloading_fixtures do
+ assert_kind_of EM, @cache.read("foo")
+ end
+
+ remove_constants(:EM)
+ ActiveSupport::Dependencies.clear
+ end
+
+ def test_two_classes_autoloading
+ with_autoloading_fixtures do
+ @cache.write("foo", [EM.new, ClassFolder.new])
+ end
+
+ remove_constants(:EM, :ClassFolder)
+ ActiveSupport::Dependencies.clear
+
+ with_autoloading_fixtures do
+ loaded = @cache.read("foo")
+ assert_kind_of Array, loaded
+ assert_equal 2, loaded.size
+ assert_kind_of EM, loaded[0]
+ assert_kind_of ClassFolder, loaded[1]
+ end
+
+ remove_constants(:EM, :ClassFolder)
+ ActiveSupport::Dependencies.clear
+ end
+end
diff --git a/activesupport/test/cache/behaviors/cache_delete_matched_behavior.rb b/activesupport/test/cache/behaviors/cache_delete_matched_behavior.rb
new file mode 100644
index 0000000000..6f59ce48d2
--- /dev/null
+++ b/activesupport/test/cache/behaviors/cache_delete_matched_behavior.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module CacheDeleteMatchedBehavior
+ def test_delete_matched
+ @cache.write("foo", "bar")
+ @cache.write("fu", "baz")
+ @cache.write("foo/bar", "baz")
+ @cache.write("fu/baz", "bar")
+ @cache.delete_matched(/oo/)
+ assert !@cache.exist?("foo")
+ assert @cache.exist?("fu")
+ assert !@cache.exist?("foo/bar")
+ assert @cache.exist?("fu/baz")
+ end
+end
diff --git a/activesupport/test/cache/behaviors/cache_increment_decrement_behavior.rb b/activesupport/test/cache/behaviors/cache_increment_decrement_behavior.rb
new file mode 100644
index 0000000000..16b7abc679
--- /dev/null
+++ b/activesupport/test/cache/behaviors/cache_increment_decrement_behavior.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module CacheIncrementDecrementBehavior
+ def test_increment
+ @cache.write("foo", 1, raw: true)
+ assert_equal 1, @cache.read("foo").to_i
+ assert_equal 2, @cache.increment("foo")
+ assert_equal 2, @cache.read("foo").to_i
+ assert_equal 3, @cache.increment("foo")
+ assert_equal 3, @cache.read("foo").to_i
+
+ missing = @cache.increment("bar")
+ assert(missing.nil? || missing == 1)
+ end
+
+ def test_decrement
+ @cache.write("foo", 3, raw: true)
+ assert_equal 3, @cache.read("foo").to_i
+ assert_equal 2, @cache.decrement("foo")
+ assert_equal 2, @cache.read("foo").to_i
+ assert_equal 1, @cache.decrement("foo")
+ assert_equal 1, @cache.read("foo").to_i
+
+ missing = @cache.decrement("bar")
+ assert(missing.nil? || missing == -1)
+ end
+end
diff --git a/activesupport/test/cache/behaviors/cache_store_behavior.rb b/activesupport/test/cache/behaviors/cache_store_behavior.rb
new file mode 100644
index 0000000000..bdc689b8b4
--- /dev/null
+++ b/activesupport/test/cache/behaviors/cache_store_behavior.rb
@@ -0,0 +1,353 @@
+# frozen_string_literal: true
+
+# Tests the base functionality that should be identical across all cache stores.
+module CacheStoreBehavior
+ def test_should_read_and_write_strings
+ assert @cache.write("foo", "bar")
+ assert_equal "bar", @cache.read("foo")
+ end
+
+ def test_should_overwrite
+ @cache.write("foo", "bar")
+ @cache.write("foo", "baz")
+ assert_equal "baz", @cache.read("foo")
+ end
+
+ def test_fetch_without_cache_miss
+ @cache.write("foo", "bar")
+ assert_not_called(@cache, :write) do
+ assert_equal "bar", @cache.fetch("foo") { "baz" }
+ end
+ end
+
+ def test_fetch_with_cache_miss
+ assert_called_with(@cache, :write, ["foo", "baz", @cache.options]) do
+ assert_equal "baz", @cache.fetch("foo") { "baz" }
+ end
+ end
+
+ def test_fetch_with_cache_miss_passes_key_to_block
+ cache_miss = false
+ assert_equal 3, @cache.fetch("foo") { |key| cache_miss = true; key.length }
+ assert cache_miss
+
+ cache_miss = false
+ assert_equal 3, @cache.fetch("foo") { |key| cache_miss = true; key.length }
+ assert !cache_miss
+ end
+
+ def test_fetch_with_forced_cache_miss
+ @cache.write("foo", "bar")
+ assert_not_called(@cache, :read) do
+ assert_called_with(@cache, :write, ["foo", "bar", @cache.options.merge(force: true)]) do
+ @cache.fetch("foo", force: true) { "bar" }
+ end
+ end
+ end
+
+ def test_fetch_with_cached_nil
+ @cache.write("foo", nil)
+ assert_not_called(@cache, :write) do
+ assert_nil @cache.fetch("foo") { "baz" }
+ end
+ end
+
+ def test_fetch_with_forced_cache_miss_with_block
+ @cache.write("foo", "bar")
+ assert_equal "foo_bar", @cache.fetch("foo", force: true) { "foo_bar" }
+ end
+
+ def test_fetch_with_forced_cache_miss_without_block
+ @cache.write("foo", "bar")
+ assert_raises(ArgumentError) do
+ @cache.fetch("foo", force: true)
+ end
+
+ assert_equal "bar", @cache.read("foo")
+ end
+
+ def test_should_read_and_write_hash
+ assert @cache.write("foo", a: "b")
+ assert_equal({ a: "b" }, @cache.read("foo"))
+ end
+
+ def test_should_read_and_write_integer
+ assert @cache.write("foo", 1)
+ assert_equal 1, @cache.read("foo")
+ end
+
+ def test_should_read_and_write_nil
+ assert @cache.write("foo", nil)
+ assert_nil @cache.read("foo")
+ end
+
+ def test_should_read_and_write_false
+ assert @cache.write("foo", false)
+ assert_equal false, @cache.read("foo")
+ end
+
+ def test_read_multi
+ @cache.write("foo", "bar")
+ @cache.write("fu", "baz")
+ @cache.write("fud", "biz")
+ assert_equal({ "foo" => "bar", "fu" => "baz" }, @cache.read_multi("foo", "fu"))
+ end
+
+ def test_read_multi_with_expires
+ time = Time.now
+ @cache.write("foo", "bar", expires_in: 10)
+ @cache.write("fu", "baz")
+ @cache.write("fud", "biz")
+ Time.stub(:now, time + 11) do
+ assert_equal({ "fu" => "baz" }, @cache.read_multi("foo", "fu"))
+ end
+ end
+
+ def test_fetch_multi
+ @cache.write("foo", "bar")
+ @cache.write("fud", "biz")
+
+ values = @cache.fetch_multi("foo", "fu", "fud") { |value| value * 2 }
+
+ assert_equal({ "foo" => "bar", "fu" => "fufu", "fud" => "biz" }, values)
+ assert_equal("fufu", @cache.read("fu"))
+ end
+
+ def test_multi_with_objects
+ cache_struct = Struct.new(:cache_key, :title)
+ foo = cache_struct.new("foo", "FOO!")
+ bar = cache_struct.new("bar")
+
+ @cache.write("bar", "BAM!")
+
+ values = @cache.fetch_multi(foo, bar) { |object| object.title }
+
+ assert_equal({ foo => "FOO!", bar => "BAM!" }, values)
+ end
+
+ def test_fetch_multi_without_block
+ assert_raises(ArgumentError) do
+ @cache.fetch_multi("foo")
+ end
+ end
+
+ def test_read_and_write_compressed_small_data
+ @cache.write("foo", "bar", compress: true)
+ assert_equal "bar", @cache.read("foo")
+ end
+
+ def test_read_and_write_compressed_large_data
+ @cache.write("foo", "bar", compress: true, compress_threshold: 2)
+ assert_equal "bar", @cache.read("foo")
+ end
+
+ def test_read_and_write_compressed_nil
+ @cache.write("foo", nil, compress: true)
+ assert_nil @cache.read("foo")
+ end
+
+ def test_read_and_write_uncompressed_small_data
+ @cache.write("foo", "bar", compress: false)
+ assert_equal "bar", @cache.read("foo")
+ end
+
+ def test_read_and_write_uncompressed_nil
+ @cache.write("foo", nil, compress: false)
+ assert_nil @cache.read("foo")
+ end
+
+ def test_cache_key
+ obj = Object.new
+ def obj.cache_key
+ :foo
+ end
+ @cache.write(obj, "bar")
+ assert_equal "bar", @cache.read("foo")
+ end
+
+ def test_param_as_cache_key
+ obj = Object.new
+ def obj.to_param
+ "foo"
+ end
+ @cache.write(obj, "bar")
+ assert_equal "bar", @cache.read("foo")
+ end
+
+ def test_unversioned_cache_key
+ obj = Object.new
+ def obj.cache_key
+ "foo"
+ end
+ def obj.cache_key_with_version
+ "foo-v1"
+ end
+ @cache.write(obj, "bar")
+ assert_equal "bar", @cache.read("foo")
+ end
+
+ def test_array_as_cache_key
+ @cache.write([:fu, "foo"], "bar")
+ assert_equal "bar", @cache.read("fu/foo")
+ end
+
+ def test_hash_as_cache_key
+ @cache.write({ foo: 1, fu: 2 }, "bar")
+ assert_equal "bar", @cache.read("foo=1/fu=2")
+ end
+
+ def test_keys_are_case_sensitive
+ @cache.write("foo", "bar")
+ assert_nil @cache.read("FOO")
+ end
+
+ def test_exist
+ @cache.write("foo", "bar")
+ assert_equal true, @cache.exist?("foo")
+ assert_equal false, @cache.exist?("bar")
+ end
+
+ def test_nil_exist
+ @cache.write("foo", nil)
+ assert @cache.exist?("foo")
+ end
+
+ def test_delete
+ @cache.write("foo", "bar")
+ assert @cache.exist?("foo")
+ assert @cache.delete("foo")
+ assert !@cache.exist?("foo")
+ end
+
+ def test_original_store_objects_should_not_be_immutable
+ bar = "bar".dup
+ @cache.write("foo", bar)
+ assert_nothing_raised { bar.gsub!(/.*/, "baz") }
+ end
+
+ def test_expires_in
+ time = Time.local(2008, 4, 24)
+
+ Time.stub(:now, time) do
+ @cache.write("foo", "bar")
+ assert_equal "bar", @cache.read("foo")
+ end
+
+ Time.stub(:now, time + 30) do
+ assert_equal "bar", @cache.read("foo")
+ end
+
+ Time.stub(:now, time + 61) do
+ assert_nil @cache.read("foo")
+ end
+ end
+
+ def test_race_condition_protection_skipped_if_not_defined
+ @cache.write("foo", "bar")
+ time = @cache.send(:read_entry, @cache.send(:normalize_key, "foo", {}), {}).expires_at
+
+ Time.stub(:now, Time.at(time)) do
+ result = @cache.fetch("foo") do
+ assert_nil @cache.read("foo")
+ "baz"
+ end
+ assert_equal "baz", result
+ end
+ end
+
+ def test_race_condition_protection_is_limited
+ time = Time.now
+ @cache.write("foo", "bar", expires_in: 60)
+ Time.stub(:now, time + 71) do
+ result = @cache.fetch("foo", race_condition_ttl: 10) do
+ assert_nil @cache.read("foo")
+ "baz"
+ end
+ assert_equal "baz", result
+ end
+ end
+
+ def test_race_condition_protection_is_safe
+ time = Time.now
+ @cache.write("foo", "bar", expires_in: 60)
+ Time.stub(:now, time + 61) do
+ begin
+ @cache.fetch("foo", race_condition_ttl: 10) do
+ assert_equal "bar", @cache.read("foo")
+ raise ArgumentError.new
+ end
+ rescue ArgumentError
+ end
+ assert_equal "bar", @cache.read("foo")
+ end
+ Time.stub(:now, time + 91) do
+ assert_nil @cache.read("foo")
+ end
+ end
+
+ def test_race_condition_protection
+ time = Time.now
+ @cache.write("foo", "bar", expires_in: 60)
+ Time.stub(:now, time + 61) do
+ result = @cache.fetch("foo", race_condition_ttl: 10) do
+ assert_equal "bar", @cache.read("foo")
+ "baz"
+ end
+ assert_equal "baz", result
+ end
+ end
+
+ def test_crazy_key_characters
+ crazy_key = "#/:*(<+=> )&$%@?;'\"\'`~-"
+ assert @cache.write(crazy_key, "1", raw: true)
+ assert_equal "1", @cache.read(crazy_key)
+ assert_equal "1", @cache.fetch(crazy_key)
+ assert @cache.delete(crazy_key)
+ assert_equal "2", @cache.fetch(crazy_key, raw: true) { "2" }
+ assert_equal 3, @cache.increment(crazy_key)
+ assert_equal 2, @cache.decrement(crazy_key)
+ end
+
+ def test_really_long_keys
+ key = "".dup
+ 900.times { key << "x" }
+ assert @cache.write(key, "bar")
+ assert_equal "bar", @cache.read(key)
+ assert_equal "bar", @cache.fetch(key)
+ assert_nil @cache.read("#{key}x")
+ assert_equal({ key => "bar" }, @cache.read_multi(key))
+ assert @cache.delete(key)
+ end
+
+ def test_cache_hit_instrumentation
+ key = "test_key"
+ @events = []
+ ActiveSupport::Notifications.subscribe "cache_read.active_support" do |*args|
+ @events << ActiveSupport::Notifications::Event.new(*args)
+ end
+ assert @cache.write(key, "1", raw: true)
+ assert @cache.fetch(key) {}
+ assert_equal 1, @events.length
+ assert_equal "cache_read.active_support", @events[0].name
+ assert_equal :fetch, @events[0].payload[:super_operation]
+ assert @events[0].payload[:hit]
+ ensure
+ ActiveSupport::Notifications.unsubscribe "cache_read.active_support"
+ end
+
+ def test_cache_miss_instrumentation
+ @events = []
+ ActiveSupport::Notifications.subscribe(/^cache_(.*)\.active_support$/) do |*args|
+ @events << ActiveSupport::Notifications::Event.new(*args)
+ end
+ assert_not @cache.fetch("bad_key") {}
+ assert_equal 3, @events.length
+ assert_equal "cache_read.active_support", @events[0].name
+ assert_equal "cache_generate.active_support", @events[1].name
+ assert_equal "cache_write.active_support", @events[2].name
+ assert_equal :fetch, @events[0].payload[:super_operation]
+ assert_not @events[0].payload[:hit]
+ ensure
+ ActiveSupport::Notifications.unsubscribe "cache_read.active_support"
+ end
+end
diff --git a/activesupport/test/cache/behaviors/cache_store_version_behavior.rb b/activesupport/test/cache/behaviors/cache_store_version_behavior.rb
new file mode 100644
index 0000000000..c2e4d046af
--- /dev/null
+++ b/activesupport/test/cache/behaviors/cache_store_version_behavior.rb
@@ -0,0 +1,88 @@
+# frozen_string_literal: true
+
+module CacheStoreVersionBehavior
+ ModelWithKeyAndVersion = Struct.new(:cache_key, :cache_version)
+
+ def test_fetch_with_right_version_should_hit
+ @cache.fetch("foo", version: 1) { "bar" }
+ assert_equal "bar", @cache.read("foo", version: 1)
+ end
+
+ def test_fetch_with_wrong_version_should_miss
+ @cache.fetch("foo", version: 1) { "bar" }
+ assert_nil @cache.read("foo", version: 2)
+ end
+
+ def test_read_with_right_version_should_hit
+ @cache.write("foo", "bar", version: 1)
+ assert_equal "bar", @cache.read("foo", version: 1)
+ end
+
+ def test_read_with_wrong_version_should_miss
+ @cache.write("foo", "bar", version: 1)
+ assert_nil @cache.read("foo", version: 2)
+ end
+
+ def test_exist_with_right_version_should_be_true
+ @cache.write("foo", "bar", version: 1)
+ assert @cache.exist?("foo", version: 1)
+ end
+
+ def test_exist_with_wrong_version_should_be_false
+ @cache.write("foo", "bar", version: 1)
+ assert !@cache.exist?("foo", version: 2)
+ end
+
+ def test_reading_and_writing_with_model_supporting_cache_version
+ m1v1 = ModelWithKeyAndVersion.new("model/1", 1)
+ m1v2 = ModelWithKeyAndVersion.new("model/1", 2)
+
+ @cache.write(m1v1, "bar")
+ assert_equal "bar", @cache.read(m1v1)
+ assert_nil @cache.read(m1v2)
+ end
+
+ def test_reading_and_writing_with_model_supporting_cache_version_using_nested_key
+ m1v1 = ModelWithKeyAndVersion.new("model/1", 1)
+ m1v2 = ModelWithKeyAndVersion.new("model/1", 2)
+
+ @cache.write([ "something", m1v1 ], "bar")
+ assert_equal "bar", @cache.read([ "something", m1v1 ])
+ assert_nil @cache.read([ "something", m1v2 ])
+ end
+
+ def test_fetching_with_model_supporting_cache_version
+ m1v1 = ModelWithKeyAndVersion.new("model/1", 1)
+ m1v2 = ModelWithKeyAndVersion.new("model/1", 2)
+
+ @cache.fetch(m1v1) { "bar" }
+ assert_equal "bar", @cache.fetch(m1v1) { "bu" }
+ assert_equal "bu", @cache.fetch(m1v2) { "bu" }
+ end
+
+ def test_exist_with_model_supporting_cache_version
+ m1v1 = ModelWithKeyAndVersion.new("model/1", 1)
+ m1v2 = ModelWithKeyAndVersion.new("model/1", 2)
+
+ @cache.write(m1v1, "bar")
+ assert @cache.exist?(m1v1)
+ assert_not @cache.fetch(m1v2)
+ end
+
+ def test_fetch_multi_with_model_supporting_cache_version
+ m1v1 = ModelWithKeyAndVersion.new("model/1", 1)
+ m2v1 = ModelWithKeyAndVersion.new("model/2", 1)
+ m2v2 = ModelWithKeyAndVersion.new("model/2", 2)
+
+ first_fetch_values = @cache.fetch_multi(m1v1, m2v1) { |m| m.cache_key }
+ second_fetch_values = @cache.fetch_multi(m1v1, m2v2) { |m| m.cache_key + " 2nd" }
+
+ assert_equal({ m1v1 => "model/1", m2v1 => "model/2" }, first_fetch_values)
+ assert_equal({ m1v1 => "model/1", m2v2 => "model/2 2nd" }, second_fetch_values)
+ end
+
+ def test_version_is_normalized
+ @cache.write("foo", "bar", version: 1)
+ assert_equal "bar", @cache.read("foo", version: "1")
+ end
+end
diff --git a/activesupport/test/cache/behaviors/encoded_key_cache_behavior.rb b/activesupport/test/cache/behaviors/encoded_key_cache_behavior.rb
new file mode 100644
index 0000000000..da16142496
--- /dev/null
+++ b/activesupport/test/cache/behaviors/encoded_key_cache_behavior.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+# https://rails.lighthouseapp.com/projects/8994/tickets/6225-memcachestore-cant-deal-with-umlauts-and-special-characters
+# The error is caused by character encodings that can't be compared with ASCII-8BIT regular expressions and by special
+# characters like the umlaut in UTF-8.
+module EncodedKeyCacheBehavior
+ Encoding.list.each do |encoding|
+ define_method "test_#{encoding.name.underscore}_encoded_values" do
+ key = "foo".dup.force_encoding(encoding)
+ assert @cache.write(key, "1", raw: true)
+ assert_equal "1", @cache.read(key)
+ assert_equal "1", @cache.fetch(key)
+ assert @cache.delete(key)
+ assert_equal "2", @cache.fetch(key, raw: true) { "2" }
+ assert_equal 3, @cache.increment(key)
+ assert_equal 2, @cache.decrement(key)
+ end
+ end
+
+ def test_common_utf8_values
+ key = "\xC3\xBCmlaut".dup.force_encoding(Encoding::UTF_8)
+ assert @cache.write(key, "1", raw: true)
+ assert_equal "1", @cache.read(key)
+ assert_equal "1", @cache.fetch(key)
+ assert @cache.delete(key)
+ assert_equal "2", @cache.fetch(key, raw: true) { "2" }
+ assert_equal 3, @cache.increment(key)
+ assert_equal 2, @cache.decrement(key)
+ end
+
+ def test_retains_encoding
+ key = "\xC3\xBCmlaut".dup.force_encoding(Encoding::UTF_8)
+ assert @cache.write(key, "1", raw: true)
+ assert_equal Encoding::UTF_8, key.encoding
+ end
+end
diff --git a/activesupport/test/cache/behaviors/local_cache_behavior.rb b/activesupport/test/cache/behaviors/local_cache_behavior.rb
new file mode 100644
index 0000000000..f7302df4c8
--- /dev/null
+++ b/activesupport/test/cache/behaviors/local_cache_behavior.rb
@@ -0,0 +1,132 @@
+# frozen_string_literal: true
+
+module LocalCacheBehavior
+ def test_local_writes_are_persistent_on_the_remote_cache
+ retval = @cache.with_local_cache do
+ @cache.write("foo", "bar")
+ end
+ assert retval
+ assert_equal "bar", @cache.read("foo")
+ end
+
+ def test_clear_also_clears_local_cache
+ @cache.with_local_cache do
+ @cache.write("foo", "bar")
+ @cache.clear
+ assert_nil @cache.read("foo")
+ end
+
+ assert_nil @cache.read("foo")
+ end
+
+ def test_cleanup_clears_local_cache_but_not_remote_cache
+ begin
+ @cache.cleanup
+ rescue NotImplementedError
+ skip
+ end
+
+ @cache.with_local_cache do
+ @cache.write("foo", "bar")
+ assert_equal "bar", @cache.read("foo")
+
+ @cache.send(:bypass_local_cache) { @cache.write("foo", "baz") }
+ assert_equal "bar", @cache.read("foo")
+
+ @cache.cleanup
+ assert_equal "baz", @cache.read("foo")
+ end
+ end
+
+ def test_local_cache_of_write
+ @cache.with_local_cache do
+ @cache.write("foo", "bar")
+ @peek.delete("foo")
+ assert_equal "bar", @cache.read("foo")
+ end
+ end
+
+ def test_local_cache_of_read
+ @cache.write("foo", "bar")
+ @cache.with_local_cache do
+ assert_equal "bar", @cache.read("foo")
+ end
+ end
+
+ def test_local_cache_of_read_nil
+ @cache.with_local_cache do
+ assert_nil @cache.read("foo")
+ @cache.send(:bypass_local_cache) { @cache.write "foo", "bar" }
+ assert_nil @cache.read("foo")
+ end
+ end
+
+ def test_local_cache_fetch
+ @cache.with_local_cache do
+ @cache.send(:local_cache).write "foo", "bar"
+ assert_equal "bar", @cache.send(:local_cache).fetch("foo")
+ end
+ end
+
+ def test_local_cache_of_write_nil
+ @cache.with_local_cache do
+ assert @cache.write("foo", nil)
+ assert_nil @cache.read("foo")
+ @peek.write("foo", "bar")
+ assert_nil @cache.read("foo")
+ end
+ end
+
+ def test_local_cache_of_write_with_unless_exist
+ @cache.with_local_cache do
+ @cache.write("foo", "bar")
+ @cache.write("foo", "baz", unless_exist: true)
+ assert_equal @peek.read("foo"), @cache.read("foo")
+ end
+ end
+
+ def test_local_cache_of_delete
+ @cache.with_local_cache do
+ @cache.write("foo", "bar")
+ @cache.delete("foo")
+ assert_nil @cache.read("foo")
+ end
+ end
+
+ def test_local_cache_of_exist
+ @cache.with_local_cache do
+ @cache.write("foo", "bar")
+ @peek.delete("foo")
+ assert @cache.exist?("foo")
+ end
+ end
+
+ def test_local_cache_of_increment
+ @cache.with_local_cache do
+ @cache.write("foo", 1, raw: true)
+ @peek.write("foo", 2, raw: true)
+ @cache.increment("foo")
+ assert_equal 3, @cache.read("foo")
+ end
+ end
+
+ def test_local_cache_of_decrement
+ @cache.with_local_cache do
+ @cache.write("foo", 1, raw: true)
+ @peek.write("foo", 3, raw: true)
+ @cache.decrement("foo")
+ assert_equal 2, @cache.read("foo")
+ end
+ end
+
+ def test_middleware
+ app = lambda { |env|
+ result = @cache.write("foo", "bar")
+ assert_equal "bar", @cache.read("foo") # make sure 'foo' was written
+ assert result
+ [200, {}, []]
+ }
+ app = @cache.middleware.new(app)
+ app.call({})
+ end
+end
diff --git a/activesupport/test/cache/cache_entry_test.rb b/activesupport/test/cache/cache_entry_test.rb
new file mode 100644
index 0000000000..80ff7ad564
--- /dev/null
+++ b/activesupport/test/cache/cache_entry_test.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+require "abstract_unit"
+require "active_support/cache"
+
+class CacheEntryTest < ActiveSupport::TestCase
+ def test_expired
+ entry = ActiveSupport::Cache::Entry.new("value")
+ assert !entry.expired?, "entry not expired"
+ entry = ActiveSupport::Cache::Entry.new("value", expires_in: 60)
+ assert !entry.expired?, "entry not expired"
+ Time.stub(:now, Time.now + 61) do
+ assert entry.expired?, "entry is expired"
+ end
+ end
+
+ def test_compressed_values
+ value = "value" * 100
+ entry = ActiveSupport::Cache::Entry.new(value, compress: true, compress_threshold: 1)
+ assert_equal value, entry.value
+ assert(value.bytesize > entry.size, "value is compressed")
+ end
+
+ def test_compressed_by_default
+ value = "value" * 100
+ entry = ActiveSupport::Cache::Entry.new(value, compress_threshold: 1)
+ assert_equal value, entry.value
+ assert(value.bytesize > entry.size, "value is compressed")
+ end
+
+ def test_uncompressed_values
+ value = "value" * 100
+ entry = ActiveSupport::Cache::Entry.new(value, compress: false)
+ assert_equal value, entry.value
+ assert_equal value.bytesize, entry.size
+ end
+end
diff --git a/activesupport/test/cache/cache_key_test.rb b/activesupport/test/cache/cache_key_test.rb
new file mode 100644
index 0000000000..84e656f504
--- /dev/null
+++ b/activesupport/test/cache/cache_key_test.rb
@@ -0,0 +1,90 @@
+# frozen_string_literal: true
+
+require "abstract_unit"
+require "active_support/cache"
+
+class CacheKeyTest < ActiveSupport::TestCase
+ def test_entry_legacy_optional_ivars
+ legacy = Class.new(ActiveSupport::Cache::Entry) do
+ def initialize(value, options = {})
+ @value = value
+ @expires_in = nil
+ @created_at = nil
+ super
+ end
+ end
+
+ entry = legacy.new "foo"
+ assert_equal "foo", entry.value
+ end
+
+ def test_expand_cache_key
+ assert_equal "1/2/true", ActiveSupport::Cache.expand_cache_key([1, "2", true])
+ assert_equal "name/1/2/true", ActiveSupport::Cache.expand_cache_key([1, "2", true], :name)
+ end
+
+ def test_expand_cache_key_with_rails_cache_id
+ with_env("RAILS_CACHE_ID" => "c99") do
+ assert_equal "c99/foo", ActiveSupport::Cache.expand_cache_key(:foo)
+ assert_equal "c99/foo", ActiveSupport::Cache.expand_cache_key([:foo])
+ assert_equal "c99/foo/bar", ActiveSupport::Cache.expand_cache_key([:foo, :bar])
+ assert_equal "nm/c99/foo", ActiveSupport::Cache.expand_cache_key(:foo, :nm)
+ assert_equal "nm/c99/foo", ActiveSupport::Cache.expand_cache_key([:foo], :nm)
+ assert_equal "nm/c99/foo/bar", ActiveSupport::Cache.expand_cache_key([:foo, :bar], :nm)
+ end
+ end
+
+ def test_expand_cache_key_with_rails_app_version
+ with_env("RAILS_APP_VERSION" => "rails3") do
+ assert_equal "rails3/foo", ActiveSupport::Cache.expand_cache_key(:foo)
+ end
+ end
+
+ def test_expand_cache_key_rails_cache_id_should_win_over_rails_app_version
+ with_env("RAILS_CACHE_ID" => "c99", "RAILS_APP_VERSION" => "rails3") do
+ assert_equal "c99/foo", ActiveSupport::Cache.expand_cache_key(:foo)
+ end
+ end
+
+ def test_expand_cache_key_respond_to_cache_key
+ key = "foo".dup
+ def key.cache_key
+ :foo_key
+ end
+ assert_equal "foo_key", ActiveSupport::Cache.expand_cache_key(key)
+ end
+
+ def test_expand_cache_key_array_with_something_that_responds_to_cache_key
+ key = "foo".dup
+ def key.cache_key
+ :foo_key
+ end
+ assert_equal "foo_key", ActiveSupport::Cache.expand_cache_key([key])
+ end
+
+ def test_expand_cache_key_of_nil
+ assert_equal "", ActiveSupport::Cache.expand_cache_key(nil)
+ end
+
+ def test_expand_cache_key_of_false
+ assert_equal "false", ActiveSupport::Cache.expand_cache_key(false)
+ end
+
+ def test_expand_cache_key_of_true
+ assert_equal "true", ActiveSupport::Cache.expand_cache_key(true)
+ end
+
+ def test_expand_cache_key_of_array_like_object
+ assert_equal "foo/bar/baz", ActiveSupport::Cache.expand_cache_key(%w{foo bar baz}.to_enum)
+ end
+
+ private
+
+ def with_env(kv)
+ old_values = {}
+ kv.each { |key, value| old_values[key], ENV[key] = ENV[key], value }
+ yield
+ ensure
+ old_values.each { |key, value| ENV[key] = value }
+ end
+end
diff --git a/activesupport/test/cache/cache_store_logger_test.rb b/activesupport/test/cache/cache_store_logger_test.rb
new file mode 100644
index 0000000000..1af6893cc9
--- /dev/null
+++ b/activesupport/test/cache/cache_store_logger_test.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+require "abstract_unit"
+require "active_support/cache"
+
+class CacheStoreLoggerTest < ActiveSupport::TestCase
+ def setup
+ @cache = ActiveSupport::Cache.lookup_store(:memory_store)
+
+ @buffer = StringIO.new
+ @cache.logger = ActiveSupport::Logger.new(@buffer)
+ end
+
+ def test_logging
+ @cache.fetch("foo") { "bar" }
+ assert @buffer.string.present?
+ end
+
+ def test_log_with_string_namespace
+ @cache.fetch("foo", namespace: "string_namespace") { "bar" }
+ assert_match %r{string_namespace:foo}, @buffer.string
+ end
+
+ def test_log_with_proc_namespace
+ proc = Proc.new do
+ "proc_namespace"
+ end
+ @cache.fetch("foo", namespace: proc) { "bar" }
+ assert_match %r{proc_namespace:foo}, @buffer.string
+ end
+
+ def test_mute_logging
+ @cache.mute { @cache.fetch("foo") { "bar" } }
+ assert @buffer.string.blank?
+ end
+end
diff --git a/activesupport/test/cache/cache_store_namespace_test.rb b/activesupport/test/cache/cache_store_namespace_test.rb
new file mode 100644
index 0000000000..b52a61c500
--- /dev/null
+++ b/activesupport/test/cache/cache_store_namespace_test.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require "abstract_unit"
+require "active_support/cache"
+
+class CacheStoreNamespaceTest < ActiveSupport::TestCase
+ def test_static_namespace
+ cache = ActiveSupport::Cache.lookup_store(:memory_store, namespace: "tester")
+ cache.write("foo", "bar")
+ assert_equal "bar", cache.read("foo")
+ assert_equal "bar", cache.instance_variable_get(:@data)["tester:foo"].value
+ end
+
+ def test_proc_namespace
+ test_val = "tester"
+ proc = lambda { test_val }
+ cache = ActiveSupport::Cache.lookup_store(:memory_store, namespace: proc)
+ cache.write("foo", "bar")
+ assert_equal "bar", cache.read("foo")
+ assert_equal "bar", cache.instance_variable_get(:@data)["tester:foo"].value
+ end
+
+ def test_delete_matched_key_start
+ cache = ActiveSupport::Cache.lookup_store(:memory_store, namespace: "tester")
+ cache.write("foo", "bar")
+ cache.write("fu", "baz")
+ cache.delete_matched(/^fo/)
+ assert !cache.exist?("foo")
+ assert cache.exist?("fu")
+ end
+
+ def test_delete_matched_key
+ cache = ActiveSupport::Cache.lookup_store(:memory_store, namespace: "foo")
+ cache.write("foo", "bar")
+ cache.write("fu", "baz")
+ cache.delete_matched(/OO/i)
+ assert !cache.exist?("foo")
+ assert cache.exist?("fu")
+ end
+end
diff --git a/activesupport/test/cache/cache_store_setting_test.rb b/activesupport/test/cache/cache_store_setting_test.rb
new file mode 100644
index 0000000000..368cb39f97
--- /dev/null
+++ b/activesupport/test/cache/cache_store_setting_test.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+require "abstract_unit"
+require "active_support/cache"
+require "dalli"
+
+class CacheStoreSettingTest < ActiveSupport::TestCase
+ def test_memory_store_gets_created_if_no_arguments_passed_to_lookup_store_method
+ store = ActiveSupport::Cache.lookup_store
+ assert_kind_of(ActiveSupport::Cache::MemoryStore, store)
+ end
+
+ def test_memory_store
+ store = ActiveSupport::Cache.lookup_store :memory_store
+ assert_kind_of(ActiveSupport::Cache::MemoryStore, store)
+ end
+
+ def test_file_fragment_cache_store
+ store = ActiveSupport::Cache.lookup_store :file_store, "/path/to/cache/directory"
+ assert_kind_of(ActiveSupport::Cache::FileStore, store)
+ assert_equal "/path/to/cache/directory", store.cache_path
+ end
+
+ def test_mem_cache_fragment_cache_store
+ assert_called_with(Dalli::Client, :new, [%w[localhost], {}]) do
+ store = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost"
+ assert_kind_of(ActiveSupport::Cache::MemCacheStore, store)
+ end
+ end
+
+ def test_mem_cache_fragment_cache_store_with_given_mem_cache
+ mem_cache = Dalli::Client.new
+ assert_not_called(Dalli::Client, :new) do
+ store = ActiveSupport::Cache.lookup_store :mem_cache_store, mem_cache
+ assert_kind_of(ActiveSupport::Cache::MemCacheStore, store)
+ end
+ end
+
+ def test_mem_cache_fragment_cache_store_with_not_dalli_client
+ assert_not_called(Dalli::Client, :new) do
+ memcache = Object.new
+ assert_raises(ArgumentError) do
+ ActiveSupport::Cache.lookup_store :mem_cache_store, memcache
+ end
+ end
+ end
+
+ def test_mem_cache_fragment_cache_store_with_multiple_servers
+ assert_called_with(Dalli::Client, :new, [%w[localhost 192.168.1.1], {}]) do
+ store = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost", "192.168.1.1"
+ assert_kind_of(ActiveSupport::Cache::MemCacheStore, store)
+ end
+ end
+
+ def test_mem_cache_fragment_cache_store_with_options
+ assert_called_with(Dalli::Client, :new, [%w[localhost 192.168.1.1], { timeout: 10 }]) do
+ store = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost", "192.168.1.1", namespace: "foo", timeout: 10
+ assert_kind_of(ActiveSupport::Cache::MemCacheStore, store)
+ assert_equal "foo", store.options[:namespace]
+ end
+ end
+
+ def test_object_assigned_fragment_cache_store
+ store = ActiveSupport::Cache.lookup_store ActiveSupport::Cache::FileStore.new("/path/to/cache/directory")
+ assert_kind_of(ActiveSupport::Cache::FileStore, store)
+ assert_equal "/path/to/cache/directory", store.cache_path
+ end
+end
diff --git a/activesupport/test/cache/cache_store_write_multi_test.rb b/activesupport/test/cache/cache_store_write_multi_test.rb
new file mode 100644
index 0000000000..5b6fd678c5
--- /dev/null
+++ b/activesupport/test/cache/cache_store_write_multi_test.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+require "abstract_unit"
+require "active_support/cache"
+
+class CacheStoreWriteMultiEntriesStoreProviderInterfaceTest < ActiveSupport::TestCase
+ setup do
+ @cache = ActiveSupport::Cache.lookup_store(:null_store)
+ end
+
+ test "fetch_multi uses write_multi_entries store provider interface" do
+ assert_called_with(@cache, :write_multi_entries) do
+ @cache.fetch_multi "a", "b", "c" do |key|
+ key * 2
+ end
+ end
+ end
+end
+
+class CacheStoreWriteMultiInstrumentationTest < ActiveSupport::TestCase
+ setup do
+ @cache = ActiveSupport::Cache.lookup_store(:null_store)
+ end
+
+ test "instrumentation" do
+ writes = { "a" => "aa", "b" => "bb" }
+
+ events = with_instrumentation "write_multi" do
+ @cache.write_multi(writes)
+ end
+
+ assert_equal %w[ cache_write_multi.active_support ], events.map(&:name)
+ assert_nil events[0].payload[:super_operation]
+ assert_equal({ "a" => "aa", "b" => "bb" }, events[0].payload[:key])
+ end
+
+ test "instrumentation with fetch_multi as super operation" do
+ skip "fetch_multi isn't instrumented yet"
+
+ events = with_instrumentation "write_multi" do
+ @cache.fetch_multi("a", "b") { |key| key * 2 }
+ end
+
+ assert_equal %w[ cache_write_multi.active_support ], events.map(&:name)
+ assert_nil events[0].payload[:super_operation]
+ assert !events[0].payload[:hit]
+ end
+
+ private
+ def with_instrumentation(method)
+ event_name = "cache_#{method}.active_support"
+
+ [].tap do |events|
+ ActiveSupport::Notifications.subscribe event_name do |*args|
+ events << ActiveSupport::Notifications::Event.new(*args)
+ end
+ yield
+ end
+ ensure
+ ActiveSupport::Notifications.unsubscribe event_name
+ end
+end
diff --git a/activesupport/test/cache/local_cache_middleware_test.rb b/activesupport/test/cache/local_cache_middleware_test.rb
new file mode 100644
index 0000000000..e59fae0b4c
--- /dev/null
+++ b/activesupport/test/cache/local_cache_middleware_test.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+require "abstract_unit"
+require "active_support/cache"
+
+module ActiveSupport
+ module Cache
+ module Strategy
+ module LocalCache
+ class MiddlewareTest < ActiveSupport::TestCase
+ def test_local_cache_cleared_on_close
+ key = "super awesome key"
+ assert_nil LocalCacheRegistry.cache_for key
+ middleware = Middleware.new("<3", key).new(->(env) {
+ assert LocalCacheRegistry.cache_for(key), "should have a cache"
+ [200, {}, []]
+ })
+ _, _, body = middleware.call({})
+ assert LocalCacheRegistry.cache_for(key), "should still have a cache"
+ body.each {}
+ assert LocalCacheRegistry.cache_for(key), "should still have a cache"
+ body.close
+ assert_nil LocalCacheRegistry.cache_for(key)
+ end
+
+ def test_local_cache_cleared_and_response_should_be_present_on_invalid_parameters_error
+ key = "super awesome key"
+ assert_nil LocalCacheRegistry.cache_for key
+ middleware = Middleware.new("<3", key).new(->(env) {
+ assert LocalCacheRegistry.cache_for(key), "should have a cache"
+ raise Rack::Utils::InvalidParameterError
+ })
+ response = middleware.call({})
+ assert response, "response should exist"
+ assert_nil LocalCacheRegistry.cache_for(key)
+ end
+
+ def test_local_cache_cleared_on_exception
+ key = "super awesome key"
+ assert_nil LocalCacheRegistry.cache_for key
+ middleware = Middleware.new("<3", key).new(->(env) {
+ assert LocalCacheRegistry.cache_for(key), "should have a cache"
+ raise
+ })
+ assert_raises(RuntimeError) { middleware.call({}) }
+ assert_nil LocalCacheRegistry.cache_for(key)
+ end
+
+ def test_local_cache_cleared_on_throw
+ key = "super awesome key"
+ assert_nil LocalCacheRegistry.cache_for key
+ middleware = Middleware.new("<3", key).new(->(env) {
+ assert LocalCacheRegistry.cache_for(key), "should have a cache"
+ throw :warden
+ })
+ assert_throws(:warden) { middleware.call({}) }
+ assert_nil LocalCacheRegistry.cache_for(key)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activesupport/test/cache/stores/file_store_test.rb b/activesupport/test/cache/stores/file_store_test.rb
new file mode 100644
index 0000000000..66231b0a82
--- /dev/null
+++ b/activesupport/test/cache/stores/file_store_test.rb
@@ -0,0 +1,131 @@
+# frozen_string_literal: true
+
+require "abstract_unit"
+require "active_support/cache"
+require_relative "../behaviors"
+require "pathname"
+
+class FileStoreTest < ActiveSupport::TestCase
+ def setup
+ Dir.mkdir(cache_dir) unless File.exist?(cache_dir)
+ @cache = ActiveSupport::Cache.lookup_store(:file_store, cache_dir, expires_in: 60)
+ @peek = ActiveSupport::Cache.lookup_store(:file_store, cache_dir, expires_in: 60)
+ @cache_with_pathname = ActiveSupport::Cache.lookup_store(:file_store, Pathname.new(cache_dir), expires_in: 60)
+
+ @buffer = StringIO.new
+ @cache.logger = ActiveSupport::Logger.new(@buffer)
+ end
+
+ def teardown
+ FileUtils.rm_r(cache_dir)
+ rescue Errno::ENOENT
+ end
+
+ def cache_dir
+ File.join(Dir.pwd, "tmp_cache")
+ end
+
+ include CacheStoreBehavior
+ include CacheStoreVersionBehavior
+ include LocalCacheBehavior
+ include CacheDeleteMatchedBehavior
+ include CacheIncrementDecrementBehavior
+ include AutoloadingCacheBehavior
+
+ def test_clear
+ gitkeep = File.join(cache_dir, ".gitkeep")
+ keep = File.join(cache_dir, ".keep")
+ FileUtils.touch([gitkeep, keep])
+ @cache.clear
+ assert File.exist?(gitkeep)
+ assert File.exist?(keep)
+ end
+
+ def test_clear_without_cache_dir
+ FileUtils.rm_r(cache_dir)
+ @cache.clear
+ end
+
+ def test_long_uri_encoded_keys
+ @cache.write("%" * 870, 1)
+ assert_equal 1, @cache.read("%" * 870)
+ end
+
+ def test_key_transformation
+ key = @cache.send(:normalize_key, "views/index?id=1", {})
+ assert_equal "views/index?id=1", @cache.send(:file_path_key, key)
+ end
+
+ def test_key_transformation_with_pathname
+ FileUtils.touch(File.join(cache_dir, "foo"))
+ key = @cache_with_pathname.send(:normalize_key, "views/index?id=1", {})
+ assert_equal "views/index?id=1", @cache_with_pathname.send(:file_path_key, key)
+ end
+
+ # Test that generated cache keys are short enough to have Tempfile stuff added to them and
+ # remain valid
+ def test_filename_max_size
+ key = "#{'A' * ActiveSupport::Cache::FileStore::FILENAME_MAX_SIZE}"
+ path = @cache.send(:normalize_key, key, {})
+ Dir::Tmpname.create(path) do |tmpname, n, opts|
+ assert File.basename(tmpname + ".lock").length <= 255, "Temp filename too long: #{File.basename(tmpname + '.lock').length}"
+ end
+ end
+
+ # Because file systems have a maximum filename size, filenames > max size should be split in to directories
+ # If filename is 'AAAAB', where max size is 4, the returned path should be AAAA/B
+ def test_key_transformation_max_filename_size
+ key = "#{'A' * ActiveSupport::Cache::FileStore::FILENAME_MAX_SIZE}B"
+ path = @cache.send(:normalize_key, key, {})
+ assert path.split("/").all? { |dir_name| dir_name.size <= ActiveSupport::Cache::FileStore::FILENAME_MAX_SIZE }
+ assert_equal "B", File.basename(path)
+ end
+
+ # If nothing has been stored in the cache, there is a chance the cache directory does not yet exist
+ # Ensure delete_matched gracefully handles this case
+ def test_delete_matched_when_cache_directory_does_not_exist
+ assert_nothing_raised do
+ ActiveSupport::Cache::FileStore.new("/test/cache/directory").delete_matched(/does_not_exist/)
+ end
+ end
+
+ def test_delete_does_not_delete_empty_parent_dir
+ sub_cache_dir = File.join(cache_dir, "subdir/")
+ sub_cache_store = ActiveSupport::Cache::FileStore.new(sub_cache_dir)
+ assert_nothing_raised do
+ assert sub_cache_store.write("foo", "bar")
+ assert sub_cache_store.delete("foo")
+ end
+ assert File.exist?(cache_dir), "Parent of top level cache dir was deleted!"
+ assert File.exist?(sub_cache_dir), "Top level cache dir was deleted!"
+ assert Dir.entries(sub_cache_dir).reject { |f| ActiveSupport::Cache::FileStore::EXCLUDED_DIRS.include?(f) }.empty?
+ end
+
+ def test_log_exception_when_cache_read_fails
+ File.stub(:exist?, -> { raise StandardError.new("failed") }) do
+ @cache.send(:read_entry, "winston", {})
+ assert @buffer.string.present?
+ end
+ end
+
+ def test_cleanup_removes_all_expired_entries
+ time = Time.now
+ @cache.write("foo", "bar", expires_in: 10)
+ @cache.write("baz", "qux")
+ @cache.write("quux", "corge", expires_in: 20)
+ Time.stub(:now, time + 15) do
+ @cache.cleanup
+ assert_not @cache.exist?("foo")
+ assert @cache.exist?("baz")
+ assert @cache.exist?("quux")
+ assert_equal 2, Dir.glob(File.join(cache_dir, "**")).size
+ end
+ end
+
+ def test_write_with_unless_exist
+ assert_equal true, @cache.write(1, "aaaaaaaaaa")
+ assert_equal false, @cache.write(1, "aaaaaaaaaa", unless_exist: true)
+ @cache.write(1, nil)
+ assert_equal false, @cache.write(1, "aaaaaaaaaa", unless_exist: true)
+ end
+end
diff --git a/activesupport/test/cache/stores/mem_cache_store_test.rb b/activesupport/test/cache/stores/mem_cache_store_test.rb
new file mode 100644
index 0000000000..99624caf8a
--- /dev/null
+++ b/activesupport/test/cache/stores/mem_cache_store_test.rb
@@ -0,0 +1,92 @@
+# frozen_string_literal: true
+
+require "abstract_unit"
+require "active_support/cache"
+require_relative "../behaviors"
+require "dalli"
+
+class MemCacheStoreTest < ActiveSupport::TestCase
+ begin
+ ss = Dalli::Client.new("localhost:11211").stats
+ raise Dalli::DalliError unless ss["localhost:11211"]
+
+ MEMCACHE_UP = true
+ rescue Dalli::DalliError
+ $stderr.puts "Skipping memcached tests. Start memcached and try again."
+ MEMCACHE_UP = false
+ end
+
+ def setup
+ skip "memcache server is not up" unless MEMCACHE_UP
+
+ @cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, expires_in: 60)
+ @peek = ActiveSupport::Cache.lookup_store(:mem_cache_store)
+ @data = @cache.instance_variable_get(:@data)
+ @cache.clear
+ @cache.silence!
+ @cache.logger = ActiveSupport::Logger.new(File::NULL)
+ end
+
+ include CacheStoreBehavior
+ include CacheStoreVersionBehavior
+ include LocalCacheBehavior
+ include CacheIncrementDecrementBehavior
+ include EncodedKeyCacheBehavior
+ include AutoloadingCacheBehavior
+
+ def test_raw_values
+ cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, raw: true)
+ cache.clear
+ cache.write("foo", 2)
+ assert_equal "2", cache.read("foo")
+ end
+
+ def test_raw_values_with_marshal
+ cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, raw: true)
+ cache.clear
+ cache.write("foo", Marshal.dump([]))
+ assert_equal [], cache.read("foo")
+ end
+
+ def test_local_cache_raw_values
+ cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, raw: true)
+ cache.clear
+ cache.with_local_cache do
+ cache.write("foo", 2)
+ assert_equal "2", cache.read("foo")
+ end
+ end
+
+ def test_increment_expires_in
+ cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, raw: true)
+ cache.clear
+ assert_called_with cache.instance_variable_get(:@data), :incr, [ "foo", 1, 60 ] do
+ cache.increment("foo", 1, expires_in: 60)
+ end
+ end
+
+ def test_decrement_expires_in
+ cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, raw: true)
+ cache.clear
+ assert_called_with cache.instance_variable_get(:@data), :decr, [ "foo", 1, 60 ] do
+ cache.decrement("foo", 1, expires_in: 60)
+ end
+ end
+
+ def test_local_cache_raw_values_with_marshal
+ cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, raw: true)
+ cache.clear
+ cache.with_local_cache do
+ cache.write("foo", Marshal.dump([]))
+ assert_equal [], cache.read("foo")
+ end
+ end
+
+ def test_read_should_return_a_different_object_id_each_time_it_is_called
+ @cache.write("foo", "bar")
+ value = @cache.read("foo")
+ assert_not_equal value.object_id, @cache.read("foo").object_id
+ value << "bingo"
+ assert_not_equal value, @cache.read("foo")
+ end
+end
diff --git a/activesupport/test/cache/stores/memory_store_test.rb b/activesupport/test/cache/stores/memory_store_test.rb
new file mode 100644
index 0000000000..3981f05331
--- /dev/null
+++ b/activesupport/test/cache/stores/memory_store_test.rb
@@ -0,0 +1,109 @@
+# frozen_string_literal: true
+
+require "abstract_unit"
+require "active_support/cache"
+require_relative "../behaviors"
+
+class MemoryStoreTest < ActiveSupport::TestCase
+ def setup
+ @record_size = ActiveSupport::Cache.lookup_store(:memory_store).send(:cached_size, 1, ActiveSupport::Cache::Entry.new("aaaaaaaaaa"))
+ @cache = ActiveSupport::Cache.lookup_store(:memory_store, expires_in: 60, size: @record_size * 10 + 1)
+ end
+
+ include CacheStoreBehavior
+ include CacheStoreVersionBehavior
+ include CacheDeleteMatchedBehavior
+ include CacheIncrementDecrementBehavior
+
+ def test_prune_size
+ @cache.write(1, "aaaaaaaaaa") && sleep(0.001)
+ @cache.write(2, "bbbbbbbbbb") && sleep(0.001)
+ @cache.write(3, "cccccccccc") && sleep(0.001)
+ @cache.write(4, "dddddddddd") && sleep(0.001)
+ @cache.write(5, "eeeeeeeeee") && sleep(0.001)
+ @cache.read(2) && sleep(0.001)
+ @cache.read(4)
+ @cache.prune(@record_size * 3)
+ assert @cache.exist?(5)
+ assert @cache.exist?(4)
+ assert !@cache.exist?(3), "no entry"
+ assert @cache.exist?(2)
+ assert !@cache.exist?(1), "no entry"
+ end
+
+ def test_prune_size_on_write
+ @cache.write(1, "aaaaaaaaaa") && sleep(0.001)
+ @cache.write(2, "bbbbbbbbbb") && sleep(0.001)
+ @cache.write(3, "cccccccccc") && sleep(0.001)
+ @cache.write(4, "dddddddddd") && sleep(0.001)
+ @cache.write(5, "eeeeeeeeee") && sleep(0.001)
+ @cache.write(6, "ffffffffff") && sleep(0.001)
+ @cache.write(7, "gggggggggg") && sleep(0.001)
+ @cache.write(8, "hhhhhhhhhh") && sleep(0.001)
+ @cache.write(9, "iiiiiiiiii") && sleep(0.001)
+ @cache.write(10, "kkkkkkkkkk") && sleep(0.001)
+ @cache.read(2) && sleep(0.001)
+ @cache.read(4) && sleep(0.001)
+ @cache.write(11, "llllllllll")
+ assert @cache.exist?(11)
+ assert @cache.exist?(10)
+ assert @cache.exist?(9)
+ assert @cache.exist?(8)
+ assert @cache.exist?(7)
+ assert !@cache.exist?(6), "no entry"
+ assert !@cache.exist?(5), "no entry"
+ assert @cache.exist?(4)
+ assert !@cache.exist?(3), "no entry"
+ assert @cache.exist?(2)
+ assert !@cache.exist?(1), "no entry"
+ end
+
+ def test_prune_size_on_write_based_on_key_length
+ @cache.write(1, "aaaaaaaaaa") && sleep(0.001)
+ @cache.write(2, "bbbbbbbbbb") && sleep(0.001)
+ @cache.write(3, "cccccccccc") && sleep(0.001)
+ @cache.write(4, "dddddddddd") && sleep(0.001)
+ @cache.write(5, "eeeeeeeeee") && sleep(0.001)
+ @cache.write(6, "ffffffffff") && sleep(0.001)
+ @cache.write(7, "gggggggggg") && sleep(0.001)
+ @cache.write(8, "hhhhhhhhhh") && sleep(0.001)
+ @cache.write(9, "iiiiiiiiii") && sleep(0.001)
+ long_key = "*" * 2 * @record_size
+ @cache.write(long_key, "llllllllll")
+ assert @cache.exist?(long_key)
+ assert @cache.exist?(9)
+ assert @cache.exist?(8)
+ assert @cache.exist?(7)
+ assert @cache.exist?(6)
+ assert !@cache.exist?(5), "no entry"
+ assert !@cache.exist?(4), "no entry"
+ assert !@cache.exist?(3), "no entry"
+ assert !@cache.exist?(2), "no entry"
+ assert !@cache.exist?(1), "no entry"
+ end
+
+ def test_pruning_is_capped_at_a_max_time
+ def @cache.delete_entry(*args)
+ sleep(0.01)
+ super
+ end
+ @cache.write(1, "aaaaaaaaaa") && sleep(0.001)
+ @cache.write(2, "bbbbbbbbbb") && sleep(0.001)
+ @cache.write(3, "cccccccccc") && sleep(0.001)
+ @cache.write(4, "dddddddddd") && sleep(0.001)
+ @cache.write(5, "eeeeeeeeee") && sleep(0.001)
+ @cache.prune(30, 0.001)
+ assert @cache.exist?(5)
+ assert @cache.exist?(4)
+ assert @cache.exist?(3)
+ assert @cache.exist?(2)
+ assert !@cache.exist?(1)
+ end
+
+ def test_write_with_unless_exist
+ assert_equal true, @cache.write(1, "aaaaaaaaaa")
+ assert_equal false, @cache.write(1, "aaaaaaaaaa", unless_exist: true)
+ @cache.write(1, nil)
+ assert_equal false, @cache.write(1, "aaaaaaaaaa", unless_exist: true)
+ end
+end
diff --git a/activesupport/test/cache/stores/null_store_test.rb b/activesupport/test/cache/stores/null_store_test.rb
new file mode 100644
index 0000000000..a891cbffc8
--- /dev/null
+++ b/activesupport/test/cache/stores/null_store_test.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+require "abstract_unit"
+require "active_support/cache"
+require_relative "../behaviors"
+
+class NullStoreTest < ActiveSupport::TestCase
+ def setup
+ @cache = ActiveSupport::Cache.lookup_store(:null_store)
+ end
+
+ def test_clear
+ @cache.clear
+ end
+
+ def test_cleanup
+ @cache.cleanup
+ end
+
+ def test_write
+ assert_equal true, @cache.write("name", "value")
+ end
+
+ def test_read
+ @cache.write("name", "value")
+ assert_nil @cache.read("name")
+ end
+
+ def test_delete
+ @cache.write("name", "value")
+ assert_equal false, @cache.delete("name")
+ end
+
+ def test_increment
+ @cache.write("name", 1, raw: true)
+ assert_nil @cache.increment("name")
+ end
+
+ def test_decrement
+ @cache.write("name", 1, raw: true)
+ assert_nil @cache.increment("name")
+ end
+
+ def test_delete_matched
+ @cache.write("name", "value")
+ @cache.delete_matched(/name/)
+ end
+
+ def test_local_store_strategy
+ @cache.with_local_cache do
+ @cache.write("name", "value")
+ assert_equal "value", @cache.read("name")
+ @cache.delete("name")
+ assert_nil @cache.read("name")
+ @cache.write("name", "value")
+ end
+ assert_nil @cache.read("name")
+ end
+end
diff --git a/activesupport/test/cache/stores/redis_cache_store_test.rb b/activesupport/test/cache/stores/redis_cache_store_test.rb
new file mode 100644
index 0000000000..7f684f7a0f
--- /dev/null
+++ b/activesupport/test/cache/stores/redis_cache_store_test.rb
@@ -0,0 +1,151 @@
+# frozen_string_literal: true
+
+require "abstract_unit"
+require "active_support/cache"
+require "active_support/cache/redis_cache_store"
+require_relative "../behaviors"
+
+module ActiveSupport::Cache::RedisCacheStoreTests
+ class LookupTest < ActiveSupport::TestCase
+ test "may be looked up as :redis_cache_store" do
+ assert_kind_of ActiveSupport::Cache::RedisCacheStore,
+ ActiveSupport::Cache.lookup_store(:redis_cache_store)
+ end
+ end
+
+ class InitializationTest < ActiveSupport::TestCase
+ test "omitted URL uses Redis client with default settings" do
+ assert_called_with Redis, :new, [
+ url: nil,
+ connect_timeout: 20, read_timeout: 1, write_timeout: 1,
+ reconnect_attempts: 0,
+ ] do
+ build
+ end
+ end
+
+ test "no URLs uses Redis client with default settings" do
+ assert_called_with Redis, :new, [
+ url: nil,
+ connect_timeout: 20, read_timeout: 1, write_timeout: 1,
+ reconnect_attempts: 0,
+ ] do
+ build url: []
+ end
+ end
+
+ test "singular URL uses Redis client" do
+ assert_called_with Redis, :new, [
+ url: "redis://localhost:6379/0",
+ connect_timeout: 20, read_timeout: 1, write_timeout: 1,
+ reconnect_attempts: 0,
+ ] do
+ build url: "redis://localhost:6379/0"
+ end
+ end
+
+ test "one URL uses Redis client" do
+ assert_called_with Redis, :new, [
+ url: "redis://localhost:6379/0",
+ connect_timeout: 20, read_timeout: 1, write_timeout: 1,
+ reconnect_attempts: 0,
+ ] do
+ build url: %w[ redis://localhost:6379/0 ]
+ end
+ end
+
+ test "multiple URLs uses Redis::Distributed client" do
+ assert_called_with Redis, :new, [
+ [ url: "redis://localhost:6379/0",
+ connect_timeout: 20, read_timeout: 1, write_timeout: 1,
+ reconnect_attempts: 0 ],
+ [ url: "redis://localhost:6379/1",
+ connect_timeout: 20, read_timeout: 1, write_timeout: 1,
+ reconnect_attempts: 0 ],
+ ], returns: Redis.new do
+ @cache = build url: %w[ redis://localhost:6379/0 redis://localhost:6379/1 ]
+ assert_kind_of ::Redis::Distributed, @cache.redis
+ end
+ end
+
+ test "block argument uses yielded client" do
+ block = -> { :custom_redis_client }
+ assert_called block, :call do
+ build redis: block
+ end
+ end
+
+ private
+ def build(**kwargs)
+ ActiveSupport::Cache::RedisCacheStore.new(**kwargs).tap do |cache|
+ cache.redis
+ end
+ end
+ end
+
+ class StoreTest < ActiveSupport::TestCase
+ setup do
+ @namespace = "namespace"
+
+ @cache = ActiveSupport::Cache::RedisCacheStore.new(timeout: 0.1, namespace: @namespace, expires_in: 60)
+ # @cache.logger = Logger.new($stdout) # For test debugging
+
+ # For LocalCacheBehavior tests
+ @peek = ActiveSupport::Cache::RedisCacheStore.new(timeout: 0.1, namespace: @namespace)
+ end
+
+ teardown do
+ @cache.clear
+ @cache.redis.disconnect!
+ end
+ end
+
+ class RedisCacheStoreCommonBehaviorTest < StoreTest
+ include CacheStoreBehavior
+ include CacheStoreVersionBehavior
+ include LocalCacheBehavior
+ include CacheIncrementDecrementBehavior
+ include AutoloadingCacheBehavior
+ end
+
+ # Separate test class so we can omit the namespace which causes expected,
+ # appropriate complaints about incompatible string encodings.
+ class KeyEncodingSafetyTest < StoreTest
+ include EncodedKeyCacheBehavior
+
+ setup do
+ @cache = ActiveSupport::Cache::RedisCacheStore.new(timeout: 0.1)
+ @cache.logger = nil
+ end
+ end
+
+ class StoreAPITest < StoreTest
+ end
+
+ class FailureSafetyTest < StoreTest
+ test "fetch read failure returns nil" do
+ end
+
+ test "fetch read failure does not attempt to write" do
+ end
+
+ test "write failure returns nil" do
+ end
+ end
+
+ class DeleteMatchedTest < StoreTest
+ test "deletes keys matching glob" do
+ @cache.write("foo", "bar")
+ @cache.write("fu", "baz")
+ @cache.delete_matched("foo*")
+ assert !@cache.exist?("foo")
+ assert @cache.exist?("fu")
+ end
+
+ test "fails with regexp matchers" do
+ assert_raise ArgumentError do
+ @cache.delete_matched(/OO/i)
+ end
+ end
+ end
+end
diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb
deleted file mode 100644
index c543122d91..0000000000
--- a/activesupport/test/caching_test.rb
+++ /dev/null
@@ -1,1192 +0,0 @@
-require "logger"
-require "abstract_unit"
-require "active_support/cache"
-require "dependencies_test_helpers"
-
-require "pathname"
-
-module ActiveSupport
- module Cache
- module Strategy
- module LocalCache
- class MiddlewareTest < ActiveSupport::TestCase
- def test_local_cache_cleared_on_close
- key = "super awesome key"
- assert_nil LocalCacheRegistry.cache_for key
- middleware = Middleware.new("<3", key).new(->(env) {
- assert LocalCacheRegistry.cache_for(key), "should have a cache"
- [200, {}, []]
- })
- _, _, body = middleware.call({})
- assert LocalCacheRegistry.cache_for(key), "should still have a cache"
- body.each {}
- assert LocalCacheRegistry.cache_for(key), "should still have a cache"
- body.close
- assert_nil LocalCacheRegistry.cache_for(key)
- end
-
- def test_local_cache_cleared_and_response_should_be_present_on_invalid_parameters_error
- key = "super awesome key"
- assert_nil LocalCacheRegistry.cache_for key
- middleware = Middleware.new("<3", key).new(->(env) {
- assert LocalCacheRegistry.cache_for(key), "should have a cache"
- raise Rack::Utils::InvalidParameterError
- })
- response = middleware.call({})
- assert response, "response should exist"
- assert_nil LocalCacheRegistry.cache_for(key)
- end
-
- def test_local_cache_cleared_on_exception
- key = "super awesome key"
- assert_nil LocalCacheRegistry.cache_for key
- middleware = Middleware.new("<3", key).new(->(env) {
- assert LocalCacheRegistry.cache_for(key), "should have a cache"
- raise
- })
- assert_raises(RuntimeError) { middleware.call({}) }
- assert_nil LocalCacheRegistry.cache_for(key)
- end
- end
- end
- end
- end
-end
-
-class CacheKeyTest < ActiveSupport::TestCase
- def test_entry_legacy_optional_ivars
- legacy = Class.new(ActiveSupport::Cache::Entry) do
- def initialize(value, options = {})
- @value = value
- @expires_in = nil
- @created_at = nil
- super
- end
- end
-
- entry = legacy.new "foo"
- assert_equal "foo", entry.value
- end
-
- def test_expand_cache_key
- assert_equal "1/2/true", ActiveSupport::Cache.expand_cache_key([1, "2", true])
- assert_equal "name/1/2/true", ActiveSupport::Cache.expand_cache_key([1, "2", true], :name)
- end
-
- def test_expand_cache_key_with_rails_cache_id
- with_env("RAILS_CACHE_ID" => "c99") do
- assert_equal "c99/foo", ActiveSupport::Cache.expand_cache_key(:foo)
- assert_equal "c99/foo", ActiveSupport::Cache.expand_cache_key([:foo])
- assert_equal "c99/foo/bar", ActiveSupport::Cache.expand_cache_key([:foo, :bar])
- assert_equal "nm/c99/foo", ActiveSupport::Cache.expand_cache_key(:foo, :nm)
- assert_equal "nm/c99/foo", ActiveSupport::Cache.expand_cache_key([:foo], :nm)
- assert_equal "nm/c99/foo/bar", ActiveSupport::Cache.expand_cache_key([:foo, :bar], :nm)
- end
- end
-
- def test_expand_cache_key_with_rails_app_version
- with_env("RAILS_APP_VERSION" => "rails3") do
- assert_equal "rails3/foo", ActiveSupport::Cache.expand_cache_key(:foo)
- end
- end
-
- def test_expand_cache_key_rails_cache_id_should_win_over_rails_app_version
- with_env("RAILS_CACHE_ID" => "c99", "RAILS_APP_VERSION" => "rails3") do
- assert_equal "c99/foo", ActiveSupport::Cache.expand_cache_key(:foo)
- end
- end
-
- def test_expand_cache_key_respond_to_cache_key
- key = "foo"
- def key.cache_key
- :foo_key
- end
- assert_equal "foo_key", ActiveSupport::Cache.expand_cache_key(key)
- end
-
- def test_expand_cache_key_array_with_something_that_responds_to_cache_key
- key = "foo"
- def key.cache_key
- :foo_key
- end
- assert_equal "foo_key", ActiveSupport::Cache.expand_cache_key([key])
- end
-
- def test_expand_cache_key_of_nil
- assert_equal "", ActiveSupport::Cache.expand_cache_key(nil)
- end
-
- def test_expand_cache_key_of_false
- assert_equal "false", ActiveSupport::Cache.expand_cache_key(false)
- end
-
- def test_expand_cache_key_of_true
- assert_equal "true", ActiveSupport::Cache.expand_cache_key(true)
- end
-
- def test_expand_cache_key_of_array_like_object
- assert_equal "foo/bar/baz", ActiveSupport::Cache.expand_cache_key(%w{foo bar baz}.to_enum)
- end
-
- private
-
- def with_env(kv)
- old_values = {}
- kv.each { |key, value| old_values[key], ENV[key] = ENV[key], value }
- yield
- ensure
- old_values.each { |key, value| ENV[key] = value }
- end
-end
-
-class CacheStoreSettingTest < ActiveSupport::TestCase
- def test_memory_store_gets_created_if_no_arguments_passed_to_lookup_store_method
- store = ActiveSupport::Cache.lookup_store
- assert_kind_of(ActiveSupport::Cache::MemoryStore, store)
- end
-
- def test_memory_store
- store = ActiveSupport::Cache.lookup_store :memory_store
- assert_kind_of(ActiveSupport::Cache::MemoryStore, store)
- end
-
- def test_file_fragment_cache_store
- store = ActiveSupport::Cache.lookup_store :file_store, "/path/to/cache/directory"
- assert_kind_of(ActiveSupport::Cache::FileStore, store)
- assert_equal "/path/to/cache/directory", store.cache_path
- end
-
- def test_mem_cache_fragment_cache_store
- assert_called_with(Dalli::Client, :new, [%w[localhost], {}]) do
- store = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost"
- assert_kind_of(ActiveSupport::Cache::MemCacheStore, store)
- end
- end
-
- def test_mem_cache_fragment_cache_store_with_given_mem_cache
- mem_cache = Dalli::Client.new
- assert_not_called(Dalli::Client, :new) do
- store = ActiveSupport::Cache.lookup_store :mem_cache_store, mem_cache
- assert_kind_of(ActiveSupport::Cache::MemCacheStore, store)
- end
- end
-
- def test_mem_cache_fragment_cache_store_with_not_dalli_client
- assert_not_called(Dalli::Client, :new) do
- memcache = Object.new
- assert_raises(ArgumentError) do
- ActiveSupport::Cache.lookup_store :mem_cache_store, memcache
- end
- end
- end
-
- def test_mem_cache_fragment_cache_store_with_multiple_servers
- assert_called_with(Dalli::Client, :new, [%w[localhost 192.168.1.1], {}]) do
- store = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost", "192.168.1.1"
- assert_kind_of(ActiveSupport::Cache::MemCacheStore, store)
- end
- end
-
- def test_mem_cache_fragment_cache_store_with_options
- assert_called_with(Dalli::Client, :new, [%w[localhost 192.168.1.1], { timeout: 10 }]) do
- store = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost", "192.168.1.1", namespace: "foo", timeout: 10
- assert_kind_of(ActiveSupport::Cache::MemCacheStore, store)
- assert_equal "foo", store.options[:namespace]
- end
- end
-
- def test_object_assigned_fragment_cache_store
- store = ActiveSupport::Cache.lookup_store ActiveSupport::Cache::FileStore.new("/path/to/cache/directory")
- assert_kind_of(ActiveSupport::Cache::FileStore, store)
- assert_equal "/path/to/cache/directory", store.cache_path
- end
-end
-
-class CacheStoreNamespaceTest < ActiveSupport::TestCase
- def test_static_namespace
- cache = ActiveSupport::Cache.lookup_store(:memory_store, namespace: "tester")
- cache.write("foo", "bar")
- assert_equal "bar", cache.read("foo")
- assert_equal "bar", cache.instance_variable_get(:@data)["tester:foo"].value
- end
-
- def test_proc_namespace
- test_val = "tester"
- proc = lambda { test_val }
- cache = ActiveSupport::Cache.lookup_store(:memory_store, namespace: proc)
- cache.write("foo", "bar")
- assert_equal "bar", cache.read("foo")
- assert_equal "bar", cache.instance_variable_get(:@data)["tester:foo"].value
- end
-
- def test_delete_matched_key_start
- cache = ActiveSupport::Cache.lookup_store(:memory_store, namespace: "tester")
- cache.write("foo", "bar")
- cache.write("fu", "baz")
- cache.delete_matched(/^fo/)
- assert !cache.exist?("foo")
- assert cache.exist?("fu")
- end
-
- def test_delete_matched_key
- cache = ActiveSupport::Cache.lookup_store(:memory_store, namespace: "foo")
- cache.write("foo", "bar")
- cache.write("fu", "baz")
- cache.delete_matched(/OO/i)
- assert !cache.exist?("foo")
- assert cache.exist?("fu")
- end
-end
-
-# Tests the base functionality that should be identical across all cache stores.
-module CacheStoreBehavior
- def test_should_read_and_write_strings
- assert @cache.write("foo", "bar")
- assert_equal "bar", @cache.read("foo")
- end
-
- def test_should_overwrite
- @cache.write("foo", "bar")
- @cache.write("foo", "baz")
- assert_equal "baz", @cache.read("foo")
- end
-
- def test_fetch_without_cache_miss
- @cache.write("foo", "bar")
- assert_not_called(@cache, :write) do
- assert_equal "bar", @cache.fetch("foo") { "baz" }
- end
- end
-
- def test_fetch_with_cache_miss
- assert_called_with(@cache, :write, ["foo", "baz", @cache.options]) do
- assert_equal "baz", @cache.fetch("foo") { "baz" }
- end
- end
-
- def test_fetch_with_cache_miss_passes_key_to_block
- cache_miss = false
- assert_equal 3, @cache.fetch("foo") { |key| cache_miss = true; key.length }
- assert cache_miss
-
- cache_miss = false
- assert_equal 3, @cache.fetch("foo") { |key| cache_miss = true; key.length }
- assert !cache_miss
- end
-
- def test_fetch_with_forced_cache_miss
- @cache.write("foo", "bar")
- assert_not_called(@cache, :read) do
- assert_called_with(@cache, :write, ["foo", "bar", @cache.options.merge(force: true)]) do
- @cache.fetch("foo", force: true) { "bar" }
- end
- end
- end
-
- def test_fetch_with_cached_nil
- @cache.write("foo", nil)
- assert_not_called(@cache, :write) do
- assert_nil @cache.fetch("foo") { "baz" }
- end
- end
-
- def test_fetch_with_forced_cache_miss_with_block
- @cache.write("foo", "bar")
- assert_equal "foo_bar", @cache.fetch("foo", force: true) { "foo_bar" }
- end
-
- def test_fetch_with_forced_cache_miss_without_block
- @cache.write("foo", "bar")
- assert_raises(ArgumentError) do
- @cache.fetch("foo", force: true)
- end
-
- assert_equal "bar", @cache.read("foo")
- end
-
- def test_should_read_and_write_hash
- assert @cache.write("foo", a: "b")
- assert_equal({ a: "b" }, @cache.read("foo"))
- end
-
- def test_should_read_and_write_integer
- assert @cache.write("foo", 1)
- assert_equal 1, @cache.read("foo")
- end
-
- def test_should_read_and_write_nil
- assert @cache.write("foo", nil)
- assert_nil @cache.read("foo")
- end
-
- def test_should_read_and_write_false
- assert @cache.write("foo", false)
- assert_equal false, @cache.read("foo")
- end
-
- def test_read_multi
- @cache.write("foo", "bar")
- @cache.write("fu", "baz")
- @cache.write("fud", "biz")
- assert_equal({ "foo" => "bar", "fu" => "baz" }, @cache.read_multi("foo", "fu"))
- end
-
- def test_read_multi_with_expires
- time = Time.now
- @cache.write("foo", "bar", expires_in: 10)
- @cache.write("fu", "baz")
- @cache.write("fud", "biz")
- Time.stub(:now, time + 11) do
- assert_equal({ "fu" => "baz" }, @cache.read_multi("foo", "fu"))
- end
- end
-
- def test_fetch_multi
- @cache.write("foo", "bar")
- @cache.write("fud", "biz")
-
- values = @cache.fetch_multi("foo", "fu", "fud") { |value| value * 2 }
-
- assert_equal({ "foo" => "bar", "fu" => "fufu", "fud" => "biz" }, values)
- assert_equal("fufu", @cache.read("fu"))
- end
-
- def test_multi_with_objects
- cache_struct = Struct.new(:cache_key, :title)
- foo = cache_struct.new("foo", "FOO!")
- bar = cache_struct.new("bar")
-
- @cache.write("bar", "BAM!")
-
- values = @cache.fetch_multi(foo, bar) { |object| object.title }
-
- assert_equal({ foo => "FOO!", bar => "BAM!" }, values)
- end
-
- def test_fetch_multi_without_block
- assert_raises(ArgumentError) do
- @cache.fetch_multi("foo")
- end
- end
-
- def test_read_and_write_compressed_small_data
- @cache.write("foo", "bar", compress: true)
- assert_equal "bar", @cache.read("foo")
- end
-
- def test_read_and_write_compressed_large_data
- @cache.write("foo", "bar", compress: true, compress_threshold: 2)
- assert_equal "bar", @cache.read("foo")
- end
-
- def test_read_and_write_compressed_nil
- @cache.write("foo", nil, compress: true)
- assert_nil @cache.read("foo")
- end
-
- def test_cache_key
- obj = Object.new
- def obj.cache_key
- :foo
- end
- @cache.write(obj, "bar")
- assert_equal "bar", @cache.read("foo")
- end
-
- def test_param_as_cache_key
- obj = Object.new
- def obj.to_param
- "foo"
- end
- @cache.write(obj, "bar")
- assert_equal "bar", @cache.read("foo")
- end
-
- def test_array_as_cache_key
- @cache.write([:fu, "foo"], "bar")
- assert_equal "bar", @cache.read("fu/foo")
- end
-
- def test_hash_as_cache_key
- @cache.write({ foo: 1, fu: 2 }, "bar")
- assert_equal "bar", @cache.read("foo=1/fu=2")
- end
-
- def test_keys_are_case_sensitive
- @cache.write("foo", "bar")
- assert_nil @cache.read("FOO")
- end
-
- def test_exist
- @cache.write("foo", "bar")
- assert_equal true, @cache.exist?("foo")
- assert_equal false, @cache.exist?("bar")
- end
-
- def test_nil_exist
- @cache.write("foo", nil)
- assert @cache.exist?("foo")
- end
-
- def test_delete
- @cache.write("foo", "bar")
- assert @cache.exist?("foo")
- assert @cache.delete("foo")
- assert !@cache.exist?("foo")
- end
-
- def test_original_store_objects_should_not_be_immutable
- bar = "bar"
- @cache.write("foo", bar)
- assert_nothing_raised { bar.gsub!(/.*/, "baz") }
- end
-
- def test_expires_in
- time = Time.local(2008, 4, 24)
-
- Time.stub(:now, time) do
- @cache.write("foo", "bar")
- assert_equal "bar", @cache.read("foo")
- end
-
- Time.stub(:now, time + 30) do
- assert_equal "bar", @cache.read("foo")
- end
-
- Time.stub(:now, time + 61) do
- assert_nil @cache.read("foo")
- end
- end
-
- def test_race_condition_protection_skipped_if_not_defined
- @cache.write("foo", "bar")
- time = @cache.send(:read_entry, @cache.send(:normalize_key, "foo", {}), {}).expires_at
-
- Time.stub(:now, Time.at(time)) do
- result = @cache.fetch("foo") do
- assert_nil @cache.read("foo")
- "baz"
- end
- assert_equal "baz", result
- end
- end
-
- def test_race_condition_protection_is_limited
- time = Time.now
- @cache.write("foo", "bar", expires_in: 60)
- Time.stub(:now, time + 71) do
- result = @cache.fetch("foo", race_condition_ttl: 10) do
- assert_nil @cache.read("foo")
- "baz"
- end
- assert_equal "baz", result
- end
- end
-
- def test_race_condition_protection_is_safe
- time = Time.now
- @cache.write("foo", "bar", expires_in: 60)
- Time.stub(:now, time + 61) do
- begin
- @cache.fetch("foo", race_condition_ttl: 10) do
- assert_equal "bar", @cache.read("foo")
- raise ArgumentError.new
- end
- rescue ArgumentError
- end
- assert_equal "bar", @cache.read("foo")
- end
- Time.stub(:now, time + 91) do
- assert_nil @cache.read("foo")
- end
- end
-
- def test_race_condition_protection
- time = Time.now
- @cache.write("foo", "bar", expires_in: 60)
- Time.stub(:now, time + 61) do
- result = @cache.fetch("foo", race_condition_ttl: 10) do
- assert_equal "bar", @cache.read("foo")
- "baz"
- end
- assert_equal "baz", result
- end
- end
-
- def test_crazy_key_characters
- crazy_key = "#/:*(<+=> )&$%@?;'\"\'`~-"
- assert @cache.write(crazy_key, "1", raw: true)
- assert_equal "1", @cache.read(crazy_key)
- assert_equal "1", @cache.fetch(crazy_key)
- assert @cache.delete(crazy_key)
- assert_equal "2", @cache.fetch(crazy_key, raw: true) { "2" }
- assert_equal 3, @cache.increment(crazy_key)
- assert_equal 2, @cache.decrement(crazy_key)
- end
-
- def test_really_long_keys
- key = ""
- 900.times { key << "x" }
- assert @cache.write(key, "bar")
- assert_equal "bar", @cache.read(key)
- assert_equal "bar", @cache.fetch(key)
- assert_nil @cache.read("#{key}x")
- assert_equal({ key => "bar" }, @cache.read_multi(key))
- assert @cache.delete(key)
- end
-
- def test_cache_hit_instrumentation
- key = "test_key"
- @events = []
- ActiveSupport::Notifications.subscribe "cache_read.active_support" do |*args|
- @events << ActiveSupport::Notifications::Event.new(*args)
- end
- assert @cache.write(key, "1", raw: true)
- assert @cache.fetch(key) {}
- assert_equal 1, @events.length
- assert_equal "cache_read.active_support", @events[0].name
- assert_equal :fetch, @events[0].payload[:super_operation]
- assert @events[0].payload[:hit]
- ensure
- ActiveSupport::Notifications.unsubscribe "cache_read.active_support"
- end
-
- def test_cache_miss_instrumentation
- @events = []
- ActiveSupport::Notifications.subscribe(/^cache_(.*)\.active_support$/) do |*args|
- @events << ActiveSupport::Notifications::Event.new(*args)
- end
- assert_not @cache.fetch("bad_key") {}
- assert_equal 3, @events.length
- assert_equal "cache_read.active_support", @events[0].name
- assert_equal "cache_generate.active_support", @events[1].name
- assert_equal "cache_write.active_support", @events[2].name
- assert_equal :fetch, @events[0].payload[:super_operation]
- assert_not @events[0].payload[:hit]
- ensure
- ActiveSupport::Notifications.unsubscribe "cache_read.active_support"
- end
-end
-
-# https://rails.lighthouseapp.com/projects/8994/tickets/6225-memcachestore-cant-deal-with-umlauts-and-special-characters
-# The error is caused by character encodings that can't be compared with ASCII-8BIT regular expressions and by special
-# characters like the umlaut in UTF-8.
-module EncodedKeyCacheBehavior
- Encoding.list.each do |encoding|
- define_method "test_#{encoding.name.underscore}_encoded_values" do
- key = "foo".force_encoding(encoding)
- assert @cache.write(key, "1", raw: true)
- assert_equal "1", @cache.read(key)
- assert_equal "1", @cache.fetch(key)
- assert @cache.delete(key)
- assert_equal "2", @cache.fetch(key, raw: true) { "2" }
- assert_equal 3, @cache.increment(key)
- assert_equal 2, @cache.decrement(key)
- end
- end
-
- def test_common_utf8_values
- key = "\xC3\xBCmlaut".force_encoding(Encoding::UTF_8)
- assert @cache.write(key, "1", raw: true)
- assert_equal "1", @cache.read(key)
- assert_equal "1", @cache.fetch(key)
- assert @cache.delete(key)
- assert_equal "2", @cache.fetch(key, raw: true) { "2" }
- assert_equal 3, @cache.increment(key)
- assert_equal 2, @cache.decrement(key)
- end
-
- def test_retains_encoding
- key = "\xC3\xBCmlaut".force_encoding(Encoding::UTF_8)
- assert @cache.write(key, "1", raw: true)
- assert_equal Encoding::UTF_8, key.encoding
- end
-end
-
-module CacheDeleteMatchedBehavior
- def test_delete_matched
- @cache.write("foo", "bar")
- @cache.write("fu", "baz")
- @cache.write("foo/bar", "baz")
- @cache.write("fu/baz", "bar")
- @cache.delete_matched(/oo/)
- assert !@cache.exist?("foo")
- assert @cache.exist?("fu")
- assert !@cache.exist?("foo/bar")
- assert @cache.exist?("fu/baz")
- end
-end
-
-module CacheIncrementDecrementBehavior
- def test_increment
- @cache.write("foo", 1, raw: true)
- assert_equal 1, @cache.read("foo").to_i
- assert_equal 2, @cache.increment("foo")
- assert_equal 2, @cache.read("foo").to_i
- assert_equal 3, @cache.increment("foo")
- assert_equal 3, @cache.read("foo").to_i
- assert_nil @cache.increment("bar")
- end
-
- def test_decrement
- @cache.write("foo", 3, raw: true)
- assert_equal 3, @cache.read("foo").to_i
- assert_equal 2, @cache.decrement("foo")
- assert_equal 2, @cache.read("foo").to_i
- assert_equal 1, @cache.decrement("foo")
- assert_equal 1, @cache.read("foo").to_i
- assert_nil @cache.decrement("bar")
- end
-end
-
-module LocalCacheBehavior
- def test_local_writes_are_persistent_on_the_remote_cache
- retval = @cache.with_local_cache do
- @cache.write("foo", "bar")
- end
- assert retval
- assert_equal "bar", @cache.read("foo")
- end
-
- def test_clear_also_clears_local_cache
- @cache.with_local_cache do
- @cache.write("foo", "bar")
- @cache.clear
- assert_nil @cache.read("foo")
- end
-
- assert_nil @cache.read("foo")
- end
-
- def test_local_cache_of_write
- @cache.with_local_cache do
- @cache.write("foo", "bar")
- @peek.delete("foo")
- assert_equal "bar", @cache.read("foo")
- end
- end
-
- def test_local_cache_of_read
- @cache.write("foo", "bar")
- @cache.with_local_cache do
- assert_equal "bar", @cache.read("foo")
- end
- end
-
- def test_local_cache_of_read_nil
- @cache.with_local_cache do
- assert_nil @cache.read("foo")
- @cache.send(:bypass_local_cache) { @cache.write "foo", "bar" }
- assert_nil @cache.read("foo")
- end
- end
-
- def test_local_cache_fetch
- @cache.with_local_cache do
- @cache.send(:local_cache).write "foo", "bar"
- assert_equal "bar", @cache.send(:local_cache).fetch("foo")
- end
- end
-
- def test_local_cache_of_write_nil
- @cache.with_local_cache do
- assert @cache.write("foo", nil)
- assert_nil @cache.read("foo")
- @peek.write("foo", "bar")
- assert_nil @cache.read("foo")
- end
- end
-
- def test_local_cache_of_delete
- @cache.with_local_cache do
- @cache.write("foo", "bar")
- @cache.delete("foo")
- assert_nil @cache.read("foo")
- end
- end
-
- def test_local_cache_of_exist
- @cache.with_local_cache do
- @cache.write("foo", "bar")
- @peek.delete("foo")
- assert @cache.exist?("foo")
- end
- end
-
- def test_local_cache_of_increment
- @cache.with_local_cache do
- @cache.write("foo", 1, raw: true)
- @peek.write("foo", 2, raw: true)
- @cache.increment("foo")
- assert_equal 3, @cache.read("foo")
- end
- end
-
- def test_local_cache_of_decrement
- @cache.with_local_cache do
- @cache.write("foo", 1, raw: true)
- @peek.write("foo", 3, raw: true)
- @cache.decrement("foo")
- assert_equal 2, @cache.read("foo")
- end
- end
-
- def test_middleware
- app = lambda { |env|
- result = @cache.write("foo", "bar")
- assert_equal "bar", @cache.read("foo") # make sure 'foo' was written
- assert result
- [200, {}, []]
- }
- app = @cache.middleware.new(app)
- app.call({})
- end
-end
-
-module AutoloadingCacheBehavior
- include DependenciesTestHelpers
- def test_simple_autoloading
- with_autoloading_fixtures do
- @cache.write("foo", EM.new)
- end
-
- remove_constants(:EM)
- ActiveSupport::Dependencies.clear
-
- with_autoloading_fixtures do
- assert_kind_of EM, @cache.read("foo")
- end
-
- remove_constants(:EM)
- ActiveSupport::Dependencies.clear
- end
-
- def test_two_classes_autoloading
- with_autoloading_fixtures do
- @cache.write("foo", [EM.new, ClassFolder.new])
- end
-
- remove_constants(:EM, :ClassFolder)
- ActiveSupport::Dependencies.clear
-
- with_autoloading_fixtures do
- loaded = @cache.read("foo")
- assert_kind_of Array, loaded
- assert_equal 2, loaded.size
- assert_kind_of EM, loaded[0]
- assert_kind_of ClassFolder, loaded[1]
- end
-
- remove_constants(:EM, :ClassFolder)
- ActiveSupport::Dependencies.clear
- end
-end
-
-class FileStoreTest < ActiveSupport::TestCase
- def setup
- Dir.mkdir(cache_dir) unless File.exist?(cache_dir)
- @cache = ActiveSupport::Cache.lookup_store(:file_store, cache_dir, expires_in: 60)
- @peek = ActiveSupport::Cache.lookup_store(:file_store, cache_dir, expires_in: 60)
- @cache_with_pathname = ActiveSupport::Cache.lookup_store(:file_store, Pathname.new(cache_dir), expires_in: 60)
-
- @buffer = StringIO.new
- @cache.logger = ActiveSupport::Logger.new(@buffer)
- end
-
- def teardown
- FileUtils.rm_r(cache_dir)
- rescue Errno::ENOENT
- end
-
- def cache_dir
- File.join(Dir.pwd, "tmp_cache")
- end
-
- include CacheStoreBehavior
- include LocalCacheBehavior
- include CacheDeleteMatchedBehavior
- include CacheIncrementDecrementBehavior
- include AutoloadingCacheBehavior
-
- def test_clear
- gitkeep = File.join(cache_dir, ".gitkeep")
- keep = File.join(cache_dir, ".keep")
- FileUtils.touch([gitkeep, keep])
- @cache.clear
- assert File.exist?(gitkeep)
- assert File.exist?(keep)
- end
-
- def test_clear_without_cache_dir
- FileUtils.rm_r(cache_dir)
- @cache.clear
- end
-
- def test_long_uri_encoded_keys
- @cache.write("%" * 870, 1)
- assert_equal 1, @cache.read("%" * 870)
- end
-
- def test_key_transformation
- key = @cache.send(:normalize_key, "views/index?id=1", {})
- assert_equal "views/index?id=1", @cache.send(:file_path_key, key)
- end
-
- def test_key_transformation_with_pathname
- FileUtils.touch(File.join(cache_dir, "foo"))
- key = @cache_with_pathname.send(:normalize_key, "views/index?id=1", {})
- assert_equal "views/index?id=1", @cache_with_pathname.send(:file_path_key, key)
- end
-
- # Test that generated cache keys are short enough to have Tempfile stuff added to them and
- # remain valid
- def test_filename_max_size
- key = "#{'A' * ActiveSupport::Cache::FileStore::FILENAME_MAX_SIZE}"
- path = @cache.send(:normalize_key, key, {})
- Dir::Tmpname.create(path) do |tmpname, n, opts|
- assert File.basename(tmpname + ".lock").length <= 255, "Temp filename too long: #{File.basename(tmpname + '.lock').length}"
- end
- end
-
- # Because file systems have a maximum filename size, filenames > max size should be split in to directories
- # If filename is 'AAAAB', where max size is 4, the returned path should be AAAA/B
- def test_key_transformation_max_filename_size
- key = "#{'A' * ActiveSupport::Cache::FileStore::FILENAME_MAX_SIZE}B"
- path = @cache.send(:normalize_key, key, {})
- assert path.split("/").all? { |dir_name| dir_name.size <= ActiveSupport::Cache::FileStore::FILENAME_MAX_SIZE }
- assert_equal "B", File.basename(path)
- end
-
- # If nothing has been stored in the cache, there is a chance the cache directory does not yet exist
- # Ensure delete_matched gracefully handles this case
- def test_delete_matched_when_cache_directory_does_not_exist
- assert_nothing_raised do
- ActiveSupport::Cache::FileStore.new("/test/cache/directory").delete_matched(/does_not_exist/)
- end
- end
-
- def test_delete_does_not_delete_empty_parent_dir
- sub_cache_dir = File.join(cache_dir, "subdir/")
- sub_cache_store = ActiveSupport::Cache::FileStore.new(sub_cache_dir)
- assert_nothing_raised do
- assert sub_cache_store.write("foo", "bar")
- assert sub_cache_store.delete("foo")
- end
- assert File.exist?(cache_dir), "Parent of top level cache dir was deleted!"
- assert File.exist?(sub_cache_dir), "Top level cache dir was deleted!"
- assert Dir.entries(sub_cache_dir).reject { |f| ActiveSupport::Cache::FileStore::EXCLUDED_DIRS.include?(f) }.empty?
- end
-
- def test_log_exception_when_cache_read_fails
- File.stub(:exist?, -> { raise StandardError.new("failed") }) do
- @cache.send(:read_entry, "winston", {})
- assert @buffer.string.present?
- end
- end
-
- def test_cleanup_removes_all_expired_entries
- time = Time.now
- @cache.write("foo", "bar", expires_in: 10)
- @cache.write("baz", "qux")
- @cache.write("quux", "corge", expires_in: 20)
- Time.stub(:now, time + 15) do
- @cache.cleanup
- assert_not @cache.exist?("foo")
- assert @cache.exist?("baz")
- assert @cache.exist?("quux")
- end
- end
-
- def test_write_with_unless_exist
- assert_equal true, @cache.write(1, "aaaaaaaaaa")
- assert_equal false, @cache.write(1, "aaaaaaaaaa", unless_exist: true)
- @cache.write(1, nil)
- assert_equal false, @cache.write(1, "aaaaaaaaaa", unless_exist: true)
- end
-end
-
-class MemoryStoreTest < ActiveSupport::TestCase
- def setup
- @record_size = ActiveSupport::Cache.lookup_store(:memory_store).send(:cached_size, 1, ActiveSupport::Cache::Entry.new("aaaaaaaaaa"))
- @cache = ActiveSupport::Cache.lookup_store(:memory_store, expires_in: 60, size: @record_size * 10 + 1)
- end
-
- include CacheStoreBehavior
- include CacheDeleteMatchedBehavior
- include CacheIncrementDecrementBehavior
-
- def test_prune_size
- @cache.write(1, "aaaaaaaaaa") && sleep(0.001)
- @cache.write(2, "bbbbbbbbbb") && sleep(0.001)
- @cache.write(3, "cccccccccc") && sleep(0.001)
- @cache.write(4, "dddddddddd") && sleep(0.001)
- @cache.write(5, "eeeeeeeeee") && sleep(0.001)
- @cache.read(2) && sleep(0.001)
- @cache.read(4)
- @cache.prune(@record_size * 3)
- assert @cache.exist?(5)
- assert @cache.exist?(4)
- assert !@cache.exist?(3), "no entry"
- assert @cache.exist?(2)
- assert !@cache.exist?(1), "no entry"
- end
-
- def test_prune_size_on_write
- @cache.write(1, "aaaaaaaaaa") && sleep(0.001)
- @cache.write(2, "bbbbbbbbbb") && sleep(0.001)
- @cache.write(3, "cccccccccc") && sleep(0.001)
- @cache.write(4, "dddddddddd") && sleep(0.001)
- @cache.write(5, "eeeeeeeeee") && sleep(0.001)
- @cache.write(6, "ffffffffff") && sleep(0.001)
- @cache.write(7, "gggggggggg") && sleep(0.001)
- @cache.write(8, "hhhhhhhhhh") && sleep(0.001)
- @cache.write(9, "iiiiiiiiii") && sleep(0.001)
- @cache.write(10, "kkkkkkkkkk") && sleep(0.001)
- @cache.read(2) && sleep(0.001)
- @cache.read(4) && sleep(0.001)
- @cache.write(11, "llllllllll")
- assert @cache.exist?(11)
- assert @cache.exist?(10)
- assert @cache.exist?(9)
- assert @cache.exist?(8)
- assert @cache.exist?(7)
- assert !@cache.exist?(6), "no entry"
- assert !@cache.exist?(5), "no entry"
- assert @cache.exist?(4)
- assert !@cache.exist?(3), "no entry"
- assert @cache.exist?(2)
- assert !@cache.exist?(1), "no entry"
- end
-
- def test_prune_size_on_write_based_on_key_length
- @cache.write(1, "aaaaaaaaaa") && sleep(0.001)
- @cache.write(2, "bbbbbbbbbb") && sleep(0.001)
- @cache.write(3, "cccccccccc") && sleep(0.001)
- @cache.write(4, "dddddddddd") && sleep(0.001)
- @cache.write(5, "eeeeeeeeee") && sleep(0.001)
- @cache.write(6, "ffffffffff") && sleep(0.001)
- @cache.write(7, "gggggggggg") && sleep(0.001)
- @cache.write(8, "hhhhhhhhhh") && sleep(0.001)
- @cache.write(9, "iiiiiiiiii") && sleep(0.001)
- long_key = "*" * 2 * @record_size
- @cache.write(long_key, "llllllllll")
- assert @cache.exist?(long_key)
- assert @cache.exist?(9)
- assert @cache.exist?(8)
- assert @cache.exist?(7)
- assert @cache.exist?(6)
- assert !@cache.exist?(5), "no entry"
- assert !@cache.exist?(4), "no entry"
- assert !@cache.exist?(3), "no entry"
- assert !@cache.exist?(2), "no entry"
- assert !@cache.exist?(1), "no entry"
- end
-
- def test_pruning_is_capped_at_a_max_time
- def @cache.delete_entry(*args)
- sleep(0.01)
- super
- end
- @cache.write(1, "aaaaaaaaaa") && sleep(0.001)
- @cache.write(2, "bbbbbbbbbb") && sleep(0.001)
- @cache.write(3, "cccccccccc") && sleep(0.001)
- @cache.write(4, "dddddddddd") && sleep(0.001)
- @cache.write(5, "eeeeeeeeee") && sleep(0.001)
- @cache.prune(30, 0.001)
- assert @cache.exist?(5)
- assert @cache.exist?(4)
- assert @cache.exist?(3)
- assert @cache.exist?(2)
- assert !@cache.exist?(1)
- end
-
- def test_write_with_unless_exist
- assert_equal true, @cache.write(1, "aaaaaaaaaa")
- assert_equal false, @cache.write(1, "aaaaaaaaaa", unless_exist: true)
- @cache.write(1, nil)
- assert_equal false, @cache.write(1, "aaaaaaaaaa", unless_exist: true)
- end
-end
-
-class MemCacheStoreTest < ActiveSupport::TestCase
- require "dalli"
-
- begin
- ss = Dalli::Client.new("localhost:11211").stats
- raise Dalli::DalliError unless ss["localhost:11211"]
-
- MEMCACHE_UP = true
- rescue Dalli::DalliError
- $stderr.puts "Skipping memcached tests. Start memcached and try again."
- MEMCACHE_UP = false
- end
-
- def setup
- skip "memcache server is not up" unless MEMCACHE_UP
-
- @cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, expires_in: 60)
- @peek = ActiveSupport::Cache.lookup_store(:mem_cache_store)
- @data = @cache.instance_variable_get(:@data)
- @cache.clear
- @cache.silence!
- @cache.logger = ActiveSupport::Logger.new("/dev/null")
- end
-
- include CacheStoreBehavior
- include LocalCacheBehavior
- include CacheIncrementDecrementBehavior
- include EncodedKeyCacheBehavior
- include AutoloadingCacheBehavior
-
- def test_raw_values
- cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, raw: true)
- cache.clear
- cache.write("foo", 2)
- assert_equal "2", cache.read("foo")
- end
-
- def test_raw_values_with_marshal
- cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, raw: true)
- cache.clear
- cache.write("foo", Marshal.dump([]))
- assert_equal [], cache.read("foo")
- end
-
- def test_local_cache_raw_values
- cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, raw: true)
- cache.clear
- cache.with_local_cache do
- cache.write("foo", 2)
- assert_equal "2", cache.read("foo")
- end
- end
-
- def test_local_cache_raw_values_with_marshal
- cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, raw: true)
- cache.clear
- cache.with_local_cache do
- cache.write("foo", Marshal.dump([]))
- assert_equal [], cache.read("foo")
- end
- end
-
- def test_read_should_return_a_different_object_id_each_time_it_is_called
- @cache.write("foo", "bar")
- value = @cache.read("foo")
- assert_not_equal value.object_id, @cache.read("foo").object_id
- value << "bingo"
- assert_not_equal value, @cache.read("foo")
- end
-end
-
-class NullStoreTest < ActiveSupport::TestCase
- def setup
- @cache = ActiveSupport::Cache.lookup_store(:null_store)
- end
-
- def test_clear
- @cache.clear
- end
-
- def test_cleanup
- @cache.cleanup
- end
-
- def test_write
- assert_equal true, @cache.write("name", "value")
- end
-
- def test_read
- @cache.write("name", "value")
- assert_nil @cache.read("name")
- end
-
- def test_delete
- @cache.write("name", "value")
- assert_equal false, @cache.delete("name")
- end
-
- def test_increment
- @cache.write("name", 1, raw: true)
- assert_nil @cache.increment("name")
- end
-
- def test_decrement
- @cache.write("name", 1, raw: true)
- assert_nil @cache.increment("name")
- end
-
- def test_delete_matched
- @cache.write("name", "value")
- @cache.delete_matched(/name/)
- end
-
- def test_local_store_strategy
- @cache.with_local_cache do
- @cache.write("name", "value")
- assert_equal "value", @cache.read("name")
- @cache.delete("name")
- assert_nil @cache.read("name")
- @cache.write("name", "value")
- end
- assert_nil @cache.read("name")
- end
-end
-
-class CacheStoreLoggerTest < ActiveSupport::TestCase
- def setup
- @cache = ActiveSupport::Cache.lookup_store(:memory_store)
-
- @buffer = StringIO.new
- @cache.logger = ActiveSupport::Logger.new(@buffer)
- end
-
- def test_logging
- @cache.fetch("foo") { "bar" }
- assert @buffer.string.present?
- end
-
- def test_log_with_string_namespace
- @cache.fetch("foo", namespace: "string_namespace") { "bar" }
- assert_match %r{string_namespace:foo}, @buffer.string
- end
-
- def test_log_with_proc_namespace
- proc = Proc.new do
- "proc_namespace"
- end
- @cache.fetch("foo", namespace: proc) { "bar" }
- assert_match %r{proc_namespace:foo}, @buffer.string
- end
-
- def test_mute_logging
- @cache.mute { @cache.fetch("foo") { "bar" } }
- assert @buffer.string.blank?
- end
-end
-
-class CacheEntryTest < ActiveSupport::TestCase
- def test_expired
- entry = ActiveSupport::Cache::Entry.new("value")
- assert !entry.expired?, "entry not expired"
- entry = ActiveSupport::Cache::Entry.new("value", expires_in: 60)
- assert !entry.expired?, "entry not expired"
- Time.stub(:now, Time.now + 61) do
- assert entry.expired?, "entry is expired"
- end
- end
-
- def test_compress_values
- value = "value" * 100
- entry = ActiveSupport::Cache::Entry.new(value, compress: true, compress_threshold: 1)
- assert_equal value, entry.value
- assert(value.bytesize > entry.size, "value is compressed")
- end
-
- def test_non_compress_values
- value = "value" * 100
- entry = ActiveSupport::Cache::Entry.new(value)
- assert_equal value, entry.value
- assert_equal value.bytesize, entry.size
- end
-end
diff --git a/activesupport/test/callback_inheritance_test.rb b/activesupport/test/callback_inheritance_test.rb
index 9e2f7527e0..67813a749e 100644
--- a/activesupport/test/callback_inheritance_test.rb
+++ b/activesupport/test/callback_inheritance_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
class GrandParent
diff --git a/activesupport/test/callbacks_test.rb b/activesupport/test/callbacks_test.rb
index 4f00afb581..30f1632460 100644
--- a/activesupport/test/callbacks_test.rb
+++ b/activesupport/test/callbacks_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
module CallbacksTest
@@ -75,7 +77,7 @@ module CallbacksTest
skip_callback :save, :after, :after_save_method, unless: :yes
skip_callback :save, :after, :after_save_method, if: :no
skip_callback :save, :before, :before_save_method, unless: :no
- skip_callback :save, :before, CallbackClass , if: :yes
+ skip_callback :save, :before, CallbackClass, if: :yes
def yes; true; end
def no; false; end
end
@@ -191,13 +193,6 @@ module CallbacksTest
before_save Proc.new { |r| r.history << "b00m" }, if: :no
before_save Proc.new { |r| r.history << [:before_save, :symbol] }, unless: :no
before_save Proc.new { |r| r.history << "b00m" }, unless: :yes
- # string
- ActiveSupport::Deprecation.silence do
- before_save Proc.new { |r| r.history << [:before_save, :string] }, if: "yes"
- before_save Proc.new { |r| r.history << "b00m" }, if: "no"
- before_save Proc.new { |r| r.history << [:before_save, :string] }, unless: "no"
- before_save Proc.new { |r| r.history << "b00m" }, unless: "yes"
- end
# Combined if and unless
before_save Proc.new { |r| r.history << [:before_save, :combined_symbol] }, if: :yes, unless: :no
before_save Proc.new { |r| r.history << "b00m" }, if: :yes, unless: :yes
@@ -590,8 +585,6 @@ module CallbacksTest
[:before_save, :proc],
[:before_save, :symbol],
[:before_save, :symbol],
- [:before_save, :string],
- [:before_save, :string],
[:before_save, :combined_symbol],
], person.history
end
@@ -1180,14 +1173,15 @@ module CallbacksTest
end
end
- class DeprecatedWarningTest < ActiveSupport::TestCase
- def test_deprecate_string_conditional_options
+ class NotSupportedStringConditionalTest < ActiveSupport::TestCase
+ def test_string_conditional_options
klass = Class.new(Record)
- assert_deprecated { klass.before_save :tweedle, if: "true" }
- assert_deprecated { klass.after_save :tweedle, unless: "false" }
- assert_deprecated { klass.skip_callback :save, :before, :tweedle, if: "true" }
- assert_deprecated { klass.skip_callback :save, :after, :tweedle, unless: "false" }
+ assert_raises(ArgumentError) { klass.before_save :tweedle, if: ["true"] }
+ assert_raises(ArgumentError) { klass.before_save :tweedle, if: "true" }
+ assert_raises(ArgumentError) { klass.after_save :tweedle, unless: "false" }
+ assert_raises(ArgumentError) { klass.skip_callback :save, :before, :tweedle, if: "true" }
+ assert_raises(ArgumentError) { klass.skip_callback :save, :after, :tweedle, unless: "false" }
end
end
diff --git a/activesupport/test/class_cache_test.rb b/activesupport/test/class_cache_test.rb
index 004b4dc9ce..7b97028e8c 100644
--- a/activesupport/test/class_cache_test.rb
+++ b/activesupport/test/class_cache_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/dependencies"
diff --git a/activesupport/test/clean_backtrace_test.rb b/activesupport/test/clean_backtrace_test.rb
index 5ed518cdb0..1b44c7c9bf 100644
--- a/activesupport/test/clean_backtrace_test.rb
+++ b/activesupport/test/clean_backtrace_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
class BacktraceCleanerFilterTest < ActiveSupport::TestCase
@@ -8,8 +10,8 @@ class BacktraceCleanerFilterTest < ActiveSupport::TestCase
test "backtrace should filter all lines in a backtrace, removing prefixes" do
assert_equal \
- ["/my/class.rb", "/my/module.rb"],
- @bc.clean(["/my/prefix/my/class.rb", "/my/prefix/my/module.rb"])
+ ["/my/class.rb", "/my/module.rb"],
+ @bc.clean(["/my/prefix/my/class.rb", "/my/prefix/my/module.rb"])
end
test "backtrace cleaner should allow removing filters" do
diff --git a/activesupport/test/clean_logger_test.rb b/activesupport/test/clean_logger_test.rb
index cf37fc5639..6d8f7064ce 100644
--- a/activesupport/test/clean_logger_test.rb
+++ b/activesupport/test/clean_logger_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "stringio"
require "active_support/logger"
diff --git a/activesupport/test/concern_test.rb b/activesupport/test/concern_test.rb
index 7a5a5414a7..ef75a320d1 100644
--- a/activesupport/test/concern_test.rb
+++ b/activesupport/test/concern_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/concern"
diff --git a/activesupport/test/concurrency/load_interlock_aware_monitor_test.rb b/activesupport/test/concurrency/load_interlock_aware_monitor_test.rb
new file mode 100644
index 0000000000..2d0f45ec5f
--- /dev/null
+++ b/activesupport/test/concurrency/load_interlock_aware_monitor_test.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+require "abstract_unit"
+require "concurrent/atomic/count_down_latch"
+require "active_support/concurrency/load_interlock_aware_monitor"
+
+module ActiveSupport
+ module Concurrency
+ class LoadInterlockAwareMonitorTest < ActiveSupport::TestCase
+ def setup
+ @monitor = ActiveSupport::Concurrency::LoadInterlockAwareMonitor.new
+ end
+
+ def test_entering_with_no_blocking
+ assert @monitor.mon_enter
+ end
+
+ def test_entering_with_blocking
+ load_interlock_latch = Concurrent::CountDownLatch.new
+ monitor_latch = Concurrent::CountDownLatch.new
+
+ able_to_use_monitor = false
+ able_to_load = false
+
+ thread_with_load_interlock = Thread.new do
+ ActiveSupport::Dependencies.interlock.running do
+ load_interlock_latch.count_down
+ monitor_latch.wait
+
+ @monitor.synchronize do
+ able_to_use_monitor = true
+ end
+ end
+ end
+
+ thread_with_monitor_lock = Thread.new do
+ @monitor.synchronize do
+ monitor_latch.count_down
+ load_interlock_latch.wait
+
+ ActiveSupport::Dependencies.interlock.loading do
+ able_to_load = true
+ end
+ end
+ end
+
+ thread_with_load_interlock.join
+ thread_with_monitor_lock.join
+
+ assert able_to_use_monitor
+ assert able_to_load
+ end
+ end
+ end
+end
diff --git a/activesupport/test/configurable_test.rb b/activesupport/test/configurable_test.rb
index 3cd6d2d4d0..10719596df 100644
--- a/activesupport/test/configurable_test.rb
+++ b/activesupport/test/configurable_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/configurable"
diff --git a/activesupport/test/constantize_test_cases.rb b/activesupport/test/constantize_test_cases.rb
index 32b720bcbb..2c6145940b 100644
--- a/activesupport/test/constantize_test_cases.rb
+++ b/activesupport/test/constantize_test_cases.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "dependencies_test_helpers"
module Ace
diff --git a/activesupport/test/core_ext/array/access_test.rb b/activesupport/test/core_ext/array/access_test.rb
index a38ea36d00..8c217023cf 100644
--- a/activesupport/test/core_ext/array/access_test.rb
+++ b/activesupport/test/core_ext/array/access_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/core_ext/array"
diff --git a/activesupport/test/core_ext/array/conversions_test.rb b/activesupport/test/core_ext/array/conversions_test.rb
index 29e661a99b..0a7c43d421 100644
--- a/activesupport/test/core_ext/array/conversions_test.rb
+++ b/activesupport/test/core_ext/array/conversions_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/core_ext/array"
require "active_support/core_ext/big_decimal"
@@ -58,7 +60,7 @@ class ToSentenceTest < ActiveSupport::TestCase
["one", "two"].to_sentence(passing: "invalid option")
end
- assert_equal exception.message, "Unknown key: :passing. Valid keys are: :words_connector, :two_words_connector, :last_word_connector, :locale"
+ assert_equal "Unknown key: :passing. Valid keys are: :words_connector, :two_words_connector, :last_word_connector, :locale", exception.message
end
def test_always_returns_string
@@ -88,7 +90,7 @@ class ToXmlTest < ActiveSupport::TestCase
def test_to_xml_with_hash_elements
xml = [
{ name: "David", age: 26, age_in_millis: 820497600000 },
- { name: "Jason", age: 31, age_in_millis: BigDecimal.new("1.0") }
+ { name: "Jason", age: 31, age_in_millis: BigDecimal("1.0") }
].to_xml(skip_instruct: true, indent: 0)
assert_equal '<objects type="array"><object>', xml.first(30)
@@ -171,7 +173,7 @@ class ToXmlTest < ActiveSupport::TestCase
def test_to_xml_with_instruct
xml = [
{ name: "David", age: 26, age_in_millis: 820497600000 },
- { name: "Jason", age: 31, age_in_millis: BigDecimal.new("1.0") }
+ { name: "Jason", age: 31, age_in_millis: BigDecimal("1.0") }
].to_xml(skip_instruct: false, indent: 0)
assert_match(/^<\?xml [^>]*/, xml)
@@ -181,7 +183,7 @@ class ToXmlTest < ActiveSupport::TestCase
def test_to_xml_with_block
xml = [
{ name: "David", age: 26, age_in_millis: 820497600000 },
- { name: "Jason", age: 31, age_in_millis: BigDecimal.new("1.0") }
+ { name: "Jason", age: 31, age_in_millis: BigDecimal("1.0") }
].to_xml(skip_instruct: true, indent: 0) do |builder|
builder.count 2
end
diff --git a/activesupport/test/core_ext/array/extract_options_test.rb b/activesupport/test/core_ext/array/extract_options_test.rb
index 1651bee0f6..7a4b15cd71 100644
--- a/activesupport/test/core_ext/array/extract_options_test.rb
+++ b/activesupport/test/core_ext/array/extract_options_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/core_ext/array"
require "active_support/core_ext/hash"
diff --git a/activesupport/test/core_ext/array/grouping_test.rb b/activesupport/test/core_ext/array/grouping_test.rb
index 4c6aadba8c..c182b91826 100644
--- a/activesupport/test/core_ext/array/grouping_test.rb
+++ b/activesupport/test/core_ext/array/grouping_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/core_ext/array"
@@ -114,7 +116,7 @@ class SplitTest < ActiveSupport::TestCase
def test_split_with_block
a = (1..10).to_a
assert_equal [[1, 2], [4, 5], [7, 8], [10]], a.split { |i| i % 3 == 0 }
- assert_equal [1, 2, 3, 4, 5, 6, 7, 8, 9 , 10], a
+ assert_equal [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], a
end
def test_split_with_edge_values
diff --git a/activesupport/test/core_ext/array/prepend_append_test.rb b/activesupport/test/core_ext/array/prepend_append_test.rb
index 763e26191d..c34acd66ad 100644
--- a/activesupport/test/core_ext/array/prepend_append_test.rb
+++ b/activesupport/test/core_ext/array/prepend_append_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/core_ext/array"
diff --git a/activesupport/test/core_ext/array/wrap_test.rb b/activesupport/test/core_ext/array/wrap_test.rb
index ae846cb3f2..46564b4d73 100644
--- a/activesupport/test/core_ext/array/wrap_test.rb
+++ b/activesupport/test/core_ext/array/wrap_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/core_ext/array"
diff --git a/activesupport/test/core_ext/bigdecimal_test.rb b/activesupport/test/core_ext/bigdecimal_test.rb
index 43b659546f..62588be33b 100644
--- a/activesupport/test/core_ext/bigdecimal_test.rb
+++ b/activesupport/test/core_ext/bigdecimal_test.rb
@@ -1,9 +1,11 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/core_ext/big_decimal"
class BigDecimalTest < ActiveSupport::TestCase
def test_to_s
- bd = BigDecimal.new "0.01"
+ bd = BigDecimal "0.01"
assert_equal "0.01", bd.to_s
assert_equal "+0.01", bd.to_s("+F")
assert_equal "+0.0 1", bd.to_s("+1F")
diff --git a/activesupport/test/core_ext/class/attribute_test.rb b/activesupport/test/core_ext/class/attribute_test.rb
index 5a9ec78cc1..be6ad82367 100644
--- a/activesupport/test/core_ext/class/attribute_test.rb
+++ b/activesupport/test/core_ext/class/attribute_test.rb
@@ -1,9 +1,15 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/core_ext/class/attribute"
class ClassAttributeTest < ActiveSupport::TestCase
def setup
- @klass = Class.new { class_attribute :setting }
+ @klass = Class.new do
+ class_attribute :setting
+ class_attribute :timeout, default: 5
+ end
+
@sub = Class.new(@klass)
end
@@ -12,6 +18,10 @@ class ClassAttributeTest < ActiveSupport::TestCase
assert_nil @sub.setting
end
+ test "custom default" do
+ assert_equal 5, @klass.timeout
+ end
+
test "inheritable" do
@klass.setting = 1
assert_equal 1, @sub.setting
diff --git a/activesupport/test/core_ext/class_test.rb b/activesupport/test/core_ext/class_test.rb
index a7905196ae..9cc006fc63 100644
--- a/activesupport/test/core_ext/class_test.rb
+++ b/activesupport/test/core_ext/class_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/core_ext/class"
require "set"
diff --git a/activesupport/test/core_ext/date_and_time_behavior.rb b/activesupport/test/core_ext/date_and_time_behavior.rb
index 6c77e8f313..1176ed647a 100644
--- a/activesupport/test/core_ext/date_and_time_behavior.rb
+++ b/activesupport/test/core_ext/date_and_time_behavior.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
module DateAndTimeBehavior
@@ -7,6 +9,11 @@ module DateAndTimeBehavior
end
def test_prev_day
+ assert_equal date_time_init(2005, 2, 24, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_day(-2)
+ assert_equal date_time_init(2005, 2, 23, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_day(-1)
+ assert_equal date_time_init(2005, 2, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_day(0)
+ assert_equal date_time_init(2005, 2, 21, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_day(1)
+ assert_equal date_time_init(2005, 2, 20, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_day(2)
assert_equal date_time_init(2005, 2, 21, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_day
assert_equal date_time_init(2005, 2, 28, 10, 10, 10), date_time_init(2005, 3, 2, 10, 10, 10).prev_day.prev_day
end
@@ -17,6 +24,11 @@ module DateAndTimeBehavior
end
def test_next_day
+ assert_equal date_time_init(2005, 2, 20, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_day(-2)
+ assert_equal date_time_init(2005, 2, 21, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_day(-1)
+ assert_equal date_time_init(2005, 2, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_day(0)
+ assert_equal date_time_init(2005, 2, 23, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_day(1)
+ assert_equal date_time_init(2005, 2, 24, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_day(2)
assert_equal date_time_init(2005, 2, 23, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_day
assert_equal date_time_init(2005, 3, 2, 10, 10, 10), date_time_init(2005, 2, 28, 10, 10, 10).next_day.next_day
end
@@ -149,6 +161,16 @@ module DateAndTimeBehavior
assert_equal date_time_init(2015, 1, 5, 15, 15, 10), date_time_init(2015, 1, 3, 15, 15, 10).next_weekday
end
+ def test_next_month
+ assert_equal date_time_init(2004, 12, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_month(-2)
+ assert_equal date_time_init(2005, 1, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_month(-1)
+ assert_equal date_time_init(2005, 2, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_month(0)
+ assert_equal date_time_init(2005, 3, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_month(1)
+ assert_equal date_time_init(2005, 4, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_month(2)
+ assert_equal date_time_init(2005, 3, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_month
+ assert_equal date_time_init(2005, 4, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_month.next_month
+ end
+
def test_next_month_on_31st
assert_equal date_time_init(2005, 9, 30, 15, 15, 10), date_time_init(2005, 8, 31, 15, 15, 10).next_month
end
@@ -158,7 +180,13 @@ module DateAndTimeBehavior
end
def test_next_year
+ assert_equal date_time_init(2003, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).next_year(-2)
+ assert_equal date_time_init(2004, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).next_year(-1)
+ assert_equal date_time_init(2005, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).next_year(0)
+ assert_equal date_time_init(2006, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).next_year(1)
+ assert_equal date_time_init(2007, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).next_year(2)
assert_equal date_time_init(2006, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).next_year
+ assert_equal date_time_init(2007, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).next_year.next_year
end
def test_prev_week
@@ -201,6 +229,16 @@ module DateAndTimeBehavior
assert_equal date_time_init(2015, 1, 2, 15, 15, 10), date_time_init(2015, 1, 4, 15, 15, 10).prev_weekday
end
+ def test_prev_month
+ assert_equal date_time_init(2005, 4, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_month(-2)
+ assert_equal date_time_init(2005, 3, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_month(-1)
+ assert_equal date_time_init(2005, 2, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_month(0)
+ assert_equal date_time_init(2005, 1, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_month(1)
+ assert_equal date_time_init(2004, 12, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_month(2)
+ assert_equal date_time_init(2005, 1, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_month
+ assert_equal date_time_init(2004, 12, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_month.prev_month
+ end
+
def test_prev_month_on_31st
assert_equal date_time_init(2004, 2, 29, 10, 10, 10), date_time_init(2004, 3, 31, 10, 10, 10).prev_month
end
@@ -210,7 +248,21 @@ module DateAndTimeBehavior
end
def test_prev_year
+ assert_equal date_time_init(2007, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).prev_year(-2)
+ assert_equal date_time_init(2006, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).prev_year(-1)
+ assert_equal date_time_init(2005, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).prev_year(0)
+ assert_equal date_time_init(2004, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).prev_year(1)
+ assert_equal date_time_init(2003, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).prev_year(2)
assert_equal date_time_init(2004, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).prev_year
+ assert_equal date_time_init(2003, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).prev_year.prev_year
+ end
+
+ def test_last_month_on_31st
+ assert_equal date_time_init(2004, 2, 29, 0, 0, 0), date_time_init(2004, 3, 31, 0, 0, 0).last_month
+ end
+
+ def test_last_year
+ assert_equal date_time_init(2004, 6, 5, 10, 0, 0), date_time_init(2005, 6, 5, 10, 0, 0).last_year
end
def test_days_to_week_start
@@ -245,24 +297,24 @@ module DateAndTimeBehavior
def test_beginning_of_week
assert_equal date_time_init(2005, 1, 31, 0, 0, 0), date_time_init(2005, 2, 4, 10, 10, 10).beginning_of_week
- assert_equal date_time_init(2005, 11, 28, 0, 0, 0), date_time_init(2005, 11, 28, 0, 0, 0).beginning_of_week #monday
- assert_equal date_time_init(2005, 11, 28, 0, 0, 0), date_time_init(2005, 11, 29, 0, 0, 0).beginning_of_week #tuesday
- assert_equal date_time_init(2005, 11, 28, 0, 0, 0), date_time_init(2005, 11, 30, 0, 0, 0).beginning_of_week #wednesday
- assert_equal date_time_init(2005, 11, 28, 0, 0, 0), date_time_init(2005, 12, 01, 0, 0, 0).beginning_of_week #thursday
- assert_equal date_time_init(2005, 11, 28, 0, 0, 0), date_time_init(2005, 12, 02, 0, 0, 0).beginning_of_week #friday
- assert_equal date_time_init(2005, 11, 28, 0, 0, 0), date_time_init(2005, 12, 03, 0, 0, 0).beginning_of_week #saturday
- assert_equal date_time_init(2005, 11, 28, 0, 0, 0), date_time_init(2005, 12, 04, 0, 0, 0).beginning_of_week #sunday
+ assert_equal date_time_init(2005, 11, 28, 0, 0, 0), date_time_init(2005, 11, 28, 0, 0, 0).beginning_of_week # monday
+ assert_equal date_time_init(2005, 11, 28, 0, 0, 0), date_time_init(2005, 11, 29, 0, 0, 0).beginning_of_week # tuesday
+ assert_equal date_time_init(2005, 11, 28, 0, 0, 0), date_time_init(2005, 11, 30, 0, 0, 0).beginning_of_week # wednesday
+ assert_equal date_time_init(2005, 11, 28, 0, 0, 0), date_time_init(2005, 12, 01, 0, 0, 0).beginning_of_week # thursday
+ assert_equal date_time_init(2005, 11, 28, 0, 0, 0), date_time_init(2005, 12, 02, 0, 0, 0).beginning_of_week # friday
+ assert_equal date_time_init(2005, 11, 28, 0, 0, 0), date_time_init(2005, 12, 03, 0, 0, 0).beginning_of_week # saturday
+ assert_equal date_time_init(2005, 11, 28, 0, 0, 0), date_time_init(2005, 12, 04, 0, 0, 0).beginning_of_week # sunday
end
def test_end_of_week
assert_equal date_time_init(2008, 1, 6, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 12, 31, 10, 10, 10).end_of_week
- assert_equal date_time_init(2007, 9, 2, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 8, 27, 0, 0, 0).end_of_week #monday
- assert_equal date_time_init(2007, 9, 2, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 8, 28, 0, 0, 0).end_of_week #tuesday
- assert_equal date_time_init(2007, 9, 2, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 8, 29, 0, 0, 0).end_of_week #wednesday
- assert_equal date_time_init(2007, 9, 2, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 8, 30, 0, 0, 0).end_of_week #thursday
- assert_equal date_time_init(2007, 9, 2, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 8, 31, 0, 0, 0).end_of_week #friday
- assert_equal date_time_init(2007, 9, 2, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 9, 01, 0, 0, 0).end_of_week #saturday
- assert_equal date_time_init(2007, 9, 2, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 9, 02, 0, 0, 0).end_of_week #sunday
+ assert_equal date_time_init(2007, 9, 2, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 8, 27, 0, 0, 0).end_of_week # monday
+ assert_equal date_time_init(2007, 9, 2, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 8, 28, 0, 0, 0).end_of_week # tuesday
+ assert_equal date_time_init(2007, 9, 2, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 8, 29, 0, 0, 0).end_of_week # wednesday
+ assert_equal date_time_init(2007, 9, 2, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 8, 30, 0, 0, 0).end_of_week # thursday
+ assert_equal date_time_init(2007, 9, 2, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 8, 31, 0, 0, 0).end_of_week # friday
+ assert_equal date_time_init(2007, 9, 2, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 9, 01, 0, 0, 0).end_of_week # saturday
+ assert_equal date_time_init(2007, 9, 2, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 9, 02, 0, 0, 0).end_of_week # sunday
end
def test_end_of_month
@@ -276,6 +328,26 @@ module DateAndTimeBehavior
assert_equal date_time_init(2007, 12, 31, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 12, 31, 10, 10, 10).end_of_year
end
+ def test_next_occurring
+ assert_equal date_time_init(2017, 12, 18, 3, 14, 15), date_time_init(2017, 12, 14, 3, 14, 15).next_occurring(:monday)
+ assert_equal date_time_init(2017, 12, 19, 3, 14, 15), date_time_init(2017, 12, 14, 3, 14, 15).next_occurring(:tuesday)
+ assert_equal date_time_init(2017, 12, 20, 3, 14, 15), date_time_init(2017, 12, 14, 3, 14, 15).next_occurring(:wednesday)
+ assert_equal date_time_init(2017, 12, 21, 3, 14, 15), date_time_init(2017, 12, 14, 3, 14, 15).next_occurring(:thursday)
+ assert_equal date_time_init(2017, 12, 15, 3, 14, 15), date_time_init(2017, 12, 14, 3, 14, 15).next_occurring(:friday)
+ assert_equal date_time_init(2017, 12, 16, 3, 14, 15), date_time_init(2017, 12, 14, 3, 14, 15).next_occurring(:saturday)
+ assert_equal date_time_init(2017, 12, 17, 3, 14, 15), date_time_init(2017, 12, 14, 3, 14, 15).next_occurring(:sunday)
+ end
+
+ def test_prev_occurring
+ assert_equal date_time_init(2017, 12, 11, 3, 14, 15), date_time_init(2017, 12, 14, 3, 14, 15).prev_occurring(:monday)
+ assert_equal date_time_init(2017, 12, 12, 3, 14, 15), date_time_init(2017, 12, 14, 3, 14, 15).prev_occurring(:tuesday)
+ assert_equal date_time_init(2017, 12, 13, 3, 14, 15), date_time_init(2017, 12, 14, 3, 14, 15).prev_occurring(:wednesday)
+ assert_equal date_time_init(2017, 12, 7, 3, 14, 15), date_time_init(2017, 12, 14, 3, 14, 15).prev_occurring(:thursday)
+ assert_equal date_time_init(2017, 12, 8, 3, 14, 15), date_time_init(2017, 12, 14, 3, 14, 15).prev_occurring(:friday)
+ assert_equal date_time_init(2017, 12, 9, 3, 14, 15), date_time_init(2017, 12, 14, 3, 14, 15).prev_occurring(:saturday)
+ assert_equal date_time_init(2017, 12, 10, 3, 14, 15), date_time_init(2017, 12, 14, 3, 14, 15).prev_occurring(:sunday)
+ end
+
def test_monday_with_default_beginning_of_week_set
with_bw_default(:saturday) do
assert_equal date_time_init(2012, 9, 17, 0, 0, 0), date_time_init(2012, 9, 18, 0, 0, 0).monday
diff --git a/activesupport/test/core_ext/date_and_time_compatibility_test.rb b/activesupport/test/core_ext/date_and_time_compatibility_test.rb
index 4c90460032..266829a452 100644
--- a/activesupport/test/core_ext/date_and_time_compatibility_test.rb
+++ b/activesupport/test/core_ext/date_and_time_compatibility_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/time"
require "time_zone_test_helpers"
@@ -16,11 +18,13 @@ class DateAndTimeCompatibilityTest < ActiveSupport::TestCase
def test_time_to_time_preserves_timezone
with_preserve_timezone(true) do
with_env_tz "US/Eastern" do
- time = Time.new(2016, 4, 23, 15, 11, 12, 3600).to_time
+ source = Time.new(2016, 4, 23, 15, 11, 12, 3600)
+ time = source.to_time
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
assert_equal @utc_offset, time.utc_offset
+ assert_equal source.object_id, time.object_id
end
end
end
@@ -28,11 +32,43 @@ class DateAndTimeCompatibilityTest < ActiveSupport::TestCase
def test_time_to_time_does_not_preserve_time_zone
with_preserve_timezone(false) do
with_env_tz "US/Eastern" do
- time = Time.new(2016, 4, 23, 15, 11, 12, 3600).to_time
+ source = Time.new(2016, 4, 23, 15, 11, 12, 3600)
+ time = source.to_time
+
+ assert_instance_of Time, time
+ assert_equal @utc_time, time.getutc
+ assert_equal @system_offset, time.utc_offset
+ assert_not_equal source.object_id, time.object_id
+ end
+ end
+ end
+
+ def test_time_to_time_frozen_preserves_timezone
+ with_preserve_timezone(true) do
+ with_env_tz "US/Eastern" do
+ source = Time.new(2016, 4, 23, 15, 11, 12, 3600).freeze
+ time = source.to_time
+
+ assert_instance_of Time, time
+ assert_equal @utc_time, time.getutc
+ assert_equal @utc_offset, time.utc_offset
+ assert_equal source.object_id, time.object_id
+ assert_predicate time, :frozen?
+ end
+ end
+ end
+
+ def test_time_to_time_frozen_does_not_preserve_time_zone
+ with_preserve_timezone(false) do
+ with_env_tz "US/Eastern" do
+ source = Time.new(2016, 4, 23, 15, 11, 12, 3600).freeze
+ time = source.to_time
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
assert_equal @system_offset, time.utc_offset
+ assert_not_equal source.object_id, time.object_id
+ assert_not_predicate time, :frozen?
end
end
end
@@ -40,7 +76,8 @@ class DateAndTimeCompatibilityTest < ActiveSupport::TestCase
def test_datetime_to_time_preserves_timezone
with_preserve_timezone(true) do
with_env_tz "US/Eastern" do
- time = DateTime.new(2016, 4, 23, 15, 11, 12, Rational(1, 24)).to_time
+ source = DateTime.new(2016, 4, 23, 15, 11, 12, Rational(1, 24))
+ time = source.to_time
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
@@ -52,11 +89,40 @@ class DateAndTimeCompatibilityTest < ActiveSupport::TestCase
def test_datetime_to_time_does_not_preserve_time_zone
with_preserve_timezone(false) do
with_env_tz "US/Eastern" do
- time = DateTime.new(2016, 4, 23, 15, 11, 12, Rational(1, 24)).to_time
+ source = DateTime.new(2016, 4, 23, 15, 11, 12, Rational(1, 24))
+ time = source.to_time
+
+ assert_instance_of Time, time
+ assert_equal @utc_time, time.getutc
+ assert_equal @system_offset, time.utc_offset
+ end
+ end
+ end
+
+ def test_datetime_to_time_frozen_preserves_timezone
+ with_preserve_timezone(true) do
+ with_env_tz "US/Eastern" do
+ source = DateTime.new(2016, 4, 23, 15, 11, 12, Rational(1, 24)).freeze
+ time = source.to_time
+
+ assert_instance_of Time, time
+ assert_equal @utc_time, time.getutc
+ assert_equal @utc_offset, time.utc_offset
+ assert_not_predicate time, :frozen?
+ end
+ end
+ end
+
+ def test_datetime_to_time_frozen_does_not_preserve_time_zone
+ with_preserve_timezone(false) do
+ with_env_tz "US/Eastern" do
+ source = DateTime.new(2016, 4, 23, 15, 11, 12, Rational(1, 24)).freeze
+ time = source.to_time
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
assert_equal @system_offset, time.utc_offset
+ assert_not_predicate time, :frozen?
end
end
end
@@ -64,14 +130,16 @@ class DateAndTimeCompatibilityTest < ActiveSupport::TestCase
def test_twz_to_time_preserves_timezone
with_preserve_timezone(true) do
with_env_tz "US/Eastern" do
- time = ActiveSupport::TimeWithZone.new(@utc_time, @zone).to_time
+ source = ActiveSupport::TimeWithZone.new(@utc_time, @zone)
+ time = source.to_time
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
assert_instance_of Time, time.getutc
assert_equal @utc_offset, time.utc_offset
- time = ActiveSupport::TimeWithZone.new(@date_time, @zone).to_time
+ source = ActiveSupport::TimeWithZone.new(@date_time, @zone)
+ time = source.to_time
assert_instance_of Time, time
assert_equal @date_time, time.getutc
@@ -84,14 +152,16 @@ class DateAndTimeCompatibilityTest < ActiveSupport::TestCase
def test_twz_to_time_does_not_preserve_time_zone
with_preserve_timezone(false) do
with_env_tz "US/Eastern" do
- time = ActiveSupport::TimeWithZone.new(@utc_time, @zone).to_time
+ source = ActiveSupport::TimeWithZone.new(@utc_time, @zone)
+ time = source.to_time
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
assert_instance_of Time, time.getutc
assert_equal @system_offset, time.utc_offset
- time = ActiveSupport::TimeWithZone.new(@date_time, @zone).to_time
+ source = ActiveSupport::TimeWithZone.new(@date_time, @zone)
+ time = source.to_time
assert_instance_of Time, time
assert_equal @date_time, time.getutc
@@ -101,10 +171,59 @@ class DateAndTimeCompatibilityTest < ActiveSupport::TestCase
end
end
+ def test_twz_to_time_frozen_preserves_timezone
+ with_preserve_timezone(true) do
+ with_env_tz "US/Eastern" do
+ source = ActiveSupport::TimeWithZone.new(@utc_time, @zone).freeze
+ time = source.to_time
+
+ assert_instance_of Time, time
+ assert_equal @utc_time, time.getutc
+ assert_instance_of Time, time.getutc
+ assert_equal @utc_offset, time.utc_offset
+ assert_not_predicate time, :frozen?
+
+ source = ActiveSupport::TimeWithZone.new(@date_time, @zone).freeze
+ time = source.to_time
+
+ assert_instance_of Time, time
+ assert_equal @date_time, time.getutc
+ assert_instance_of Time, time.getutc
+ assert_equal @utc_offset, time.utc_offset
+ assert_not_predicate time, :frozen?
+ end
+ end
+ end
+
+ def test_twz_to_time_frozen_does_not_preserve_time_zone
+ with_preserve_timezone(false) do
+ with_env_tz "US/Eastern" do
+ source = ActiveSupport::TimeWithZone.new(@utc_time, @zone).freeze
+ time = source.to_time
+
+ assert_instance_of Time, time
+ assert_equal @utc_time, time.getutc
+ assert_instance_of Time, time.getutc
+ assert_equal @system_offset, time.utc_offset
+ assert_not_predicate time, :frozen?
+
+ source = ActiveSupport::TimeWithZone.new(@date_time, @zone).freeze
+ time = source.to_time
+
+ assert_instance_of Time, time
+ assert_equal @date_time, time.getutc
+ assert_instance_of Time, time.getutc
+ assert_equal @system_offset, time.utc_offset
+ assert_not_predicate time, :frozen?
+ end
+ end
+ end
+
def test_string_to_time_preserves_timezone
with_preserve_timezone(true) do
with_env_tz "US/Eastern" do
- time = "2016-04-23T15:11:12+01:00".to_time
+ source = "2016-04-23T15:11:12+01:00"
+ time = source.to_time
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
@@ -116,11 +235,40 @@ class DateAndTimeCompatibilityTest < ActiveSupport::TestCase
def test_string_to_time_does_not_preserve_time_zone
with_preserve_timezone(false) do
with_env_tz "US/Eastern" do
- time = "2016-04-23T15:11:12+01:00".to_time
+ source = "2016-04-23T15:11:12+01:00"
+ time = source.to_time
+
+ assert_instance_of Time, time
+ assert_equal @utc_time, time.getutc
+ assert_equal @system_offset, time.utc_offset
+ end
+ end
+ end
+
+ def test_string_to_time_frozen_preserves_timezone
+ with_preserve_timezone(true) do
+ with_env_tz "US/Eastern" do
+ source = "2016-04-23T15:11:12+01:00".freeze
+ time = source.to_time
+
+ assert_instance_of Time, time
+ assert_equal @utc_time, time.getutc
+ assert_equal @utc_offset, time.utc_offset
+ assert_not_predicate time, :frozen?
+ end
+ end
+ end
+
+ def test_string_to_time_frozen_does_not_preserve_time_zone
+ with_preserve_timezone(false) do
+ with_env_tz "US/Eastern" do
+ source = "2016-04-23T15:11:12+01:00".freeze
+ time = source.to_time
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
assert_equal @system_offset, time.utc_offset
+ assert_not_predicate time, :frozen?
end
end
end
diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb
index 50bb1004f7..23d17956df 100644
--- a/activesupport/test/core_ext/date_ext_test.rb
+++ b/activesupport/test/core_ext/date_ext_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/time"
require "core_ext/date_and_time_behavior"
@@ -93,11 +95,11 @@ class DateExtCalculationsTest < ActiveSupport::TestCase
end
def test_beginning_of_week_in_calendar_reform
- assert_equal Date.new(1582, 10, 1), Date.new(1582, 10, 15).beginning_of_week #friday
+ assert_equal Date.new(1582, 10, 1), Date.new(1582, 10, 15).beginning_of_week # friday
end
def test_end_of_week_in_calendar_reform
- assert_equal Date.new(1582, 10, 17), Date.new(1582, 10, 4).end_of_week #thursday
+ assert_equal Date.new(1582, 10, 17), Date.new(1582, 10, 4).end_of_week # thursday
end
def test_end_of_year
@@ -118,10 +120,6 @@ class DateExtCalculationsTest < ActiveSupport::TestCase
assert_equal Date.new(1582, 10, 4), Date.new(1583, 10, 14).prev_year
end
- def test_last_year
- assert_equal Date.new(2004, 6, 5), Date.new(2005, 6, 5).last_year
- end
-
def test_last_year_in_leap_years
assert_equal Date.new(1999, 2, 28), Date.new(2000, 2, 29).last_year
end
@@ -146,7 +144,7 @@ class DateExtCalculationsTest < ActiveSupport::TestCase
assert_equal Date.new(2012, 9, 28), Date.new(2005, 2, 28).advance(years: 7, months: 7)
assert_equal Date.new(2013, 10, 3), Date.new(2005, 2, 28).advance(years: 7, months: 19, days: 5)
assert_equal Date.new(2013, 10, 17), Date.new(2005, 2, 28).advance(years: 7, months: 19, weeks: 2, days: 5)
- assert_equal Date.new(2005, 2, 28), Date.new(2004, 2, 29).advance(years: 1) #leap day plus one year
+ assert_equal Date.new(2005, 2, 28), Date.new(2004, 2, 29).advance(years: 1) # leap day plus one year
end
def test_advance_does_first_years_and_then_days
@@ -183,10 +181,6 @@ class DateExtCalculationsTest < ActiveSupport::TestCase
assert_equal Date.new(1582, 10, 18), Date.new(1582, 10, 4).next_week
end
- def test_last_month_on_31st
- assert_equal Date.new(2004, 2, 29), Date.new(2004, 3, 31).last_month
- end
-
def test_last_quarter_on_31st
assert_equal Date.new(2004, 2, 29), Date.new(2004, 5, 31).last_quarter
end
diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb
index e3b31c10f5..f4c9dfcb25 100644
--- a/activesupport/test/core_ext/date_time_ext_test.rb
+++ b/activesupport/test/core_ext/date_time_ext_test.rb
@@ -1,11 +1,13 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/time"
require "core_ext/date_and_time_behavior"
require "time_zone_test_helpers"
class DateTimeExtCalculationsTest < ActiveSupport::TestCase
- def date_time_init(year, month, day, hour, minute, second, *args)
- DateTime.civil(year, month, day, hour, minute, second)
+ def date_time_init(year, month, day, hour, minute, second, usec = 0)
+ DateTime.civil(year, month, day, hour, minute, second + (usec / 1000000))
end
include DateAndTimeBehavior
@@ -113,7 +115,7 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
end
def test_end_of_day
- assert_equal DateTime.civil(2005, 2, 4, 23, 59, 59), DateTime.civil(2005, 2, 4, 10, 10, 10).end_of_day
+ assert_equal DateTime.civil(2005, 2, 4, 23, 59, Rational(59999999999, 1000000000)), DateTime.civil(2005, 2, 4, 10, 10, 10).end_of_day
end
def test_beginning_of_hour
@@ -121,7 +123,7 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
end
def test_end_of_hour
- assert_equal DateTime.civil(2005, 2, 4, 19, 59, 59), DateTime.civil(2005, 2, 4, 19, 30, 10).end_of_hour
+ assert_equal DateTime.civil(2005, 2, 4, 19, 59, Rational(59999999999, 1000000000)), DateTime.civil(2005, 2, 4, 19, 30, 10).end_of_hour
end
def test_beginning_of_minute
@@ -129,17 +131,13 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
end
def test_end_of_minute
- assert_equal DateTime.civil(2005, 2, 4, 19, 30, 59), DateTime.civil(2005, 2, 4, 19, 30, 10).end_of_minute
+ assert_equal DateTime.civil(2005, 2, 4, 19, 30, Rational(59999999999, 1000000000)), DateTime.civil(2005, 2, 4, 19, 30, 10).end_of_minute
end
def test_end_of_month
- assert_equal DateTime.civil(2005, 3, 31, 23, 59, 59), DateTime.civil(2005, 3, 20, 10, 10, 10).end_of_month
- assert_equal DateTime.civil(2005, 2, 28, 23, 59, 59), DateTime.civil(2005, 2, 20, 10, 10, 10).end_of_month
- assert_equal DateTime.civil(2005, 4, 30, 23, 59, 59), DateTime.civil(2005, 4, 20, 10, 10, 10).end_of_month
- end
-
- def test_last_year
- assert_equal DateTime.civil(2004, 6, 5, 10), DateTime.civil(2005, 6, 5, 10, 0, 0).last_year
+ assert_equal DateTime.civil(2005, 3, 31, 23, 59, Rational(59999999999, 1000000000)), DateTime.civil(2005, 3, 20, 10, 10, 10).end_of_month
+ assert_equal DateTime.civil(2005, 2, 28, 23, 59, Rational(59999999999, 1000000000)), DateTime.civil(2005, 2, 20, 10, 10, 10).end_of_month
+ assert_equal DateTime.civil(2005, 4, 30, 23, 59, Rational(59999999999, 1000000000)), DateTime.civil(2005, 4, 20, 10, 10, 10).end_of_month
end
def test_ago
@@ -162,12 +160,22 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal DateTime.civil(2006, 2, 22, 15, 15, 10), DateTime.civil(2005, 2, 22, 15, 15, 10).change(year: 2006)
assert_equal DateTime.civil(2005, 6, 22, 15, 15, 10), DateTime.civil(2005, 2, 22, 15, 15, 10).change(month: 6)
assert_equal DateTime.civil(2012, 9, 22, 15, 15, 10), DateTime.civil(2005, 2, 22, 15, 15, 10).change(year: 2012, month: 9)
- assert_equal DateTime.civil(2005, 2, 22, 16), DateTime.civil(2005, 2, 22, 15, 15, 10).change(hour: 16)
- assert_equal DateTime.civil(2005, 2, 22, 16, 45), DateTime.civil(2005, 2, 22, 15, 15, 10).change(hour: 16, min: 45)
- assert_equal DateTime.civil(2005, 2, 22, 15, 45), DateTime.civil(2005, 2, 22, 15, 15, 10).change(min: 45)
+ assert_equal DateTime.civil(2005, 2, 22, 16), DateTime.civil(2005, 2, 22, 15, 15, 10).change(hour: 16)
+ assert_equal DateTime.civil(2005, 2, 22, 16, 45), DateTime.civil(2005, 2, 22, 15, 15, 10).change(hour: 16, min: 45)
+ assert_equal DateTime.civil(2005, 2, 22, 15, 45), DateTime.civil(2005, 2, 22, 15, 15, 10).change(min: 45)
+
+ # datetime with non-zero offset
+ assert_equal DateTime.civil(2005, 2, 22, 15, 15, 10, Rational(-5, 24)), DateTime.civil(2005, 2, 22, 15, 15, 10, 0).change(offset: Rational(-5, 24))
# datetime with fractions of a second
assert_equal DateTime.civil(2005, 2, 1, 15, 15, 10.7), DateTime.civil(2005, 2, 22, 15, 15, 10.7).change(day: 1)
+ assert_equal DateTime.civil(2005, 1, 2, 11, 22, Rational(33000008, 1000000)), DateTime.civil(2005, 1, 2, 11, 22, 33).change(usec: 8)
+ assert_equal DateTime.civil(2005, 1, 2, 11, 22, Rational(33000008, 1000000)), DateTime.civil(2005, 1, 2, 11, 22, 33).change(nsec: 8000)
+ assert_raise(ArgumentError) { DateTime.civil(2005, 1, 2, 11, 22, 0).change(usec: 1, nsec: 1) }
+ assert_raise(ArgumentError) { DateTime.civil(2005, 1, 2, 11, 22, 0).change(usec: 1000000) }
+ assert_raise(ArgumentError) { DateTime.civil(2005, 1, 2, 11, 22, 0).change(nsec: 1000000000) }
+ assert_nothing_raised { DateTime.civil(2005, 1, 2, 11, 22, 0).change(usec: 999999) }
+ assert_nothing_raised { DateTime.civil(2005, 1, 2, 11, 22, 0).change(nsec: 999999999) }
end
def test_advance
@@ -179,7 +187,7 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal DateTime.civil(2013, 10, 3, 15, 15, 10), DateTime.civil(2005, 2, 28, 15, 15, 10).advance(years: 7, months: 19, days: 5)
assert_equal DateTime.civil(2013, 10, 17, 15, 15, 10), DateTime.civil(2005, 2, 28, 15, 15, 10).advance(years: 7, months: 19, weeks: 2, days: 5)
assert_equal DateTime.civil(2001, 12, 27, 15, 15, 10), DateTime.civil(2005, 2, 28, 15, 15, 10).advance(years: -3, months: -2, days: -1)
- assert_equal DateTime.civil(2005, 2, 28, 15, 15, 10), DateTime.civil(2004, 2, 29, 15, 15, 10).advance(years: 1) #leap day plus one year
+ assert_equal DateTime.civil(2005, 2, 28, 15, 15, 10), DateTime.civil(2004, 2, 29, 15, 15, 10).advance(years: 1) # leap day plus one year
assert_equal DateTime.civil(2005, 2, 28, 20, 15, 10), DateTime.civil(2005, 2, 28, 15, 15, 10).advance(hours: 5)
assert_equal DateTime.civil(2005, 2, 28, 15, 22, 10), DateTime.civil(2005, 2, 28, 15, 15, 10).advance(minutes: 7)
assert_equal DateTime.civil(2005, 2, 28, 15, 15, 19), DateTime.civil(2005, 2, 28, 15, 15, 10).advance(seconds: 9)
@@ -214,10 +222,6 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal DateTime.civil(2016, 2, 29), DateTime.civil(2016, 3, 7).last_week
end
- def test_last_month_on_31st
- assert_equal DateTime.civil(2004, 2, 29), DateTime.civil(2004, 3, 31).last_month
- end
-
def test_last_quarter_on_31st
assert_equal DateTime.civil(2004, 2, 29), DateTime.civil(2004, 5, 31).last_quarter
end
diff --git a/activesupport/test/core_ext/digest/uuid_test.rb b/activesupport/test/core_ext/digest/uuid_test.rb
index 866a03259a..94cb7d9418 100644
--- a/activesupport/test/core_ext/digest/uuid_test.rb
+++ b/activesupport/test/core_ext/digest/uuid_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/core_ext/digest/uuid"
diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb
index 6a275d1d5b..4a02f27def 100644
--- a/activesupport/test/core_ext/duration_test.rb
+++ b/activesupport/test/core_ext/duration_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/inflector"
require "active_support/time"
@@ -69,6 +71,8 @@ class DurationTest < ActiveSupport::TestCase
assert_equal "7 days", 7.days.inspect
assert_equal "1 week", 1.week.inspect
assert_equal "2 weeks", 1.fortnight.inspect
+ assert_equal "0 seconds", (10 % 5.seconds).inspect
+ assert_equal "10 minutes", (10.minutes + 0.seconds).inspect
end
def test_inspect_locale
@@ -84,12 +88,81 @@ class DurationTest < ActiveSupport::TestCase
assert_nothing_raised { Date.today - Date.today }
end
+ def test_plus
+ assert_equal 2.seconds, 1.second + 1.second
+ assert_instance_of ActiveSupport::Duration, 1.second + 1.second
+ assert_equal 2.seconds, 1.second + 1
+ assert_instance_of ActiveSupport::Duration, 1.second + 1
+ end
+
+ def test_minus
+ assert_equal 1.second, 2.seconds - 1.second
+ assert_instance_of ActiveSupport::Duration, 2.seconds - 1.second
+ assert_equal 1.second, 2.seconds - 1
+ assert_instance_of ActiveSupport::Duration, 2.seconds - 1
+ assert_equal 1.second, 2 - 1.second
+ assert_instance_of ActiveSupport::Duration, 2.seconds - 1
+ end
+
+ def test_multiply
+ assert_equal 7.days, 1.day * 7
+ assert_instance_of ActiveSupport::Duration, 1.day * 7
+ assert_equal 86400, 1.day * 1.second
+ end
+
+ def test_divide
+ assert_equal 1.day, 7.days / 7
+ assert_instance_of ActiveSupport::Duration, 7.days / 7
+
+ assert_equal 1.hour, 1.day / 24
+ assert_instance_of ActiveSupport::Duration, 1.day / 24
+
+ assert_equal 24, 86400 / 1.hour
+ assert_kind_of Integer, 86400 / 1.hour
+
+ assert_equal 24, 1.day / 1.hour
+ assert_kind_of Integer, 1.day / 1.hour
+
+ assert_equal 1, 1.day / 1.day
+ assert_kind_of Integer, 1.day / 1.hour
+ end
+
+ def test_modulo
+ assert_equal 1.minute, 5.minutes % 120
+ assert_instance_of ActiveSupport::Duration, 5.minutes % 120
+
+ assert_equal 1.minute, 5.minutes % 2.minutes
+ assert_instance_of ActiveSupport::Duration, 5.minutes % 2.minutes
+
+ assert_equal 1.minute, 5.minutes % 120.seconds
+ assert_instance_of ActiveSupport::Duration, 5.minutes % 120.seconds
+
+ assert_equal 5.minutes, 5.minutes % 1.hour
+ assert_instance_of ActiveSupport::Duration, 5.minutes % 1.hour
+
+ assert_equal 1.day, 36.days % 604800
+ assert_instance_of ActiveSupport::Duration, 36.days % 604800
+
+ assert_equal 1.day, 36.days % 7.days
+ assert_instance_of ActiveSupport::Duration, 36.days % 7.days
+
+ assert_equal 800.seconds, 8000 % 1.hour
+ assert_instance_of ActiveSupport::Duration, 8000 % 1.hour
+
+ assert_equal 1.month, 13.months % 1.year
+ assert_instance_of ActiveSupport::Duration, 13.months % 1.year
+ end
+
+ def test_date_added_with_multiplied_duration
+ assert_equal Date.civil(2017, 1, 3), Date.civil(2017, 1, 1) + 1.day * 2
+ end
+
def test_plus_with_time
assert_equal 1 + 1.second, 1.second + 1, "Duration + Numeric should == Numeric + Duration"
end
def test_time_plus_duration_returns_same_time_datatype
- twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Moscow"] , Time.utc(2016, 4, 28, 00, 45))
+ twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Moscow"], Time.utc(2016, 4, 28, 00, 45))
now = Time.now.utc
%w( second minute hour day week month year ).each do |unit|
assert_equal((now + 1.send(unit)).class, Time, "Time + 1.#{unit} must be Time")
@@ -179,6 +252,19 @@ class DurationTest < ActiveSupport::TestCase
Time.zone = nil
end
+ def test_before_and_afer
+ t = Time.local(2000)
+ assert_equal t + 1, 1.second.after(t)
+ assert_equal t - 1, 1.second.before(t)
+ end
+
+ def test_before_and_after_without_argument
+ Time.stub(:now, Time.local(2000)) do
+ assert_equal Time.now - 1.second, 1.second.before
+ assert_equal Time.now + 1.second, 1.second.after
+ end
+ end
+
def test_adding_hours_across_dst_boundary
with_env_tz "CET" do
assert_equal Time.local(2009, 3, 29, 0, 0, 0) + 24.hours, Time.local(2009, 3, 30, 1, 0, 0)
@@ -237,6 +323,168 @@ class DurationTest < ActiveSupport::TestCase
assert_equal(1, (61 <=> 1.minute))
end
+ def test_implicit_coercion
+ assert_equal 2.days, 2 * 1.day
+ assert_instance_of ActiveSupport::Duration, 2 * 1.day
+ assert_equal Time.utc(2017, 1, 3), Time.utc(2017, 1, 1) + 2 * 1.day
+ assert_equal Date.civil(2017, 1, 3), Date.civil(2017, 1, 1) + 2 * 1.day
+ end
+
+ def test_scalar_coerce
+ scalar = ActiveSupport::Duration::Scalar.new(10)
+ assert_instance_of ActiveSupport::Duration::Scalar, 10 + scalar
+ assert_instance_of ActiveSupport::Duration, 10.seconds + scalar
+ end
+
+ def test_scalar_delegations
+ scalar = ActiveSupport::Duration::Scalar.new(10)
+ assert_kind_of Float, scalar.to_f
+ assert_kind_of Integer, scalar.to_i
+ assert_kind_of String, scalar.to_s
+ end
+
+ def test_scalar_unary_minus
+ scalar = ActiveSupport::Duration::Scalar.new(10)
+
+ assert_equal(-10, -scalar)
+ assert_instance_of ActiveSupport::Duration::Scalar, -scalar
+ end
+
+ def test_scalar_compare
+ scalar = ActiveSupport::Duration::Scalar.new(10)
+
+ assert_equal(1, scalar <=> 5)
+ assert_equal(0, scalar <=> 10)
+ assert_equal(-1, scalar <=> 15)
+ assert_nil(scalar <=> "foo")
+ end
+
+ def test_scalar_plus
+ scalar = ActiveSupport::Duration::Scalar.new(10)
+
+ assert_equal 20, 10 + scalar
+ assert_instance_of ActiveSupport::Duration::Scalar, 10 + scalar
+ assert_equal 20, scalar + 10
+ assert_instance_of ActiveSupport::Duration::Scalar, scalar + 10
+ assert_equal 20, 10.seconds + scalar
+ assert_instance_of ActiveSupport::Duration, 10.seconds + scalar
+ assert_equal 20, scalar + 10.seconds
+ assert_instance_of ActiveSupport::Duration, scalar + 10.seconds
+
+ exception = assert_raises(TypeError) do
+ scalar + "foo"
+ end
+
+ assert_equal "no implicit conversion of String into ActiveSupport::Duration::Scalar", exception.message
+ end
+
+ def test_scalar_plus_parts
+ scalar = ActiveSupport::Duration::Scalar.new(10)
+
+ assert_equal({ days: 1, seconds: 10 }, (scalar + 1.day).parts)
+ assert_equal({ days: -1, seconds: 10 }, (scalar + -1.day).parts)
+ end
+
+ def test_scalar_minus
+ scalar = ActiveSupport::Duration::Scalar.new(10)
+
+ assert_equal 10, 20 - scalar
+ assert_instance_of ActiveSupport::Duration::Scalar, 20 - scalar
+ assert_equal 5, scalar - 5
+ assert_instance_of ActiveSupport::Duration::Scalar, scalar - 5
+ assert_equal 10, 20.seconds - scalar
+ assert_instance_of ActiveSupport::Duration, 20.seconds - scalar
+ assert_equal 5, scalar - 5.seconds
+ assert_instance_of ActiveSupport::Duration, scalar - 5.seconds
+
+ assert_equal({ days: -1, seconds: 10 }, (scalar - 1.day).parts)
+ assert_equal({ days: 1, seconds: 10 }, (scalar - -1.day).parts)
+
+ exception = assert_raises(TypeError) do
+ scalar - "foo"
+ end
+
+ assert_equal "no implicit conversion of String into ActiveSupport::Duration::Scalar", exception.message
+ end
+
+ def test_scalar_minus_parts
+ scalar = ActiveSupport::Duration::Scalar.new(10)
+
+ assert_equal({ days: -1, seconds: 10 }, (scalar - 1.day).parts)
+ assert_equal({ days: 1, seconds: 10 }, (scalar - -1.day).parts)
+ end
+
+ def test_scalar_multiply
+ scalar = ActiveSupport::Duration::Scalar.new(5)
+
+ assert_equal 10, 2 * scalar
+ assert_instance_of ActiveSupport::Duration::Scalar, 2 * scalar
+ assert_equal 10, scalar * 2
+ assert_instance_of ActiveSupport::Duration::Scalar, scalar * 2
+ assert_equal 10, 2.seconds * scalar
+ assert_instance_of ActiveSupport::Duration, 2.seconds * scalar
+ assert_equal 10, scalar * 2.seconds
+ assert_instance_of ActiveSupport::Duration, scalar * 2.seconds
+
+ exception = assert_raises(TypeError) do
+ scalar * "foo"
+ end
+
+ assert_equal "no implicit conversion of String into ActiveSupport::Duration::Scalar", exception.message
+ end
+
+ def test_scalar_multiply_parts
+ scalar = ActiveSupport::Duration::Scalar.new(1)
+ assert_equal({ days: 2 }, (scalar * 2.days).parts)
+ assert_equal(172800, (scalar * 2.days).value)
+ assert_equal({ days: -2 }, (scalar * -2.days).parts)
+ assert_equal(-172800, (scalar * -2.days).value)
+ end
+
+ def test_scalar_divide
+ scalar = ActiveSupport::Duration::Scalar.new(10)
+
+ assert_equal 10, 100 / scalar
+ assert_instance_of ActiveSupport::Duration::Scalar, 100 / scalar
+ assert_equal 5, scalar / 2
+ assert_instance_of ActiveSupport::Duration::Scalar, scalar / 2
+ assert_equal 10, 100.seconds / scalar
+ assert_instance_of ActiveSupport::Duration, 100.seconds / scalar
+ assert_equal 5, scalar / 2.seconds
+ assert_kind_of Integer, scalar / 2.seconds
+
+ exception = assert_raises(TypeError) do
+ scalar / "foo"
+ end
+
+ assert_equal "no implicit conversion of String into ActiveSupport::Duration::Scalar", exception.message
+ end
+
+ def test_scalar_modulo
+ scalar = ActiveSupport::Duration::Scalar.new(10)
+
+ assert_equal 1, 31 % scalar
+ assert_instance_of ActiveSupport::Duration::Scalar, 31 % scalar
+ assert_equal 1, scalar % 3
+ assert_instance_of ActiveSupport::Duration::Scalar, scalar % 3
+ assert_equal 1, 31.seconds % scalar
+ assert_instance_of ActiveSupport::Duration, 31.seconds % scalar
+ assert_equal 1, scalar % 3.seconds
+ assert_instance_of ActiveSupport::Duration, scalar % 3.seconds
+
+ exception = assert_raises(TypeError) do
+ scalar % "foo"
+ end
+
+ assert_equal "no implicit conversion of String into ActiveSupport::Duration::Scalar", exception.message
+ end
+
+ def test_scalar_modulo_parts
+ scalar = ActiveSupport::Duration::Scalar.new(82800)
+ assert_equal({ hours: 1 }, (scalar % 2.hours).parts)
+ assert_equal(3600, (scalar % 2.hours).value)
+ end
+
def test_twelve_months_equals_one_year
assert_equal 12.months, 1.year
end
diff --git a/activesupport/test/core_ext/enumerable_test.rb b/activesupport/test/core_ext/enumerable_test.rb
index 4f1ab993b8..8d71320931 100644
--- a/activesupport/test/core_ext/enumerable_test.rb
+++ b/activesupport/test/core_ext/enumerable_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/core_ext/array"
require "active_support/core_ext/enumerable"
@@ -171,10 +173,8 @@ class EnumerableTests < ActiveSupport::TestCase
assert_equal({ 5 => Payment.new(5), 15 => Payment.new(15), 10 => Payment.new(10) },
payments.index_by(&:price))
assert_equal Enumerator, payments.index_by.class
- if Enumerator.method_defined? :size
- assert_nil payments.index_by.size
- assert_equal 42, (1..42).index_by.size
- end
+ assert_nil payments.index_by.size
+ assert_equal 42, (1..42).index_by.size
assert_equal({ 5 => Payment.new(5), 15 => Payment.new(15), 10 => Payment.new(10) },
payments.index_by.each(&:price))
end
diff --git a/activesupport/test/core_ext/file_test.rb b/activesupport/test/core_ext/file_test.rb
index df5d09acd0..23e3c277cc 100644
--- a/activesupport/test/core_ext/file_test.rb
+++ b/activesupport/test/core_ext/file_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/core_ext/file"
diff --git a/activesupport/test/core_ext/hash/transform_keys_test.rb b/activesupport/test/core_ext/hash/transform_keys_test.rb
index 7a11d827f8..b9e41f7b25 100644
--- a/activesupport/test/core_ext/hash/transform_keys_test.rb
+++ b/activesupport/test/core_ext/hash/transform_keys_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/core_ext/hash/keys"
diff --git a/activesupport/test/core_ext/hash/transform_values_test.rb b/activesupport/test/core_ext/hash/transform_values_test.rb
index f2ac4ce6ce..d34b7fa7b9 100644
--- a/activesupport/test/core_ext/hash/transform_values_test.rb
+++ b/activesupport/test/core_ext/hash/transform_values_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/core_ext/hash/indifferent_access"
require "active_support/core_ext/hash/transform_values"
diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb
index 05813ad388..17952e9fc7 100644
--- a/activesupport/test/core_ext/hash_ext_test.rb
+++ b/activesupport/test/core_ext/hash_ext_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/core_ext/hash"
require "bigdecimal"
@@ -8,31 +10,6 @@ require "active_support/core_ext/object/deep_dup"
require "active_support/inflections"
class HashExtTest < ActiveSupport::TestCase
- class IndifferentHash < ActiveSupport::HashWithIndifferentAccess
- end
-
- class SubclassingArray < Array
- end
-
- class SubclassingHash < Hash
- end
-
- class NonIndifferentHash < Hash
- def nested_under_indifferent_access
- self
- end
- end
-
- class HashByConversion
- def initialize(hash)
- @hash = hash
- end
-
- def to_hash
- @hash
- end
- end
-
def setup
@strings = { "a" => 1, "b" => 2 }
@nested_strings = { "a" => { "b" => { "c" => 3 } } }
@@ -262,461 +239,6 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal({ "a" => { b: { "c" => 3 } } }, @nested_mixed)
end
- def test_symbolize_keys_for_hash_with_indifferent_access
- assert_instance_of Hash, @symbols.with_indifferent_access.symbolize_keys
- assert_equal @symbols, @symbols.with_indifferent_access.symbolize_keys
- assert_equal @symbols, @strings.with_indifferent_access.symbolize_keys
- assert_equal @symbols, @mixed.with_indifferent_access.symbolize_keys
- end
-
- def test_deep_symbolize_keys_for_hash_with_indifferent_access
- assert_instance_of Hash, @nested_symbols.with_indifferent_access.deep_symbolize_keys
- assert_equal @nested_symbols, @nested_symbols.with_indifferent_access.deep_symbolize_keys
- assert_equal @nested_symbols, @nested_strings.with_indifferent_access.deep_symbolize_keys
- assert_equal @nested_symbols, @nested_mixed.with_indifferent_access.deep_symbolize_keys
- end
-
- def test_symbolize_keys_bang_for_hash_with_indifferent_access
- assert_raise(NoMethodError) { @symbols.with_indifferent_access.dup.symbolize_keys! }
- assert_raise(NoMethodError) { @strings.with_indifferent_access.dup.symbolize_keys! }
- assert_raise(NoMethodError) { @mixed.with_indifferent_access.dup.symbolize_keys! }
- end
-
- def test_deep_symbolize_keys_bang_for_hash_with_indifferent_access
- assert_raise(NoMethodError) { @nested_symbols.with_indifferent_access.deep_dup.deep_symbolize_keys! }
- assert_raise(NoMethodError) { @nested_strings.with_indifferent_access.deep_dup.deep_symbolize_keys! }
- assert_raise(NoMethodError) { @nested_mixed.with_indifferent_access.deep_dup.deep_symbolize_keys! }
- end
-
- def test_symbolize_keys_preserves_keys_that_cant_be_symbolized_for_hash_with_indifferent_access
- assert_equal @illegal_symbols, @illegal_symbols.with_indifferent_access.symbolize_keys
- assert_raise(NoMethodError) { @illegal_symbols.with_indifferent_access.dup.symbolize_keys! }
- end
-
- def test_deep_symbolize_keys_preserves_keys_that_cant_be_symbolized_for_hash_with_indifferent_access
- assert_equal @nested_illegal_symbols, @nested_illegal_symbols.with_indifferent_access.deep_symbolize_keys
- assert_raise(NoMethodError) { @nested_illegal_symbols.with_indifferent_access.deep_dup.deep_symbolize_keys! }
- end
-
- def test_symbolize_keys_preserves_integer_keys_for_hash_with_indifferent_access
- assert_equal @integers, @integers.with_indifferent_access.symbolize_keys
- assert_raise(NoMethodError) { @integers.with_indifferent_access.dup.symbolize_keys! }
- end
-
- def test_deep_symbolize_keys_preserves_integer_keys_for_hash_with_indifferent_access
- assert_equal @nested_integers, @nested_integers.with_indifferent_access.deep_symbolize_keys
- assert_raise(NoMethodError) { @nested_integers.with_indifferent_access.deep_dup.deep_symbolize_keys! }
- end
-
- def test_stringify_keys_for_hash_with_indifferent_access
- assert_instance_of ActiveSupport::HashWithIndifferentAccess, @symbols.with_indifferent_access.stringify_keys
- assert_equal @strings, @symbols.with_indifferent_access.stringify_keys
- assert_equal @strings, @strings.with_indifferent_access.stringify_keys
- assert_equal @strings, @mixed.with_indifferent_access.stringify_keys
- end
-
- def test_deep_stringify_keys_for_hash_with_indifferent_access
- assert_instance_of ActiveSupport::HashWithIndifferentAccess, @nested_symbols.with_indifferent_access.deep_stringify_keys
- assert_equal @nested_strings, @nested_symbols.with_indifferent_access.deep_stringify_keys
- assert_equal @nested_strings, @nested_strings.with_indifferent_access.deep_stringify_keys
- assert_equal @nested_strings, @nested_mixed.with_indifferent_access.deep_stringify_keys
- end
-
- def test_stringify_keys_bang_for_hash_with_indifferent_access
- assert_instance_of ActiveSupport::HashWithIndifferentAccess, @symbols.with_indifferent_access.dup.stringify_keys!
- assert_equal @strings, @symbols.with_indifferent_access.dup.stringify_keys!
- assert_equal @strings, @strings.with_indifferent_access.dup.stringify_keys!
- assert_equal @strings, @mixed.with_indifferent_access.dup.stringify_keys!
- end
-
- def test_deep_stringify_keys_bang_for_hash_with_indifferent_access
- assert_instance_of ActiveSupport::HashWithIndifferentAccess, @nested_symbols.with_indifferent_access.dup.deep_stringify_keys!
- assert_equal @nested_strings, @nested_symbols.with_indifferent_access.deep_dup.deep_stringify_keys!
- assert_equal @nested_strings, @nested_strings.with_indifferent_access.deep_dup.deep_stringify_keys!
- assert_equal @nested_strings, @nested_mixed.with_indifferent_access.deep_dup.deep_stringify_keys!
- end
-
- def test_nested_under_indifferent_access
- foo = { "foo" => SubclassingHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access
- assert_kind_of ActiveSupport::HashWithIndifferentAccess, foo["foo"]
-
- foo = { "foo" => NonIndifferentHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access
- assert_kind_of NonIndifferentHash, foo["foo"]
-
- foo = { "foo" => IndifferentHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access
- assert_kind_of IndifferentHash, foo["foo"]
- end
-
- def test_indifferent_assorted
- @strings = @strings.with_indifferent_access
- @symbols = @symbols.with_indifferent_access
- @mixed = @mixed.with_indifferent_access
-
- assert_equal "a", @strings.__send__(:convert_key, :a)
-
- assert_equal 1, @strings.fetch("a")
- assert_equal 1, @strings.fetch(:a.to_s)
- assert_equal 1, @strings.fetch(:a)
-
- hashes = { :@strings => @strings, :@symbols => @symbols, :@mixed => @mixed }
- method_map = { '[]': 1, fetch: 1, values_at: [1],
- has_key?: true, include?: true, key?: true,
- member?: true }
-
- hashes.each do |name, hash|
- method_map.sort_by(&:to_s).each do |meth, expected|
- assert_equal(expected, hash.__send__(meth, "a"),
- "Calling #{name}.#{meth} 'a'")
- assert_equal(expected, hash.__send__(meth, :a),
- "Calling #{name}.#{meth} :a")
- end
- end
-
- assert_equal [1, 2], @strings.values_at("a", "b")
- assert_equal [1, 2], @strings.values_at(:a, :b)
- assert_equal [1, 2], @symbols.values_at("a", "b")
- assert_equal [1, 2], @symbols.values_at(:a, :b)
- assert_equal [1, 2], @mixed.values_at("a", "b")
- assert_equal [1, 2], @mixed.values_at(:a, :b)
- end
-
- def test_indifferent_reading
- hash = HashWithIndifferentAccess.new
- hash["a"] = 1
- hash["b"] = true
- hash["c"] = false
- hash["d"] = nil
-
- assert_equal 1, hash[:a]
- assert_equal true, hash[:b]
- assert_equal false, hash[:c]
- assert_nil hash[:d]
- assert_nil hash[:e]
- end
-
- def test_indifferent_reading_with_nonnil_default
- hash = HashWithIndifferentAccess.new(1)
- hash["a"] = 1
- hash["b"] = true
- hash["c"] = false
- hash["d"] = nil
-
- assert_equal 1, hash[:a]
- assert_equal true, hash[:b]
- assert_equal false, hash[:c]
- assert_nil hash[:d]
- assert_equal 1, hash[:e]
- end
-
- def test_indifferent_writing
- hash = HashWithIndifferentAccess.new
- hash[:a] = 1
- hash["b"] = 2
- hash[3] = 3
-
- assert_equal 1, hash["a"]
- assert_equal 2, hash["b"]
- assert_equal 1, hash[:a]
- assert_equal 2, hash[:b]
- assert_equal 3, hash[3]
- end
-
- def test_indifferent_update
- hash = HashWithIndifferentAccess.new
- hash[:a] = "a"
- hash["b"] = "b"
-
- updated_with_strings = hash.update(@strings)
- updated_with_symbols = hash.update(@symbols)
- updated_with_mixed = hash.update(@mixed)
-
- assert_equal 1, updated_with_strings[:a]
- assert_equal 1, updated_with_strings["a"]
- assert_equal 2, updated_with_strings["b"]
-
- assert_equal 1, updated_with_symbols[:a]
- assert_equal 2, updated_with_symbols["b"]
- assert_equal 2, updated_with_symbols[:b]
-
- assert_equal 1, updated_with_mixed[:a]
- assert_equal 2, updated_with_mixed["b"]
-
- assert [updated_with_strings, updated_with_symbols, updated_with_mixed].all? { |h| h.keys.size == 2 }
- end
-
- def test_update_with_to_hash_conversion
- hash = HashWithIndifferentAccess.new
- hash.update HashByConversion.new(a: 1)
- assert_equal 1, hash["a"]
- end
-
- def test_indifferent_merging
- hash = HashWithIndifferentAccess.new
- hash[:a] = "failure"
- hash["b"] = "failure"
-
- other = { "a" => 1, :b => 2 }
-
- merged = hash.merge(other)
-
- assert_equal HashWithIndifferentAccess, merged.class
- assert_equal 1, merged[:a]
- assert_equal 2, merged["b"]
-
- hash.update(other)
-
- assert_equal 1, hash[:a]
- assert_equal 2, hash["b"]
- end
-
- def test_merge_with_to_hash_conversion
- hash = HashWithIndifferentAccess.new
- merged = hash.merge HashByConversion.new(a: 1)
- assert_equal 1, merged["a"]
- end
-
- def test_indifferent_replace
- hash = HashWithIndifferentAccess.new
- hash[:a] = 42
-
- replaced = hash.replace(b: 12)
-
- assert hash.key?("b")
- assert !hash.key?(:a)
- assert_equal 12, hash[:b]
- assert_same hash, replaced
- end
-
- def test_replace_with_to_hash_conversion
- hash = HashWithIndifferentAccess.new
- hash[:a] = 42
-
- replaced = hash.replace(HashByConversion.new(b: 12))
-
- assert hash.key?("b")
- assert !hash.key?(:a)
- assert_equal 12, hash[:b]
- assert_same hash, replaced
- end
-
- def test_indifferent_merging_with_block
- hash = HashWithIndifferentAccess.new
- hash[:a] = 1
- hash["b"] = 3
-
- other = { "a" => 4, :b => 2, "c" => 10 }
-
- merged = hash.merge(other) { |key, old, new| old > new ? old : new }
-
- assert_equal HashWithIndifferentAccess, merged.class
- assert_equal 4, merged[:a]
- assert_equal 3, merged["b"]
- assert_equal 10, merged[:c]
-
- other_indifferent = HashWithIndifferentAccess.new("a" => 9, :b => 2)
-
- merged = hash.merge(other_indifferent) { |key, old, new| old + new }
-
- assert_equal HashWithIndifferentAccess, merged.class
- assert_equal 10, merged[:a]
- assert_equal 5, merged[:b]
- end
-
- def test_indifferent_reverse_merging
- hash = HashWithIndifferentAccess.new key: :old_value
- hash.reverse_merge! key: :new_value
- assert_equal :old_value, hash[:key]
-
- hash = HashWithIndifferentAccess.new("some" => "value", "other" => "value")
- hash.reverse_merge!(some: "noclobber", another: "clobber")
- assert_equal "value", hash[:some]
- assert_equal "clobber", hash[:another]
- end
-
- def test_indifferent_deleting
- get_hash = proc { { a: "foo" }.with_indifferent_access }
- hash = get_hash.call
- assert_equal "foo", hash.delete(:a)
- assert_nil hash.delete(:a)
- hash = get_hash.call
- assert_equal "foo", hash.delete("a")
- assert_nil hash.delete("a")
- end
-
- def test_indifferent_select
- hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).select { |k, v| v == 1 }
-
- assert_equal({ "a" => 1 }, hash)
- assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash
- end
-
- def test_indifferent_select_returns_enumerator
- enum = ActiveSupport::HashWithIndifferentAccess.new(@strings).select
- assert_instance_of Enumerator, enum
- end
-
- def test_indifferent_select_returns_a_hash_when_unchanged
- hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).select { |k, v| true }
-
- assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash
- end
-
- def test_indifferent_select_bang
- indifferent_strings = ActiveSupport::HashWithIndifferentAccess.new(@strings)
- indifferent_strings.select! { |k, v| v == 1 }
-
- assert_equal({ "a" => 1 }, indifferent_strings)
- assert_instance_of ActiveSupport::HashWithIndifferentAccess, indifferent_strings
- end
-
- def test_indifferent_reject
- hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).reject { |k, v| v != 1 }
-
- assert_equal({ "a" => 1 }, hash)
- assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash
- end
-
- def test_indifferent_reject_returns_enumerator
- enum = ActiveSupport::HashWithIndifferentAccess.new(@strings).reject
- assert_instance_of Enumerator, enum
- end
-
- def test_indifferent_reject_bang
- indifferent_strings = ActiveSupport::HashWithIndifferentAccess.new(@strings)
- indifferent_strings.reject! { |k, v| v != 1 }
-
- assert_equal({ "a" => 1 }, indifferent_strings)
- assert_instance_of ActiveSupport::HashWithIndifferentAccess, indifferent_strings
- end
-
- def test_indifferent_compact
- hash_contain_nil_value = @strings.merge("z" => nil)
- hash = ActiveSupport::HashWithIndifferentAccess.new(hash_contain_nil_value)
- compacted_hash = hash.compact
-
- assert_equal(@strings, compacted_hash)
- assert_equal(hash_contain_nil_value, hash)
- assert_instance_of ActiveSupport::HashWithIndifferentAccess, compacted_hash
- end
-
- def test_indifferent_to_hash
- # Should convert to a Hash with String keys.
- assert_equal @strings, @mixed.with_indifferent_access.to_hash
-
- # Should preserve the default value.
- mixed_with_default = @mixed.dup
- mixed_with_default.default = "1234"
- roundtrip = mixed_with_default.with_indifferent_access.to_hash
- assert_equal @strings, roundtrip
- assert_equal "1234", roundtrip.default
-
- # Ensure nested hashes are not HashWithIndiffereneAccess
- new_to_hash = @nested_mixed.with_indifferent_access.to_hash
- assert_not new_to_hash.instance_of?(HashWithIndifferentAccess)
- assert_not new_to_hash["a"].instance_of?(HashWithIndifferentAccess)
- assert_not new_to_hash["a"]["b"].instance_of?(HashWithIndifferentAccess)
- end
-
- def test_lookup_returns_the_same_object_that_is_stored_in_hash_indifferent_access
- hash = HashWithIndifferentAccess.new { |h, k| h[k] = [] }
- hash[:a] << 1
-
- assert_equal [1], hash[:a]
- end
-
- def test_with_indifferent_access_has_no_side_effects_on_existing_hash
- hash = { content: [{ :foo => :bar, "bar" => "baz" }] }
- hash.with_indifferent_access
-
- assert_equal [:foo, "bar"], hash[:content].first.keys
- end
-
- def test_indifferent_hash_with_array_of_hashes
- hash = { "urls" => { "url" => [ { "address" => "1" }, { "address" => "2" } ] } }.with_indifferent_access
- assert_equal "1", hash[:urls][:url].first[:address]
-
- hash = hash.to_hash
- assert_not hash.instance_of?(HashWithIndifferentAccess)
- assert_not hash["urls"].instance_of?(HashWithIndifferentAccess)
- assert_not hash["urls"]["url"].first.instance_of?(HashWithIndifferentAccess)
- end
-
- def test_should_preserve_array_subclass_when_value_is_array
- array = SubclassingArray.new
- array << { "address" => "1" }
- hash = { "urls" => { "url" => array } }.with_indifferent_access
- assert_equal SubclassingArray, hash[:urls][:url].class
- end
-
- def test_should_preserve_array_class_when_hash_value_is_frozen_array
- array = SubclassingArray.new
- array << { "address" => "1" }
- hash = { "urls" => { "url" => array.freeze } }.with_indifferent_access
- assert_equal SubclassingArray, hash[:urls][:url].class
- end
-
- def test_stringify_and_symbolize_keys_on_indifferent_preserves_hash
- h = HashWithIndifferentAccess.new
- h[:first] = 1
- h = h.stringify_keys
- assert_equal 1, h["first"]
- h = HashWithIndifferentAccess.new
- h["first"] = 1
- h = h.symbolize_keys
- assert_equal 1, h[:first]
- end
-
- def test_deep_stringify_and_deep_symbolize_keys_on_indifferent_preserves_hash
- h = HashWithIndifferentAccess.new
- h[:first] = 1
- h = h.deep_stringify_keys
- assert_equal 1, h["first"]
- h = HashWithIndifferentAccess.new
- h["first"] = 1
- h = h.deep_symbolize_keys
- assert_equal 1, h[:first]
- end
-
- def test_to_options_on_indifferent_preserves_hash
- h = HashWithIndifferentAccess.new
- h["first"] = 1
- h.to_options!
- assert_equal 1, h["first"]
- end
-
- def test_to_options_on_indifferent_preserves_works_as_hash_with_dup
- h = HashWithIndifferentAccess.new(a: { b: "b" })
- dup = h.dup
-
- dup[:a][:c] = "c"
- assert_equal "c", h[:a][:c]
- end
-
- def test_indifferent_sub_hashes
- h = { "user" => { "id" => 5 } }.with_indifferent_access
- ["user", :user].each { |user| [:id, "id"].each { |id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5" } }
-
- h = { user: { id: 5 } }.with_indifferent_access
- ["user", :user].each { |user| [:id, "id"].each { |id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5" } }
- end
-
- def test_indifferent_duplication
- # Should preserve default value
- h = HashWithIndifferentAccess.new
- h.default = "1234"
- assert_equal h.default, h.dup.default
-
- # Should preserve class for subclasses
- h = IndifferentHash.new
- assert_equal h.class, h.dup.class
- end
-
- def test_nested_dig_indifferent_access
- skip if RUBY_VERSION < "2.3.0"
- data = { "this" => { "views" => 1234 } }.with_indifferent_access
- assert_equal 1234, data.dig(:this, :views)
- end
-
def test_assert_valid_keys
assert_nothing_raised do
{ failure: "stuff", funny: "business" }.assert_valid_keys([ :failure, :funny ])
@@ -749,12 +271,6 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal "Unknown key: :failore. Valid keys are: :failure", exception.message
end
- def test_assorted_keys_not_stringified
- original = { Object.new => 2, 1 => 2, [] => true }
- indiff = original.with_indifferent_access
- assert(!indiff.keys.any? { |k| k.kind_of? String }, "A key was converted to a string!")
- end
-
def test_deep_merge
hash_1 = { a: "a", b: "b", c: { c1: "c1", c2: "c2", c3: { d1: "d1" } } }
hash_2 = { a: 1, c: { c1: 2, c3: { d2: "d2" } } }
@@ -785,41 +301,10 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal expected, hash_1
end
- def test_deep_merge_on_indifferent_access
- hash_1 = HashWithIndifferentAccess.new(a: "a", b: "b", c: { c1: "c1", c2: "c2", c3: { d1: "d1" } })
- hash_2 = HashWithIndifferentAccess.new(a: 1, c: { c1: 2, c3: { d2: "d2" } })
- hash_3 = { a: 1, c: { c1: 2, c3: { d2: "d2" } } }
- expected = { "a" => 1, "b" => "b", "c" => { "c1" => 2, "c2" => "c2", "c3" => { "d1" => "d1", "d2" => "d2" } } }
- assert_equal expected, hash_1.deep_merge(hash_2)
- assert_equal expected, hash_1.deep_merge(hash_3)
-
- hash_1.deep_merge!(hash_2)
- assert_equal expected, hash_1
- end
-
- def test_store_on_indifferent_access
- hash = HashWithIndifferentAccess.new
- hash.store(:test1, 1)
- hash.store("test1", 11)
- hash[:test2] = 2
- hash["test2"] = 22
- expected = { "test1" => 11, "test2" => 22 }
- assert_equal expected, hash
- end
-
- def test_constructor_on_indifferent_access
- hash = HashWithIndifferentAccess[:foo, 1]
- assert_equal 1, hash[:foo]
- assert_equal 1, hash["foo"]
- hash[:foo] = 3
- assert_equal 3, hash[:foo]
- assert_equal 3, hash["foo"]
- end
-
def test_reverse_merge
- defaults = { a: "x", b: "y", c: 10 }.freeze
+ defaults = { d: 0, a: "x", b: "y", c: 10 }.freeze
options = { a: 1, b: 2 }
- expected = { a: 1, b: 2, c: 10 }
+ expected = { d: 0, a: 1, b: 2, c: 10 }
# Should merge defaults into options, creating a new hash.
assert_equal expected, options.reverse_merge(defaults)
@@ -830,12 +315,30 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal expected, merged.reverse_merge!(defaults)
assert_equal expected, merged
+ # Make the order consistent with the non-overwriting reverse merge.
+ assert_equal expected.keys, merged.keys
+
# Should be an alias for reverse_merge!
merged = options.dup
assert_equal expected, merged.reverse_update(defaults)
assert_equal expected, merged
end
+ def test_with_defaults_aliases_reverse_merge
+ defaults = { a: "x", b: "y", c: 10 }.freeze
+ options = { a: 1, b: 2 }
+ expected = { a: 1, b: 2, c: 10 }
+
+ # Should be an alias for reverse_merge
+ assert_equal expected, options.with_defaults(defaults)
+ assert_not_equal expected, options
+
+ # Should be an alias for reverse_merge!
+ merged = options.dup
+ assert_equal expected, merged.with_defaults!(defaults)
+ assert_equal expected, merged
+ end
+
def test_slice
original = { a: "x", b: "y", c: 10 }
expected = { a: "x", b: "y" }
@@ -878,38 +381,6 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal expected, original.slice(*[:a, :b])
end
- def test_indifferent_slice
- original = { a: "x", b: "y", c: 10 }.with_indifferent_access
- expected = { a: "x", b: "y" }.with_indifferent_access
-
- [["a", "b"], [:a, :b]].each do |keys|
- # Should return a new hash with only the given keys.
- assert_equal expected, original.slice(*keys), keys.inspect
- assert_not_equal expected, original
- end
- end
-
- def test_indifferent_slice_inplace
- original = { a: "x", b: "y", c: 10 }.with_indifferent_access
- expected = { c: 10 }.with_indifferent_access
-
- [["a", "b"], [:a, :b]].each do |keys|
- # Should replace the hash with only the given keys.
- copy = original.dup
- assert_equal expected, copy.slice!(*keys)
- end
- end
-
- def test_indifferent_slice_access_with_symbols
- original = { "login" => "bender", "password" => "shiny", "stuff" => "foo" }
- original = original.with_indifferent_access
-
- slice = original.slice(:login, :password)
-
- assert_equal "bender", slice[:login]
- assert_equal "bender", slice["login"]
- end
-
def test_slice_bang_does_not_override_default
hash = Hash.new(0)
hash.update(a: 1, b: 2)
@@ -947,18 +418,6 @@ class HashExtTest < ActiveSupport::TestCase
assert_nil extracted[:x]
end
- def test_indifferent_extract
- original = { :a => 1, "b" => 2, :c => 3, "d" => 4 }.with_indifferent_access
- expected = { a: 1, b: 2 }.with_indifferent_access
- remaining = { c: 3, d: 4 }.with_indifferent_access
-
- [["a", "b"], [:a, :b]].each do |keys|
- copy = original.dup
- assert_equal expected, copy.extract!(*keys)
- assert_equal remaining, copy
- end
- end
-
def test_except
original = { a: "x", b: "y", c: 10 }
expected = { a: "x", b: "y" }
@@ -987,7 +446,7 @@ class HashExtTest < ActiveSupport::TestCase
original.freeze
assert_nothing_raised { original.except(:a) }
- assert_raise(RuntimeError) { original.except!(:a) }
+ assert_raise(frozen_error_class) { original.except!(:a) }
end
def test_except_does_not_delete_values_in_original
@@ -1030,54 +489,6 @@ class HashExtTest < ActiveSupport::TestCase
assert_nil(h.compact!)
assert_equal(@symbols, h)
end
-
- def test_new_with_to_hash_conversion
- hash = HashWithIndifferentAccess.new(HashByConversion.new(a: 1))
- assert hash.key?("a")
- assert_equal 1, hash[:a]
- end
-
- def test_dup_with_default_proc
- hash = HashWithIndifferentAccess.new
- hash.default_proc = proc { |h, v| raise "walrus" }
- assert_nothing_raised { hash.dup }
- end
-
- def test_dup_with_default_proc_sets_proc
- hash = HashWithIndifferentAccess.new
- hash.default_proc = proc { |h, k| k + 1 }
- new_hash = hash.dup
-
- assert_equal 3, new_hash[2]
-
- new_hash.default = 2
- assert_equal 2, new_hash[:non_existent]
- end
-
- def test_to_hash_with_raising_default_proc
- hash = HashWithIndifferentAccess.new
- hash.default_proc = proc { |h, k| raise "walrus" }
-
- assert_nothing_raised { hash.to_hash }
- end
-
- def test_new_with_to_hash_conversion_copies_default
- normal_hash = Hash.new(3)
- normal_hash[:a] = 1
-
- hash = HashWithIndifferentAccess.new(HashByConversion.new(normal_hash))
- assert_equal 1, hash[:a]
- assert_equal 3, hash[:b]
- end
-
- def test_new_with_to_hash_conversion_copies_default_proc
- normal_hash = Hash.new { 1 + 2 }
- normal_hash[:a] = 1
-
- hash = HashWithIndifferentAccess.new(HashByConversion.new(normal_hash))
- assert_equal 1, hash[:a]
- assert_equal 3, hash[:b]
- end
end
class IWriteMyOwnXML
@@ -1598,61 +1009,6 @@ class HashToXmlTest < ActiveSupport::TestCase
assert_equal expected, Hash.from_trusted_xml('<product><name type="yaml">:value</name></product>')
end
- def test_should_use_default_proc_for_unknown_key
- hash_wia = HashWithIndifferentAccess.new { 1 + 2 }
- assert_equal 3, hash_wia[:new_key]
- end
-
- def test_should_return_nil_if_no_key_is_supplied
- hash_wia = HashWithIndifferentAccess.new { 1 + 2 }
- assert_nil hash_wia.default
- end
-
- def test_should_use_default_value_for_unknown_key
- hash_wia = HashWithIndifferentAccess.new(3)
- assert_equal 3, hash_wia[:new_key]
- end
-
- def test_should_use_default_value_if_no_key_is_supplied
- hash_wia = HashWithIndifferentAccess.new(3)
- assert_equal 3, hash_wia.default
- end
-
- def test_should_nil_if_no_default_value_is_supplied
- hash_wia = HashWithIndifferentAccess.new
- assert_nil hash_wia.default
- end
-
- def test_should_return_dup_for_with_indifferent_access
- hash_wia = HashWithIndifferentAccess.new
- assert_equal hash_wia, hash_wia.with_indifferent_access
- assert_not_same hash_wia, hash_wia.with_indifferent_access
- end
-
- def test_allows_setting_frozen_array_values_with_indifferent_access
- value = [1, 2, 3].freeze
- hash = HashWithIndifferentAccess.new
- hash[:key] = value
- assert_equal hash[:key], value
- end
-
- def test_should_copy_the_default_value_when_converting_to_hash_with_indifferent_access
- hash = Hash.new(3)
- hash_wia = hash.with_indifferent_access
- assert_equal 3, hash_wia.default
- end
-
- def test_should_copy_the_default_proc_when_converting_to_hash_with_indifferent_access
- hash = Hash.new do
- 2 + 1
- end
- assert_equal 3, hash[:foo]
-
- hash_wia = hash.with_indifferent_access
- assert_equal 3, hash_wia[:foo]
- assert_equal 3, hash_wia[:bar]
- end
-
# The XML builder seems to fail miserably when trying to tag something
# with the same name as a Kernel method (throw, test, loop, select ...)
def test_kernel_method_names_to_xml
diff --git a/activesupport/test/core_ext/integer_ext_test.rb b/activesupport/test/core_ext/integer_ext_test.rb
index 137e8ce85f..14169b084d 100644
--- a/activesupport/test/core_ext/integer_ext_test.rb
+++ b/activesupport/test/core_ext/integer_ext_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/core_ext/integer"
diff --git a/activesupport/test/core_ext/kernel/concern_test.rb b/activesupport/test/core_ext/kernel/concern_test.rb
index e7e4f99d7e..b40ff6a623 100644
--- a/activesupport/test/core_ext/kernel/concern_test.rb
+++ b/activesupport/test/core_ext/kernel/concern_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/core_ext/kernel/concern"
diff --git a/activesupport/test/core_ext/kernel_test.rb b/activesupport/test/core_ext/kernel_test.rb
index 26f5088ede..ef11e10af8 100644
--- a/activesupport/test/core_ext/kernel_test.rb
+++ b/activesupport/test/core_ext/kernel_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/core_ext/kernel"
diff --git a/activesupport/test/core_ext/load_error_test.rb b/activesupport/test/core_ext/load_error_test.rb
index 44ff6bb051..41b11d0c33 100644
--- a/activesupport/test/core_ext/load_error_test.rb
+++ b/activesupport/test/core_ext/load_error_test.rb
@@ -1,9 +1,11 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/core_ext/load_error"
class TestLoadError < ActiveSupport::TestCase
def test_with_require
- assert_raise(LoadError) { require 'no_this_file_don\'t_exist' }
+ assert_raise(LoadError) { require "no_this_file_don't_exist" }
end
def test_with_load
assert_raise(LoadError) { load "nor_does_this_one" }
diff --git a/activesupport/test/core_ext/marshal_test.rb b/activesupport/test/core_ext/marshal_test.rb
index a899f98705..7ac051b4b1 100644
--- a/activesupport/test/core_ext/marshal_test.rb
+++ b/activesupport/test/core_ext/marshal_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/core_ext/marshal"
require "dependencies_test_helpers"
@@ -19,6 +21,19 @@ class MarshalTest < ActiveSupport::TestCase
end
end
+ test "that Marshal#load still works when passed a proc" do
+ example_string = "test"
+
+ example_proc = Proc.new do |o|
+ if o.is_a?(String)
+ o.capitalize!
+ end
+ end
+
+ dumped = Marshal.dump(example_string)
+ assert_equal Marshal.load(dumped, example_proc), "Test"
+ end
+
test "that a missing class is autoloaded from string" do
dumped = nil
with_autoloading_fixtures do
diff --git a/activesupport/test/core_ext/module/anonymous_test.rb b/activesupport/test/core_ext/module/anonymous_test.rb
index f885444284..606f22c9b5 100644
--- a/activesupport/test/core_ext/module/anonymous_test.rb
+++ b/activesupport/test/core_ext/module/anonymous_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/core_ext/module/anonymous"
diff --git a/activesupport/test/core_ext/module/attr_internal_test.rb b/activesupport/test/core_ext/module/attr_internal_test.rb
index 8458e278ee..c2a28eced4 100644
--- a/activesupport/test/core_ext/module/attr_internal_test.rb
+++ b/activesupport/test/core_ext/module/attr_internal_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/core_ext/module/attr_internal"
diff --git a/activesupport/test/core_ext/module/attribute_accessor_per_thread_test.rb b/activesupport/test/core_ext/module/attribute_accessor_per_thread_test.rb
index af240bc38d..e0fbd1002c 100644
--- a/activesupport/test/core_ext/module/attribute_accessor_per_thread_test.rb
+++ b/activesupport/test/core_ext/module/attribute_accessor_per_thread_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/core_ext/module/attribute_accessors_per_thread"
diff --git a/activesupport/test/core_ext/module/attribute_accessor_test.rb b/activesupport/test/core_ext/module/attribute_accessor_test.rb
index 464a000d59..f1d6859a88 100644
--- a/activesupport/test/core_ext/module/attribute_accessor_test.rb
+++ b/activesupport/test/core_ext/module/attribute_accessor_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/core_ext/module/attribute_accessors"
@@ -12,7 +14,14 @@ class ModuleAttributeAccessorTest < ActiveSupport::TestCase
cattr_accessor(:defa) { "default_accessor_value" }
cattr_reader(:defr) { "default_reader_value" }
cattr_writer(:defw) { "default_writer_value" }
+ cattr_accessor(:deff) { false }
cattr_accessor(:quux) { :quux }
+
+ cattr_accessor :def_accessor, default: "default_accessor_value"
+ cattr_reader :def_reader, default: "default_reader_value"
+ cattr_writer :def_writer, default: "default_writer_value"
+ cattr_accessor :def_false, default: false
+ cattr_accessor(:def_priority, default: false) { :no_priority }
end
@class = Class.new
@class.instance_eval { include m }
@@ -24,6 +33,21 @@ class ModuleAttributeAccessorTest < ActiveSupport::TestCase
assert_nil @object.foo
end
+ def test_mattr_default_keyword_arguments
+ assert_equal "default_accessor_value", @module.def_accessor
+ assert_equal "default_reader_value", @module.def_reader
+ assert_equal "default_writer_value", @module.class_variable_get(:@@def_writer)
+ end
+
+ def test_mattr_can_default_to_false
+ assert_equal false, @module.def_false
+ assert_equal false, @module.deff
+ end
+
+ def test_mattr_default_priority
+ assert_equal false, @module.def_priority
+ end
+
def test_should_set_mattr_value
@module.foo = :test
assert_equal :test, @object.foo
@@ -91,9 +115,23 @@ class ModuleAttributeAccessorTest < ActiveSupport::TestCase
assert_equal "default_writer_value", @module.class_variable_get("@@defw")
end
- def test_should_not_invoke_default_value_block_multiple_times
+ def test_method_invocation_should_not_invoke_the_default_block
count = 0
+
@module.cattr_accessor(:defcount) { count += 1 }
+
assert_equal 1, count
+ assert_no_difference "count" do
+ @module.defcount
+ end
+ end
+
+ def test_declaring_multiple_attributes_at_once_invokes_the_block_multiple_times
+ count = 0
+
+ @module.cattr_accessor(:defn1, :defn2) { count += 1 }
+
+ assert_equal 1, @module.defn1
+ assert_equal 2, @module.defn2
end
end
diff --git a/activesupport/test/core_ext/module/attribute_aliasing_test.rb b/activesupport/test/core_ext/module/attribute_aliasing_test.rb
index fdfa868851..187a0f4da2 100644
--- a/activesupport/test/core_ext/module/attribute_aliasing_test.rb
+++ b/activesupport/test/core_ext/module/attribute_aliasing_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/core_ext/module/aliasing"
diff --git a/activesupport/test/core_ext/module/concerning_test.rb b/activesupport/test/core_ext/module/concerning_test.rb
index 098036828a..192c3d5a9c 100644
--- a/activesupport/test/core_ext/module/concerning_test.rb
+++ b/activesupport/test/core_ext/module/concerning_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/core_ext/module/concerning"
diff --git a/activesupport/test/core_ext/module/introspection_test.rb b/activesupport/test/core_ext/module/introspection_test.rb
index db383850cd..76d3012239 100644
--- a/activesupport/test/core_ext/module/introspection_test.rb
+++ b/activesupport/test/core_ext/module/introspection_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/core_ext/module/introspection"
diff --git a/activesupport/test/core_ext/module/reachable_test.rb b/activesupport/test/core_ext/module/reachable_test.rb
index 487c7dee16..097a72fa5b 100644
--- a/activesupport/test/core_ext/module/reachable_test.rb
+++ b/activesupport/test/core_ext/module/reachable_test.rb
@@ -1,15 +1,21 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/core_ext/module/reachable"
class AnonymousTest < ActiveSupport::TestCase
test "an anonymous class or module is not reachable" do
- assert !Module.new.reachable?
- assert !Class.new.reachable?
+ assert_deprecated do
+ assert !Module.new.reachable?
+ assert !Class.new.reachable?
+ end
end
test "ordinary named classes or modules are reachable" do
- assert Kernel.reachable?
- assert Object.reachable?
+ assert_deprecated do
+ assert Kernel.reachable?
+ assert Object.reachable?
+ end
end
test "a named class or module whose constant has gone is not reachable" do
@@ -19,8 +25,10 @@ class AnonymousTest < ActiveSupport::TestCase
self.class.send(:remove_const, :C)
self.class.send(:remove_const, :M)
- assert !c.reachable?
- assert !m.reachable?
+ assert_deprecated do
+ assert !c.reachable?
+ assert !m.reachable?
+ end
end
test "a named class or module whose constants store different objects are not reachable" do
@@ -33,9 +41,11 @@ class AnonymousTest < ActiveSupport::TestCase
eval "class C; end"
eval "module M; end"
- assert C.reachable?
- assert M.reachable?
- assert !c.reachable?
- assert !m.reachable?
+ assert_deprecated do
+ assert C.reachable?
+ assert M.reachable?
+ assert !c.reachable?
+ assert !m.reachable?
+ end
end
end
diff --git a/activesupport/test/core_ext/module/remove_method_test.rb b/activesupport/test/core_ext/module/remove_method_test.rb
index 0c627f1e74..8493be8d08 100644
--- a/activesupport/test/core_ext/module/remove_method_test.rb
+++ b/activesupport/test/core_ext/module/remove_method_test.rb
@@ -1,25 +1,27 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/core_ext/module/remove_method"
module RemoveMethodTests
class A
def do_something
- return 1
+ 1
end
def do_something_protected
- return 1
+ 1
end
protected :do_something_protected
def do_something_private
- return 1
+ 1
end
private :do_something_private
class << self
def do_something_else
- return 2
+ 2
end
end
end
diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb
index a17438bf4d..e918823074 100644
--- a/activesupport/test/core_ext/module_test.rb
+++ b/activesupport/test/core_ext/module_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/core_ext/module"
@@ -70,7 +72,23 @@ Product = Struct.new(:name) do
end
end
+module ExtraMissing
+ def method_missing(sym, *args)
+ if sym == :extra_missing
+ 42
+ else
+ super
+ end
+ end
+
+ def respond_to_missing?(sym, priv = false)
+ sym == :extra_missing || super
+ end
+end
+
DecoratedTester = Struct.new(:client) do
+ include ExtraMissing
+
delegate_missing_to :client
end
@@ -332,15 +350,15 @@ class ModuleTest < ActiveSupport::TestCase
assert has_block.hello?
end
- def test_delegate_to_missing_with_method
+ def test_delegate_missing_to_with_method
assert_equal "David", DecoratedTester.new(@david).name
end
- def test_delegate_to_missing_with_reserved_methods
+ def test_delegate_missing_to_with_reserved_methods
assert_equal "David", DecoratedReserved.new(@david).name
end
- def test_delegate_to_missing_does_not_delegate_to_private_methods
+ def test_delegate_missing_to_does_not_delegate_to_private_methods
e = assert_raises(NoMethodError) do
DecoratedReserved.new(@david).private_name
end
@@ -348,7 +366,7 @@ class ModuleTest < ActiveSupport::TestCase
assert_match(/undefined method `private_name' for/, e.message)
end
- def test_delegate_to_missing_does_not_delegate_to_fake_methods
+ def test_delegate_missing_to_does_not_delegate_to_fake_methods
e = assert_raises(NoMethodError) do
DecoratedReserved.new(@david).my_fake_method
end
@@ -356,8 +374,70 @@ class ModuleTest < ActiveSupport::TestCase
assert_match(/undefined method `my_fake_method' for/, e.message)
end
+ def test_delegate_missing_to_raises_delegation_error_if_target_nil
+ e = assert_raises(Module::DelegationError) do
+ DecoratedTester.new(nil).name
+ end
+
+ assert_equal "name delegated to client, but client is nil", e.message
+ end
+
+ def test_delegate_missing_to_affects_respond_to
+ assert DecoratedTester.new(@david).respond_to?(:name)
+ assert_not DecoratedTester.new(@david).respond_to?(:private_name)
+ assert_not DecoratedTester.new(@david).respond_to?(:my_fake_method)
+
+ assert DecoratedTester.new(@david).respond_to?(:name, true)
+ assert_not DecoratedTester.new(@david).respond_to?(:private_name, true)
+ assert_not DecoratedTester.new(@david).respond_to?(:my_fake_method, true)
+ end
+
+ def test_delegate_missing_to_respects_superclass_missing
+ assert_equal 42, DecoratedTester.new(@david).extra_missing
+
+ assert_respond_to DecoratedTester.new(@david), :extra_missing
+ end
+
def test_delegate_with_case
event = Event.new(Tester.new)
assert_equal 1, event.foo
end
+
+ def test_private_delegate
+ location = Class.new do
+ def initialize(place)
+ @place = place
+ end
+
+ private(*delegate(:street, :city, to: :@place))
+ end
+
+ place = location.new(Somewhere.new("Such street", "Sad city"))
+
+ assert_not place.respond_to?(:street)
+ assert_not place.respond_to?(:city)
+
+ assert place.respond_to?(:street, true) # Asking for private method
+ assert place.respond_to?(:city, true)
+ end
+
+ def test_private_delegate_prefixed
+ location = Class.new do
+ def initialize(place)
+ @place = place
+ end
+
+ private(*delegate(:street, :city, to: :@place, prefix: :the))
+ end
+
+ place = location.new(Somewhere.new("Such street", "Sad city"))
+
+ assert_not place.respond_to?(:street)
+ assert_not place.respond_to?(:city)
+
+ assert_not place.respond_to?(:the_street)
+ assert place.respond_to?(:the_street, true)
+ assert_not place.respond_to?(:the_city)
+ assert place.respond_to?(:the_city, true)
+ end
end
diff --git a/activesupport/test/core_ext/name_error_test.rb b/activesupport/test/core_ext/name_error_test.rb
index fdb9493d3c..d1dace3713 100644
--- a/activesupport/test/core_ext/name_error_test.rb
+++ b/activesupport/test/core_ext/name_error_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/core_ext/name_error"
diff --git a/activesupport/test/core_ext/numeric_ext_test.rb b/activesupport/test/core_ext/numeric_ext_test.rb
index 3cfbe6e7e6..4b9073da54 100644
--- a/activesupport/test/core_ext/numeric_ext_test.rb
+++ b/activesupport/test/core_ext/numeric_ext_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/time"
require "active_support/core_ext/numeric"
@@ -300,7 +302,7 @@ class NumericExtFormattingTest < ActiveSupport::TestCase
assert_equal "40 KB", 41100.to_s(:human_size, precision: 2)
assert_equal "1.0 KB", kilobytes(1.0123).to_s(:human_size, precision: 2, strip_insignificant_zeros: false)
assert_equal "1.012 KB", kilobytes(1.0123).to_s(:human_size, precision: 3, significant: false)
- assert_equal "1 KB", kilobytes(1.0123).to_s(:human_size, precision: 0, significant: true) #ignores significant it precision is 0
+ assert_equal "1 KB", kilobytes(1.0123).to_s(:human_size, precision: 0, significant: true) # ignores significant it precision is 0
end
def test_to_s__human_size_with_custom_delimiter_and_separator
@@ -328,17 +330,17 @@ class NumericExtFormattingTest < ActiveSupport::TestCase
assert_equal "489.0 Thousand", 489000.to_s(:human, precision: 4, strip_insignificant_zeros: false)
assert_equal "1.2346 Million", 1234567.to_s(:human, precision: 4, significant: false)
assert_equal "1,2 Million", 1234567.to_s(:human, precision: 1, significant: false, separator: ",")
- assert_equal "1 Million", 1234567.to_s(:human, precision: 0, significant: true, separator: ",") #significant forced to false
+ assert_equal "1 Million", 1234567.to_s(:human, precision: 0, significant: true, separator: ",") # significant forced to false
end
def test_number_to_human_with_custom_units
- #Only integers
+ # Only integers
volume = { unit: "ml", thousand: "lt", million: "m3" }
assert_equal "123 lt", 123456.to_s(:human, units: volume)
assert_equal "12 ml", 12.to_s(:human, units: volume)
assert_equal "1.23 m3", 1234567.to_s(:human, units: volume)
- #Including fractionals
+ # Including fractionals
distance = { mili: "mm", centi: "cm", deci: "dm", unit: "m", ten: "dam", hundred: "hm", thousand: "km" }
assert_equal "1.23 mm", 0.00123.to_s(:human, units: distance)
assert_equal "1.23 cm", 0.0123.to_s(:human, units: distance)
@@ -351,14 +353,14 @@ class NumericExtFormattingTest < ActiveSupport::TestCase
assert_equal "1.23 km", 1230.to_s(:human, units: distance)
assert_equal "12.3 km", 12300.to_s(:human, units: distance)
- #The quantifiers don't need to be a continuous sequence
+ # The quantifiers don't need to be a continuous sequence
gangster = { hundred: "hundred bucks", million: "thousand quids" }
assert_equal "1 hundred bucks", 100.to_s(:human, units: gangster)
assert_equal "25 hundred bucks", 2500.to_s(:human, units: gangster)
assert_equal "25 thousand quids", 25000000.to_s(:human, units: gangster)
assert_equal "12300 thousand quids", 12345000000.to_s(:human, units: gangster)
- #Spaces are stripped from the resulting string
+ # Spaces are stripped from the resulting string
assert_equal "4", 4.to_s(:human, units: { unit: "", ten: "tens " })
assert_equal "4.5 tens", 45.to_s(:human, units: { unit: "", ten: " tens " })
end
diff --git a/activesupport/test/core_ext/object/acts_like_test.rb b/activesupport/test/core_ext/object/acts_like_test.rb
index 631f4e63a8..9f7b81f7fc 100644
--- a/activesupport/test/core_ext/object/acts_like_test.rb
+++ b/activesupport/test/core_ext/object/acts_like_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/core_ext/object"
diff --git a/activesupport/test/core_ext/object/blank_test.rb b/activesupport/test/core_ext/object/blank_test.rb
index 1bedc76320..749e59ec00 100644
--- a/activesupport/test/core_ext/object/blank_test.rb
+++ b/activesupport/test/core_ext/object/blank_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/core_ext/object/blank"
@@ -15,7 +17,7 @@ class BlankTest < ActiveSupport::TestCase
end
BLANK = [ EmptyTrue.new, nil, false, "", " ", " \n\t \r ", " ", "\u00a0", [], {} ]
- NOT = [ EmptyFalse.new, Object.new, true, 0, 1, "a", [nil], { nil => 0 } ]
+ NOT = [ EmptyFalse.new, Object.new, true, 0, 1, "a", [nil], { nil => 0 }, Time.now ]
def test_blank
BLANK.each { |v| assert_equal true, v.blank?, "#{v.inspect} should be blank" }
diff --git a/activesupport/test/core_ext/object/deep_dup_test.rb b/activesupport/test/core_ext/object/deep_dup_test.rb
index f247ee16de..2486592441 100644
--- a/activesupport/test/core_ext/object/deep_dup_test.rb
+++ b/activesupport/test/core_ext/object/deep_dup_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/core_ext/object"
diff --git a/activesupport/test/core_ext/object/duplicable_test.rb b/activesupport/test/core_ext/object/duplicable_test.rb
index e6f3c8aef2..b984becce3 100644
--- a/activesupport/test/core_ext/object/duplicable_test.rb
+++ b/activesupport/test/core_ext/object/duplicable_test.rb
@@ -1,18 +1,23 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "bigdecimal"
require "active_support/core_ext/object/duplicable"
require "active_support/core_ext/numeric/time"
class DuplicableTest < ActiveSupport::TestCase
- if RUBY_VERSION >= "2.4.1"
+ if RUBY_VERSION >= "2.5.0"
+ RAISE_DUP = [method(:puts)]
+ ALLOW_DUP = ["1", "symbol_from_string".to_sym, Object.new, /foo/, [], {}, Time.now, Class.new, Module.new, BigDecimal("4.56"), nil, false, true, 1, 2.3, Complex(1), Rational(1)]
+ elsif RUBY_VERSION >= "2.4.1"
RAISE_DUP = [method(:puts), Complex(1), Rational(1)]
- ALLOW_DUP = ["1", "symbol_from_string".to_sym, Object.new, /foo/, [], {}, Time.now, Class.new, Module.new, BigDecimal.new("4.56"), nil, false, true, 1, 2.3]
+ ALLOW_DUP = ["1", "symbol_from_string".to_sym, Object.new, /foo/, [], {}, Time.now, Class.new, Module.new, BigDecimal("4.56"), nil, false, true, 1, 2.3]
elsif RUBY_VERSION >= "2.4.0" # Due to 2.4.0 bug. This elsif cannot be removed unless we drop 2.4.0 support...
RAISE_DUP = [method(:puts), Complex(1), Rational(1), "symbol_from_string".to_sym]
- ALLOW_DUP = ["1", Object.new, /foo/, [], {}, Time.now, Class.new, Module.new, BigDecimal.new("4.56"), nil, false, true, 1, 2.3]
+ ALLOW_DUP = ["1", Object.new, /foo/, [], {}, Time.now, Class.new, Module.new, BigDecimal("4.56"), nil, false, true, 1, 2.3]
else
RAISE_DUP = [nil, false, true, :symbol, 1, 2.3, method(:puts), Complex(1), Rational(1)]
- ALLOW_DUP = ["1", Object.new, /foo/, [], {}, Time.now, Class.new, Module.new, BigDecimal.new("4.56")]
+ ALLOW_DUP = ["1", Object.new, /foo/, [], {}, Time.now, Class.new, Module.new, BigDecimal("4.56")]
end
def test_duplicable
diff --git a/activesupport/test/core_ext/object/inclusion_test.rb b/activesupport/test/core_ext/object/inclusion_test.rb
index 955686d6aa..52c21f2e8e 100644
--- a/activesupport/test/core_ext/object/inclusion_test.rb
+++ b/activesupport/test/core_ext/object/inclusion_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/core_ext/object/inclusion"
diff --git a/activesupport/test/core_ext/object/instance_variables_test.rb b/activesupport/test/core_ext/object/instance_variables_test.rb
index 5bdb2fbc35..a3d8daab5b 100644
--- a/activesupport/test/core_ext/object/instance_variables_test.rb
+++ b/activesupport/test/core_ext/object/instance_variables_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/core_ext/object"
@@ -17,7 +19,7 @@ class ObjectInstanceVariableTest < ActiveSupport::TestCase
end
def test_instance_exec_passes_arguments_to_block
- assert_equal %w(hello goodbye), "hello".instance_exec("goodbye") { |v| [self, v] }
+ assert_equal %w(hello goodbye), "hello".dup.instance_exec("goodbye") { |v| [self, v] }
end
def test_instance_exec_with_frozen_obj
@@ -25,7 +27,7 @@ class ObjectInstanceVariableTest < ActiveSupport::TestCase
end
def test_instance_exec_nested
- assert_equal %w(goodbye olleh bar), "hello".instance_exec("goodbye") { |arg|
+ assert_equal %w(goodbye olleh bar), "hello".dup.instance_exec("goodbye") { |arg|
[arg] + instance_exec("bar") { |v| [reverse, v] } }
end
end
diff --git a/activesupport/test/core_ext/object/json_cherry_pick_test.rb b/activesupport/test/core_ext/object/json_cherry_pick_test.rb
index dd4e90918e..22659a4050 100644
--- a/activesupport/test/core_ext/object/json_cherry_pick_test.rb
+++ b/activesupport/test/core_ext/object/json_cherry_pick_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
# These test cases were added to test that cherry-picking the json extensions
diff --git a/activesupport/test/core_ext/object/json_gem_encoding_test.rb b/activesupport/test/core_ext/object/json_gem_encoding_test.rb
index f5016d0c2a..4cdb6ed09f 100644
--- a/activesupport/test/core_ext/object/json_gem_encoding_test.rb
+++ b/activesupport/test/core_ext/object/json_gem_encoding_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "json"
require "json/encoding_test_cases"
diff --git a/activesupport/test/core_ext/object/to_param_test.rb b/activesupport/test/core_ext/object/to_param_test.rb
index 56246b24f3..612156bd99 100644
--- a/activesupport/test/core_ext/object/to_param_test.rb
+++ b/activesupport/test/core_ext/object/to_param_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/core_ext/object/to_param"
diff --git a/activesupport/test/core_ext/object/to_query_test.rb b/activesupport/test/core_ext/object/to_query_test.rb
index 298c8bf373..7593bcfa4d 100644
--- a/activesupport/test/core_ext/object/to_query_test.rb
+++ b/activesupport/test/core_ext/object/to_query_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/ordered_hash"
require "active_support/core_ext/object/to_query"
diff --git a/activesupport/test/core_ext/object/try_test.rb b/activesupport/test/core_ext/object/try_test.rb
index 5c8bf59952..fe68b24bf5 100644
--- a/activesupport/test/core_ext/object/try_test.rb
+++ b/activesupport/test/core_ext/object/try_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/core_ext/object"
diff --git a/activesupport/test/core_ext/range_ext_test.rb b/activesupport/test/core_ext/range_ext_test.rb
index d166c7309c..903c173e59 100644
--- a/activesupport/test/core_ext/range_ext_test.rb
+++ b/activesupport/test/core_ext/range_ext_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/time"
require "active_support/core_ext/numeric"
@@ -14,6 +16,11 @@ class RangeTest < ActiveSupport::TestCase
assert_equal "BETWEEN '2005-12-10 15:30:00' AND '2005-12-10 17:30:00'", date_range.to_s(:db)
end
+ def test_to_s_with_alphabets
+ alphabet_range = ("a".."z")
+ assert_equal "BETWEEN 'a' AND 'z'", alphabet_range.to_s(:db)
+ end
+
def test_to_s_with_numeric
number_range = (1..100)
assert_equal "BETWEEN '1' AND '100'", number_range.to_s(:db)
@@ -99,24 +106,27 @@ class RangeTest < ActiveSupport::TestCase
end
def test_each_on_time_with_zone
- twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Eastern Time (US & Canada)"] , Time.utc(2006, 11, 28, 10, 30))
+ twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Eastern Time (US & Canada)"], Time.utc(2006, 11, 28, 10, 30))
assert_raises TypeError do
((twz - 1.hour)..twz).each {}
end
end
def test_step_on_time_with_zone
- twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Eastern Time (US & Canada)"] , Time.utc(2006, 11, 28, 10, 30))
+ twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Eastern Time (US & Canada)"], Time.utc(2006, 11, 28, 10, 30))
assert_raises TypeError do
((twz - 1.hour)..twz).step(1) {}
end
end
def test_include_on_time_with_zone
- twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Eastern Time (US & Canada)"] , Time.utc(2006, 11, 28, 10, 30))
- assert_raises TypeError do
- ((twz - 1.hour)..twz).include?(twz)
- end
+ twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Eastern Time (US & Canada)"], Time.utc(2006, 11, 28, 10, 30))
+ assert ((twz - 1.hour)..twz).include?(twz)
+ end
+
+ def test_case_equals_on_time_with_zone
+ twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Eastern Time (US & Canada)"], Time.utc(2006, 11, 28, 10, 30))
+ assert ((twz - 1.hour)..twz) === twz
end
def test_date_time_with_each
diff --git a/activesupport/test/core_ext/regexp_ext_test.rb b/activesupport/test/core_ext/regexp_ext_test.rb
index e569a9f234..5737bdafda 100644
--- a/activesupport/test/core_ext/regexp_ext_test.rb
+++ b/activesupport/test/core_ext/regexp_ext_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/core_ext/regexp"
diff --git a/activesupport/test/core_ext/secure_random_test.rb b/activesupport/test/core_ext/secure_random_test.rb
index fc25f6ab41..7067fb524c 100644
--- a/activesupport/test/core_ext/secure_random_test.rb
+++ b/activesupport/test/core_ext/secure_random_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/core_ext/securerandom"
diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb
index 5d90fa2509..5c5abe9fd1 100644
--- a/activesupport/test/core_ext/string_ext_test.rb
+++ b/activesupport/test/core_ext/string_ext_test.rb
@@ -1,5 +1,8 @@
+# frozen_string_literal: true
+
require "date"
require "abstract_unit"
+require "timeout"
require "inflector_test_cases"
require "constantize_test_cases"
@@ -77,6 +80,12 @@ class StringInflectionsTest < ActiveSupport::TestCase
end
end
+ def test_titleize_with_keep_id_suffix
+ MixtureToTitleCaseWithKeepIdSuffix.each do |before, titleized|
+ assert_equal(titleized, before.titleize(keep_id_suffix: true))
+ end
+ end
+
def test_upcase_first
assert_equal "What a Lovely Day", "what a Lovely Day".upcase_first
end
@@ -99,6 +108,13 @@ class StringInflectionsTest < ActiveSupport::TestCase
assert_equal("capital", "Capital".camelize(:lower))
end
+ def test_camelize_invalid_option
+ e = assert_raise ArgumentError do
+ "Capital".camelize(nil)
+ end
+ assert_equal("Invalid option, use either :upper or :lower.", e.message)
+ end
+
def test_dasherize
UnderscoresToDashes.each do |underscored, dasherized|
assert_equal(dasherized, underscored.dasherize)
@@ -181,7 +197,7 @@ class StringInflectionsTest < ActiveSupport::TestCase
end
def test_string_parameterized_underscore_preserve_case
- StringToParameterizePreserceCaseWithUnderscore.each do |normal, slugged|
+ StringToParameterizePreserveCaseWithUnderscore.each do |normal, slugged|
assert_equal(slugged, normal.parameterize(separator: "_", preserve_case: true))
end
end
@@ -198,6 +214,12 @@ class StringInflectionsTest < ActiveSupport::TestCase
end
end
+ def test_humanize_with_keep_id_suffix
+ UnderscoreToHumanWithKeepIdSuffix.each do |underscore, human|
+ assert_equal(human, underscore.humanize(keep_id_suffix: true))
+ end
+ end
+
def test_humanize_with_html_escape
assert_equal "Hello", ERB::Util.html_escape("hello").humanize
end
@@ -220,7 +242,7 @@ class StringInflectionsTest < ActiveSupport::TestCase
def test_string_squish
original = %{\u205f\u3000 A string surrounded by various unicode spaces,
- with tabs(\t\t), newlines(\n\n), unicode nextlines(\u0085\u0085) and many spaces( ). \u00a0\u2007}
+ with tabs(\t\t), newlines(\n\n), unicode nextlines(\u0085\u0085) and many spaces( ). \u00a0\u2007}.dup
expected = "A string surrounded by various unicode spaces, " \
"with tabs( ), newlines( ), unicode nextlines( ) and many spaces( )."
@@ -290,8 +312,8 @@ class StringInflectionsTest < ActiveSupport::TestCase
end
def test_truncate_multibyte
- assert_equal "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 ...".force_encoding(Encoding::UTF_8),
- "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 \354\225\204\353\235\274\353\246\254\354\230\244".force_encoding(Encoding::UTF_8).truncate(10)
+ assert_equal "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 ...".dup.force_encoding(Encoding::UTF_8),
+ "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 \354\225\204\353\235\274\353\246\254\354\230\244".dup.force_encoding(Encoding::UTF_8).truncate(10)
end
def test_truncate_should_not_be_html_safe
@@ -312,7 +334,7 @@ class StringInflectionsTest < ActiveSupport::TestCase
end
def test_remove!
- original = "This is a very good day to die"
+ original = "This is a very good day to die".dup
assert_equal "This is a good day to die", original.remove!(" very")
assert_equal "This is a good day to die", original
assert_equal "This is a good day", original.remove!(" to ", /die/)
@@ -645,7 +667,7 @@ end
class OutputSafetyTest < ActiveSupport::TestCase
def setup
- @string = "hello"
+ @string = "hello".dup
@object = Class.new(Object) do
def to_s
"other"
@@ -721,7 +743,7 @@ class OutputSafetyTest < ActiveSupport::TestCase
end
test "Concatting safe onto unsafe yields unsafe" do
- @other_string = "other"
+ @other_string = "other".dup
string = @string.html_safe
@other_string.concat(string)
@@ -744,7 +766,7 @@ class OutputSafetyTest < ActiveSupport::TestCase
end
test "Concatting safe onto unsafe with << yields unsafe" do
- @other_string = "other"
+ @other_string = "other".dup
string = @string.html_safe
@other_string << string
@@ -800,7 +822,7 @@ class OutputSafetyTest < ActiveSupport::TestCase
test "Concatting an integer to safe always yields safe" do
string = @string.html_safe
string = string.concat(13)
- assert_equal "hello".concat(13), string
+ assert_equal "hello".dup.concat(13), string
assert string.html_safe?
end
@@ -855,7 +877,8 @@ end
class StringIndentTest < ActiveSupport::TestCase
test "does not indent strings that only contain newlines (edge cases)" do
- ["", "\n", "\n" * 7].each do |str|
+ ["", "\n", "\n" * 7].each do |string|
+ str = string.dup
assert_nil str.indent!(8)
assert_equal str, str.indent(8)
assert_equal str, str.indent(1, "\t")
diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb
index a399e36dc9..01cf1938be 100644
--- a/activesupport/test/core_ext/time_ext_test.rb
+++ b/activesupport/test/core_ext/time_ext_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/time"
require "core_ext/date_and_time_behavior"
@@ -176,10 +178,6 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal Time.local(2005, 2, 4, 19, 30, 59, Rational(999999999, 1000)), Time.local(2005, 2, 4, 19, 30, 10).end_of_minute
end
- def test_last_year
- assert_equal Time.local(2004, 6, 5, 10), Time.local(2005, 6, 5, 10, 0, 0).last_year
- end
-
def test_ago
assert_equal Time.local(2005, 2, 22, 10, 10, 9), Time.local(2005, 2, 22, 10, 10, 10).ago(1)
assert_equal Time.local(2005, 2, 22, 9, 10, 10), Time.local(2005, 2, 22, 10, 10, 10).ago(3600)
@@ -433,6 +431,13 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
assert_raise(ArgumentError) { Time.new(2005, 2, 22, 15, 15, 45, "-08:00").change(nsec: 1000000000) }
end
+ def test_change_offset
+ assert_equal Time.new(2006, 2, 22, 15, 15, 10, "-08:00"), Time.new(2006, 2, 22, 15, 15, 10, "+01:00").change(offset: "-08:00")
+ assert_equal Time.new(2006, 2, 22, 15, 15, 10, -28800), Time.new(2006, 2, 22, 15, 15, 10, 3600).change(offset: -28800)
+ assert_raise(ArgumentError) { Time.new(2005, 2, 22, 15, 15, 45, "+01:00").change(usec: 1000000, offset: "-08:00") }
+ assert_raise(ArgumentError) { Time.new(2005, 2, 22, 15, 15, 45, "+01:00").change(nsec: 1000000000, offset: -28800) }
+ end
+
def test_advance
assert_equal Time.local(2006, 2, 28, 15, 15, 10), Time.local(2005, 2, 28, 15, 15, 10).advance(years: 1)
assert_equal Time.local(2005, 6, 28, 15, 15, 10), Time.local(2005, 2, 28, 15, 15, 10).advance(months: 4)
@@ -446,7 +451,7 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal Time.local(2013, 10, 3, 15, 15, 10), Time.local(2005, 2, 28, 15, 15, 10).advance(years: 7, months: 19, days: 5)
assert_equal Time.local(2013, 10, 17, 15, 15, 10), Time.local(2005, 2, 28, 15, 15, 10).advance(years: 7, months: 19, weeks: 2, days: 5)
assert_equal Time.local(2001, 12, 27, 15, 15, 10), Time.local(2005, 2, 28, 15, 15, 10).advance(years: -3, months: -2, days: -1)
- assert_equal Time.local(2005, 2, 28, 15, 15, 10), Time.local(2004, 2, 29, 15, 15, 10).advance(years: 1) #leap day plus one year
+ assert_equal Time.local(2005, 2, 28, 15, 15, 10), Time.local(2004, 2, 29, 15, 15, 10).advance(years: 1) # leap day plus one year
assert_equal Time.local(2005, 2, 28, 20, 15, 10), Time.local(2005, 2, 28, 15, 15, 10).advance(hours: 5)
assert_equal Time.local(2005, 2, 28, 15, 22, 10), Time.local(2005, 2, 28, 15, 15, 10).advance(minutes: 7)
assert_equal Time.local(2005, 2, 28, 15, 15, 19), Time.local(2005, 2, 28, 15, 15, 10).advance(seconds: 9)
@@ -468,7 +473,7 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal Time.utc(2013, 10, 3, 15, 15, 10), Time.utc(2005, 2, 22, 15, 15, 10).advance(years: 7, months: 19, days: 11)
assert_equal Time.utc(2013, 10, 17, 15, 15, 10), Time.utc(2005, 2, 28, 15, 15, 10).advance(years: 7, months: 19, weeks: 2, days: 5)
assert_equal Time.utc(2001, 12, 27, 15, 15, 10), Time.utc(2005, 2, 28, 15, 15, 10).advance(years: -3, months: -2, days: -1)
- assert_equal Time.utc(2005, 2, 28, 15, 15, 10), Time.utc(2004, 2, 29, 15, 15, 10).advance(years: 1) #leap day plus one year
+ assert_equal Time.utc(2005, 2, 28, 15, 15, 10), Time.utc(2004, 2, 29, 15, 15, 10).advance(years: 1) # leap day plus one year
assert_equal Time.utc(2005, 2, 28, 20, 15, 10), Time.utc(2005, 2, 28, 15, 15, 10).advance(hours: 5)
assert_equal Time.utc(2005, 2, 28, 15, 22, 10), Time.utc(2005, 2, 28, 15, 15, 10).advance(minutes: 7)
assert_equal Time.utc(2005, 2, 28, 15, 15, 19), Time.utc(2005, 2, 28, 15, 15, 10).advance(seconds: 9)
@@ -490,7 +495,7 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal Time.new(2013, 10, 3, 15, 15, 10, "-08:00"), Time.new(2005, 2, 22, 15, 15, 10, "-08:00").advance(years: 7, months: 19, days: 11)
assert_equal Time.new(2013, 10, 17, 15, 15, 10, "-08:00"), Time.new(2005, 2, 28, 15, 15, 10, "-08:00").advance(years: 7, months: 19, weeks: 2, days: 5)
assert_equal Time.new(2001, 12, 27, 15, 15, 10, "-08:00"), Time.new(2005, 2, 28, 15, 15, 10, "-08:00").advance(years: -3, months: -2, days: -1)
- assert_equal Time.new(2005, 2, 28, 15, 15, 10, "-08:00"), Time.new(2004, 2, 29, 15, 15, 10, "-08:00").advance(years: 1) #leap day plus one year
+ assert_equal Time.new(2005, 2, 28, 15, 15, 10, "-08:00"), Time.new(2004, 2, 29, 15, 15, 10, "-08:00").advance(years: 1) # leap day plus one year
assert_equal Time.new(2005, 2, 28, 20, 15, 10, "-08:00"), Time.new(2005, 2, 28, 15, 15, 10, "-08:00").advance(hours: 5)
assert_equal Time.new(2005, 2, 28, 15, 22, 10, "-08:00"), Time.new(2005, 2, 28, 15, 15, 10, "-08:00").advance(minutes: 7)
assert_equal Time.new(2005, 2, 28, 15, 15, 19, "-08:00"), Time.new(2005, 2, 28, 15, 15, 10, "-08:00").advance(seconds: 9)
@@ -569,6 +574,11 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
Time::DATE_FORMATS.delete(:custom)
end
+ def test_rfc3339_with_fractional_seconds
+ time = Time.new(1999, 12, 31, 19, 0, Rational(1, 8), -18000)
+ assert_equal "1999-12-31T19:00:00.125-05:00", time.rfc3339(3)
+ end
+
def test_to_date
assert_equal Date.new(2005, 2, 21), Time.local(2005, 2, 21, 17, 44, 30).to_date
end
@@ -650,10 +660,6 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
end
end
- def test_last_month_on_31st
- assert_equal Time.local(2004, 2, 29), Time.local(2004, 3, 31).last_month
- end
-
def test_xmlschema_is_available
assert_nothing_raised { Time.now.xmlschema }
end
@@ -910,6 +916,37 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
def test_all_year
assert_equal Time.local(2011, 1, 1, 0, 0, 0)..Time.local(2011, 12, 31, 23, 59, 59, Rational(999999999, 1000)), Time.local(2011, 6, 7, 10, 10, 10).all_year
end
+
+ def test_rfc3339_parse
+ time = Time.rfc3339("1999-12-31T19:00:00.125-05:00")
+
+ assert_equal 1999, time.year
+ assert_equal 12, time.month
+ assert_equal 31, time.day
+ assert_equal 19, time.hour
+ assert_equal 0, time.min
+ assert_equal 0, time.sec
+ assert_equal 125000, time.usec
+ assert_equal(-18000, time.utc_offset)
+
+ exception = assert_raises(ArgumentError) do
+ Time.rfc3339("1999-12-31")
+ end
+
+ assert_equal "invalid date", exception.message
+
+ exception = assert_raises(ArgumentError) do
+ Time.rfc3339("1999-12-31T19:00:00")
+ end
+
+ assert_equal "invalid date", exception.message
+
+ exception = assert_raises(ArgumentError) do
+ Time.rfc3339("foobar")
+ end
+
+ assert_equal "invalid date", exception.message
+ end
end
class TimeExtMarshalingTest < ActiveSupport::TestCase
diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb
index ab5ec98642..b25747eadb 100644
--- a/activesupport/test/core_ext/time_with_zone_test.rb
+++ b/activesupport/test/core_ext/time_with_zone_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/time"
require "time_zone_test_helpers"
@@ -48,6 +50,12 @@ class TimeWithZoneTest < ActiveSupport::TestCase
assert_raise(ArgumentError) { @twz.in_time_zone(Object.new) }
end
+ def test_in_time_zone_with_ambiguous_time
+ with_env_tz "Europe/Moscow" do
+ assert_equal Time.utc(2014, 10, 25, 22, 0, 0), Time.local(2014, 10, 26, 1, 0, 0).in_time_zone("Moscow")
+ end
+ end
+
def test_localtime
assert_equal @twz.localtime, @twz.utc.getlocal
assert_instance_of Time, @twz.localtime
@@ -76,7 +84,7 @@ class TimeWithZoneTest < ActiveSupport::TestCase
def test_formatted_offset
assert_equal "-05:00", @twz.formatted_offset
- assert_equal "-04:00", ActiveSupport::TimeWithZone.new(Time.utc(2000, 6), @time_zone).formatted_offset #dst
+ assert_equal "-04:00", ActiveSupport::TimeWithZone.new(Time.utc(2000, 6), @time_zone).formatted_offset # dst
end
def test_dst?
@@ -86,7 +94,7 @@ class TimeWithZoneTest < ActiveSupport::TestCase
def test_zone
assert_equal "EST", @twz.zone
- assert_equal "EDT", ActiveSupport::TimeWithZone.new(Time.utc(2000, 6), @time_zone).zone #dst
+ assert_equal "EDT", ActiveSupport::TimeWithZone.new(Time.utc(2000, 6), @time_zone).zone # dst
end
def test_nsec
@@ -144,6 +152,16 @@ class TimeWithZoneTest < ActiveSupport::TestCase
assert_equal "1999-12-31T19:00:00-05:00", @twz.xmlschema(nil)
end
+ def test_iso8601_with_fractional_seconds
+ @twz += Rational(1, 8)
+ assert_equal "1999-12-31T19:00:00.125-05:00", @twz.iso8601(3)
+ end
+
+ def test_rfc3339_with_fractional_seconds
+ @twz += Rational(1, 8)
+ assert_equal "1999-12-31T19:00:00.125-05:00", @twz.rfc3339(3)
+ end
+
def test_to_yaml
yaml = <<-EOF.strip_heredoc
--- !ruby/object:ActiveSupport::TimeWithZone
@@ -289,13 +307,13 @@ class TimeWithZoneTest < ActiveSupport::TestCase
end
def test_plus_with_integer
- assert_equal Time.utc(1999, 12, 31, 19, 0 , 5), (@twz + 5).time
+ assert_equal Time.utc(1999, 12, 31, 19, 0, 5), (@twz + 5).time
end
def test_plus_with_integer_when_self_wraps_datetime
datetime = DateTime.civil(2000, 1, 1, 0)
twz = ActiveSupport::TimeWithZone.new(datetime, @time_zone)
- assert_equal DateTime.civil(1999, 12, 31, 19, 0 , 5), (twz + 5).time
+ assert_equal DateTime.civil(1999, 12, 31, 19, 0, 5), (twz + 5).time
end
def test_plus_when_crossing_time_class_limit
@@ -304,21 +322,21 @@ class TimeWithZoneTest < ActiveSupport::TestCase
end
def test_plus_with_duration
- assert_equal Time.utc(2000, 1, 5, 19, 0 , 0), (@twz + 5.days).time
+ assert_equal Time.utc(2000, 1, 5, 19, 0, 0), (@twz + 5.days).time
end
def test_minus_with_integer
- assert_equal Time.utc(1999, 12, 31, 18, 59 , 55), (@twz - 5).time
+ assert_equal Time.utc(1999, 12, 31, 18, 59, 55), (@twz - 5).time
end
def test_minus_with_integer_when_self_wraps_datetime
datetime = DateTime.civil(2000, 1, 1, 0)
twz = ActiveSupport::TimeWithZone.new(datetime, @time_zone)
- assert_equal DateTime.civil(1999, 12, 31, 18, 59 , 55), (twz - 5).time
+ assert_equal DateTime.civil(1999, 12, 31, 18, 59, 55), (twz - 5).time
end
def test_minus_with_duration
- assert_equal Time.utc(1999, 12, 26, 19, 0 , 0), (@twz - 5.days).time
+ assert_equal Time.utc(1999, 12, 26, 19, 0, 0), (@twz - 5.days).time
end
def test_minus_with_time
@@ -421,11 +439,29 @@ class TimeWithZoneTest < ActiveSupport::TestCase
assert_equal time, Time.at(time)
end
- def test_to_time
- with_env_tz "US/Eastern" do
- assert_equal Time, @twz.to_time.class
- assert_equal Time.local(1999, 12, 31, 19), @twz.to_time
- assert_equal Time.local(1999, 12, 31, 19).utc_offset, @twz.to_time.utc_offset
+ def test_to_time_with_preserve_timezone
+ with_preserve_timezone(true) do
+ with_env_tz "US/Eastern" do
+ time = @twz.to_time
+
+ assert_equal Time, time.class
+ assert_equal time.object_id, @twz.to_time.object_id
+ assert_equal Time.local(1999, 12, 31, 19), time
+ assert_equal Time.local(1999, 12, 31, 19).utc_offset, time.utc_offset
+ end
+ end
+ end
+
+ def test_to_time_without_preserve_timezone
+ with_preserve_timezone(false) do
+ with_env_tz "US/Eastern" do
+ time = @twz.to_time
+
+ assert_equal Time, time.class
+ assert_equal time.object_id, @twz.to_time.object_id
+ assert_equal Time.local(1999, 12, 31, 19), time
+ assert_equal Time.local(1999, 12, 31, 19).utc_offset, time.utc_offset
+ end
end
end
@@ -471,7 +507,7 @@ class TimeWithZoneTest < ActiveSupport::TestCase
def test_method_missing_with_time_return_value
assert_instance_of ActiveSupport::TimeWithZone, @twz.months_since(1)
- assert_equal Time.utc(2000, 1, 31, 19, 0 , 0), @twz.months_since(1).time
+ assert_equal Time.utc(2000, 1, 31, 19, 0, 0), @twz.months_since(1).time
end
def test_marshal_dump_and_load
@@ -507,6 +543,8 @@ class TimeWithZoneTest < ActiveSupport::TestCase
assert_nothing_raised do
@twz.period
@twz.time
+ @twz.to_datetime
+ @twz.to_time
end
end
@@ -595,6 +633,12 @@ class TimeWithZoneTest < ActiveSupport::TestCase
assert_equal "Fri, 31 Dec 1999 06:00:00 EST -05:00", @twz.change(hour: 6).inspect
assert_equal "Fri, 31 Dec 1999 19:15:00 EST -05:00", @twz.change(min: 15).inspect
assert_equal "Fri, 31 Dec 1999 19:00:30 EST -05:00", @twz.change(sec: 30).inspect
+ assert_equal "Fri, 31 Dec 1999 19:00:00 HST -10:00", @twz.change(offset: "-10:00").inspect
+ assert_equal "Fri, 31 Dec 1999 19:00:00 HST -10:00", @twz.change(offset: -36000).inspect
+ assert_equal "Fri, 31 Dec 1999 19:00:00 HST -10:00", @twz.change(zone: "Hawaii").inspect
+ assert_equal "Fri, 31 Dec 1999 19:00:00 HST -10:00", @twz.change(zone: -10).inspect
+ assert_equal "Fri, 31 Dec 1999 19:00:00 HST -10:00", @twz.change(zone: -36000).inspect
+ assert_equal "Fri, 31 Dec 1999 19:00:00 HST -10:00", @twz.change(zone: "Pacific/Honolulu").inspect
end
def test_change_at_dst_boundary
@@ -1263,4 +1307,10 @@ class TimeWithZoneMethodsForString < ActiveSupport::TestCase
assert_raise(ArgumentError) { @u.in_time_zone(Object.new) }
assert_raise(ArgumentError) { @z.in_time_zone(Object.new) }
end
+
+ def test_in_time_zone_with_ambiguous_time
+ with_tz_default "Moscow" do
+ assert_equal Time.utc(2014, 10, 25, 22, 0, 0), "2014-10-26 01:00:00".in_time_zone
+ end
+ end
end
diff --git a/activesupport/test/core_ext/uri_ext_test.rb b/activesupport/test/core_ext/uri_ext_test.rb
index 0f13ca9c0e..8816b0d392 100644
--- a/activesupport/test/core_ext/uri_ext_test.rb
+++ b/activesupport/test/core_ext/uri_ext_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "uri"
require "active_support/core_ext/uri"
diff --git a/activesupport/test/current_attributes_test.rb b/activesupport/test/current_attributes_test.rb
new file mode 100644
index 0000000000..1669f08f68
--- /dev/null
+++ b/activesupport/test/current_attributes_test.rb
@@ -0,0 +1,105 @@
+# frozen_string_literal: true
+
+require "abstract_unit"
+
+class CurrentAttributesTest < ActiveSupport::TestCase
+ Person = Struct.new(:name, :time_zone)
+
+ class Current < ActiveSupport::CurrentAttributes
+ attribute :world, :account, :person, :request
+ delegate :time_zone, to: :person
+
+ resets { Time.zone = "UTC" }
+
+ def account=(account)
+ super
+ self.person = "#{account}'s person"
+ end
+
+ def person=(person)
+ super
+ Time.zone = person.try(:time_zone)
+ end
+
+ def request
+ "#{super} something"
+ end
+
+ def intro
+ "#{person.name}, in #{time_zone}"
+ end
+ end
+
+ setup do
+ @original_time_zone = Time.zone
+ Current.reset
+ end
+
+ teardown do
+ Time.zone = @original_time_zone
+ end
+
+ test "read and write attribute" do
+ Current.world = "world/1"
+ assert_equal "world/1", Current.world
+ end
+
+ test "read overwritten attribute method" do
+ Current.request = "request/1"
+ assert_equal "request/1 something", Current.request
+ end
+
+ test "set attribute via overwritten method" do
+ Current.account = "account/1"
+ assert_equal "account/1", Current.account
+ assert_equal "account/1's person", Current.person
+ end
+
+ test "set auxiliary class via overwritten method" do
+ Current.person = Person.new("David", "Central Time (US & Canada)")
+ assert_equal "Central Time (US & Canada)", Time.zone.name
+ end
+
+ test "resets auxiliary class via callback" do
+ Current.person = Person.new("David", "Central Time (US & Canada)")
+ assert_equal "Central Time (US & Canada)", Time.zone.name
+
+ Current.reset
+ assert_equal "UTC", Time.zone.name
+ end
+
+ test "set attribute only via scope" do
+ Current.world = "world/1"
+
+ Current.set(world: "world/2") do
+ assert_equal "world/2", Current.world
+ end
+
+ assert_equal "world/1", Current.world
+ end
+
+ test "set multiple attributes" do
+ Current.world = "world/1"
+ Current.account = "account/1"
+
+ Current.set(world: "world/2", account: "account/2") do
+ assert_equal "world/2", Current.world
+ assert_equal "account/2", Current.account
+ end
+
+ assert_equal "world/1", Current.world
+ assert_equal "account/1", Current.account
+ end
+
+ test "delegation" do
+ Current.person = Person.new("David", "Central Time (US & Canada)")
+ assert_equal "Central Time (US & Canada)", Current.time_zone
+ assert_equal "Central Time (US & Canada)", Current.instance.time_zone
+ end
+
+ test "all methods forward to the instance" do
+ Current.person = Person.new("David", "Central Time (US & Canada)")
+ assert_equal "David, in Central Time (US & Canada)", Current.intro
+ assert_equal "David, in Central Time (US & Canada)", Current.instance.intro
+ end
+end
diff --git a/activesupport/test/dependencies/check_warnings.rb b/activesupport/test/dependencies/check_warnings.rb
index 03c3dca1d6..f7d7d2dbc7 100644
--- a/activesupport/test/dependencies/check_warnings.rb
+++ b/activesupport/test/dependencies/check_warnings.rb
@@ -1,2 +1,4 @@
+# frozen_string_literal: true
+
$check_warnings_load_count += 1
$checked_verbose = $VERBOSE
diff --git a/activesupport/test/dependencies/conflict.rb b/activesupport/test/dependencies/conflict.rb
index 7eff49bbfa..aa22ec0b09 100644
--- a/activesupport/test/dependencies/conflict.rb
+++ b/activesupport/test/dependencies/conflict.rb
@@ -1 +1,3 @@
+# frozen_string_literal: true
+
Conflict = 1
diff --git a/activesupport/test/dependencies/cross_site_depender.rb b/activesupport/test/dependencies/cross_site_depender.rb
index fbc3b64f56..3ddea7fc4b 100644
--- a/activesupport/test/dependencies/cross_site_depender.rb
+++ b/activesupport/test/dependencies/cross_site_depender.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class CrossSiteDepender
CrossSiteDependency
end
diff --git a/activesupport/test/dependencies/mutual_one.rb b/activesupport/test/dependencies/mutual_one.rb
index 05f08f82d3..bb48fddd4d 100644
--- a/activesupport/test/dependencies/mutual_one.rb
+++ b/activesupport/test/dependencies/mutual_one.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
$mutual_dependencies_count += 1
require_dependency "mutual_two"
require_dependency "mutual_two.rb"
diff --git a/activesupport/test/dependencies/mutual_two.rb b/activesupport/test/dependencies/mutual_two.rb
index 1d87d334af..ed354ed75e 100644
--- a/activesupport/test/dependencies/mutual_two.rb
+++ b/activesupport/test/dependencies/mutual_two.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
$mutual_dependencies_count += 1
require_dependency "mutual_one.rb"
require_dependency "mutual_one"
diff --git a/activesupport/test/dependencies/raises_exception.rb b/activesupport/test/dependencies/raises_exception.rb
index 0a56680fe3..67bfaabb3e 100644
--- a/activesupport/test/dependencies/raises_exception.rb
+++ b/activesupport/test/dependencies/raises_exception.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
$raises_exception_load_count += 1
raise Exception, "Loading me failed, so do not add to loaded or history."
$raises_exception_load_count += 1
diff --git a/activesupport/test/dependencies/raises_exception_without_blame_file.rb b/activesupport/test/dependencies/raises_exception_without_blame_file.rb
index 7c3856b1e6..3a6533cd3a 100644
--- a/activesupport/test/dependencies/raises_exception_without_blame_file.rb
+++ b/activesupport/test/dependencies/raises_exception_without_blame_file.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
exception = Exception.new("I am not blamable!")
class << exception
undef_method(:blame_file!)
diff --git a/activesupport/test/dependencies/requires_nonexistent0.rb b/activesupport/test/dependencies/requires_nonexistent0.rb
index 7f8a0d8419..d09dbd2485 100644
--- a/activesupport/test/dependencies/requires_nonexistent0.rb
+++ b/activesupport/test/dependencies/requires_nonexistent0.rb
@@ -1 +1,3 @@
+# frozen_string_literal: true
+
require "RMagickDontExistDude"
diff --git a/activesupport/test/dependencies/requires_nonexistent1.rb b/activesupport/test/dependencies/requires_nonexistent1.rb
index 0055177d67..ce96229172 100644
--- a/activesupport/test/dependencies/requires_nonexistent1.rb
+++ b/activesupport/test/dependencies/requires_nonexistent1.rb
@@ -1 +1,3 @@
+# frozen_string_literal: true
+
require_dependency "requires_nonexistent0"
diff --git a/activesupport/test/dependencies/service_one.rb b/activesupport/test/dependencies/service_one.rb
index afc3042269..2a4a39144d 100644
--- a/activesupport/test/dependencies/service_one.rb
+++ b/activesupport/test/dependencies/service_one.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
$loaded_service_one ||= 0
$loaded_service_one += 1
diff --git a/activesupport/test/dependencies/service_two.rb b/activesupport/test/dependencies/service_two.rb
index aabfc3c553..29cd73cbcd 100644
--- a/activesupport/test/dependencies/service_two.rb
+++ b/activesupport/test/dependencies/service_two.rb
@@ -1,2 +1,4 @@
+# frozen_string_literal: true
+
class ServiceTwo
end
diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb
index 189e54f979..d636da46d2 100644
--- a/activesupport/test/dependencies_test.rb
+++ b/activesupport/test/dependencies_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "pp"
require "active_support/dependencies"
@@ -104,7 +106,7 @@ class DependenciesTest < ActiveSupport::TestCase
with_loading "dependencies" do
old_warnings, ActiveSupport::Dependencies.warnings_on_first_load = ActiveSupport::Dependencies.warnings_on_first_load, true
filename = "check_warnings"
- expanded = File.expand_path("#{File.dirname(__FILE__)}/dependencies/#{filename}")
+ expanded = File.expand_path("dependencies/#{filename}", __dir__)
$check_warnings_load_count = 0
assert_not ActiveSupport::Dependencies.loaded.include?(expanded)
@@ -293,7 +295,7 @@ class DependenciesTest < ActiveSupport::TestCase
end
def test_doesnt_break_normal_require
- path = File.expand_path("../autoloading_fixtures/load_path", __FILE__)
+ path = File.expand_path("autoloading_fixtures/load_path", __dir__)
original_path = $:.dup
$:.push(path)
with_autoloading_fixtures do
@@ -312,7 +314,7 @@ class DependenciesTest < ActiveSupport::TestCase
end
def test_doesnt_break_normal_require_nested
- path = File.expand_path("../autoloading_fixtures/load_path", __FILE__)
+ path = File.expand_path("autoloading_fixtures/load_path", __dir__)
original_path = $:.dup
$:.push(path)
@@ -332,7 +334,7 @@ class DependenciesTest < ActiveSupport::TestCase
end
def test_require_returns_true_when_file_not_yet_required
- path = File.expand_path("../autoloading_fixtures/load_path", __FILE__)
+ path = File.expand_path("autoloading_fixtures/load_path", __dir__)
original_path = $:.dup
$:.push(path)
@@ -345,7 +347,7 @@ class DependenciesTest < ActiveSupport::TestCase
end
def test_require_returns_true_when_file_not_yet_required_even_when_no_new_constants_added
- path = File.expand_path("../autoloading_fixtures/load_path", __FILE__)
+ path = File.expand_path("autoloading_fixtures/load_path", __dir__)
original_path = $:.dup
$:.push(path)
@@ -359,7 +361,7 @@ class DependenciesTest < ActiveSupport::TestCase
end
def test_require_returns_false_when_file_already_required
- path = File.expand_path("../autoloading_fixtures/load_path", __FILE__)
+ path = File.expand_path("autoloading_fixtures/load_path", __dir__)
original_path = $:.dup
$:.push(path)
@@ -379,7 +381,7 @@ class DependenciesTest < ActiveSupport::TestCase
end
def test_load_returns_true_when_file_found
- path = File.expand_path("../autoloading_fixtures/load_path", __FILE__)
+ path = File.expand_path("autoloading_fixtures/load_path", __dir__)
original_path = $:.dup
$:.push(path)
@@ -438,7 +440,7 @@ class DependenciesTest < ActiveSupport::TestCase
def test_loadable_constants_for_path_should_handle_relative_paths
fake_root = "dependencies"
- relative_root = File.dirname(__FILE__) + "/dependencies"
+ relative_root = File.expand_path("dependencies", __dir__)
["", "/"].each do |suffix|
with_loading fake_root + suffix do
assert_equal ["A::B"], ActiveSupport::Dependencies.loadable_constants_for_path(relative_root + "/a/b")
@@ -463,7 +465,7 @@ class DependenciesTest < ActiveSupport::TestCase
end
def test_loadable_constants_with_load_path_without_trailing_slash
- path = File.dirname(__FILE__) + "/autoloading_fixtures/class_folder/inline_class.rb"
+ path = File.expand_path("autoloading_fixtures/class_folder/inline_class.rb", __dir__)
with_loading "autoloading_fixtures/class/" do
assert_equal [], ActiveSupport::Dependencies.loadable_constants_for_path(path)
end
@@ -552,7 +554,6 @@ class DependenciesTest < ActiveSupport::TestCase
assert_equal autoload + "/conflict.rb", ActiveSupport::Dependencies.search_for_file("conflict")
end
-
end
def test_custom_const_missing_should_work
@@ -992,7 +993,7 @@ class DependenciesTest < ActiveSupport::TestCase
def test_remove_constant_does_not_trigger_loading_autoloads
constant = "ShouldNotBeAutoloaded"
Object.class_eval do
- autoload constant, File.expand_path("../autoloading_fixtures/should_not_be_required", __FILE__)
+ autoload constant, File.expand_path("autoloading_fixtures/should_not_be_required", __dir__)
end
assert_nil ActiveSupport::Dependencies.remove_constant(constant), "Kernel#autoload has been triggered by remove_constant"
diff --git a/activesupport/test/dependencies_test_helpers.rb b/activesupport/test/dependencies_test_helpers.rb
index 9bc63ed89e..b54a7e70c8 100644
--- a/activesupport/test/dependencies_test_helpers.rb
+++ b/activesupport/test/dependencies_test_helpers.rb
@@ -1,7 +1,9 @@
+# frozen_string_literal: true
+
module DependenciesTestHelpers
def with_loading(*from)
old_mechanism, ActiveSupport::Dependencies.mechanism = ActiveSupport::Dependencies.mechanism, :load
- this_dir = File.dirname(__FILE__)
+ this_dir = __dir__
parent_dir = File.dirname(this_dir)
path_copy = $LOAD_PATH.dup
$LOAD_PATH.unshift(parent_dir) unless $LOAD_PATH.include?(parent_dir)
diff --git a/activesupport/test/deprecation/method_wrappers_test.rb b/activesupport/test/deprecation/method_wrappers_test.rb
index 85d057bb02..439e117c1d 100644
--- a/activesupport/test/deprecation/method_wrappers_test.rb
+++ b/activesupport/test/deprecation/method_wrappers_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/deprecation"
@@ -6,6 +8,16 @@ class MethodWrappersTest < ActiveSupport::TestCase
@klass = Class.new do
def new_method; "abc" end
alias_method :old_method, :new_method
+
+ protected
+
+ def new_protected_method; "abc" end
+ alias_method :old_protected_method, :new_protected_method
+
+ private
+
+ def new_private_method; "abc" end
+ alias_method :old_private_method, :new_private_method
end
end
@@ -31,4 +43,16 @@ class MethodWrappersTest < ActiveSupport::TestCase
assert_deprecated(warning, deprecator) { assert_equal "abc", @klass.new.old_method }
end
+
+ def test_deprecate_methods_protected_method
+ ActiveSupport::Deprecation.deprecate_methods(@klass, old_protected_method: :new_protected_method)
+
+ assert(@klass.protected_method_defined?(:old_protected_method))
+ end
+
+ def test_deprecate_methods_private_method
+ ActiveSupport::Deprecation.deprecate_methods(@klass, old_private_method: :new_private_method)
+
+ assert(@klass.private_method_defined?(:old_private_method))
+ end
end
diff --git a/activesupport/test/deprecation/proxy_wrappers_test.rb b/activesupport/test/deprecation/proxy_wrappers_test.rb
index 67afd75c44..2f866775f6 100644
--- a/activesupport/test/deprecation/proxy_wrappers_test.rb
+++ b/activesupport/test/deprecation/proxy_wrappers_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/deprecation"
diff --git a/activesupport/test/deprecation_test.rb b/activesupport/test/deprecation_test.rb
index 5f72fbf662..f2267a822f 100644
--- a/activesupport/test/deprecation_test.rb
+++ b/activesupport/test/deprecation_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/testing/stream"
@@ -35,6 +37,18 @@ class Deprecatee
A = ActiveSupport::Deprecation::DeprecatedConstantProxy.new("Deprecatee::A", "Deprecatee::B::C")
end
+class DeprecateeWithAccessor
+ include ActiveSupport::Deprecation::DeprecatedConstantAccessor
+
+ module B
+ C = 1
+ end
+ deprecate_constant "A", "DeprecateeWithAccessor::B::C"
+
+ class NewException < StandardError; end
+ deprecate_constant "OldException", "DeprecateeWithAccessor::NewException"
+end
+
class DeprecationTest < ActiveSupport::TestCase
include ActiveSupport::Testing::Stream
@@ -88,16 +102,18 @@ class DeprecationTest < ActiveSupport::TestCase
end
def test_several_behaviors
- @a, @b = nil, nil
+ @a, @b, @c = nil, nil, nil
ActiveSupport::Deprecation.behavior = [
- Proc.new { |msg, callstack| @a = msg },
- Proc.new { |msg, callstack| @b = msg }
+ lambda { |msg, callstack, horizon, gem| @a = msg },
+ lambda { |msg, callstack| @b = msg },
+ lambda { |*args| @c = args },
]
@dtc.partially
assert_match(/foo=nil/, @a)
assert_match(/foo=nil/, @b)
+ assert_equal 4, @c.size
end
def test_raise_behaviour
@@ -107,7 +123,7 @@ class DeprecationTest < ActiveSupport::TestCase
callstack = caller_locations
e = assert_raise ActiveSupport::DeprecationException do
- ActiveSupport::Deprecation.behavior.first.call(message, callstack)
+ ActiveSupport::Deprecation.behavior.first.call(message, callstack, "horizon", "gem")
end
assert_equal message, e.message
assert_equal callstack.map(&:to_s), e.backtrace.map(&:to_s)
@@ -118,7 +134,7 @@ class DeprecationTest < ActiveSupport::TestCase
behavior = ActiveSupport::Deprecation.behavior.first
content = capture(:stderr) {
- assert_nil behavior.call("Some error!", ["call stack!"])
+ assert_nil behavior.call("Some error!", ["call stack!"], "horizon", "gem")
}
assert_match(/Some error!/, content)
assert_match(/call stack!/, content)
@@ -140,11 +156,32 @@ class DeprecationTest < ActiveSupport::TestCase
behavior = ActiveSupport::Deprecation.behavior.first
stderr_output = capture(:stderr) {
- assert_nil behavior.call("Some error!", ["call stack!"])
+ assert_nil behavior.call("Some error!", ["call stack!"], "horizon", "gem")
}
assert stderr_output.empty?
end
+ def test_default_notify_behavior
+ ActiveSupport::Deprecation.behavior = :notify
+ behavior = ActiveSupport::Deprecation.behavior.first
+
+ begin
+ events = []
+ ActiveSupport::Notifications.subscribe("deprecation.my_gem_custom") { |_, **args|
+ events << args
+ }
+
+ assert_nil behavior.call("Some error!", ["call stack!"], "horizon", "MyGem::Custom")
+ assert_equal 1, events.size
+ assert_equal "Some error!", events.first[:message]
+ assert_equal ["call stack!"], events.first[:callstack]
+ assert_equal "horizon", events.first[:deprecation_horizon]
+ assert_equal "MyGem::Custom", events.first[:gem_name]
+ ensure
+ ActiveSupport::Notifications.unsubscribe("deprecation.my_gem_custom")
+ end
+ end
+
def test_deprecated_instance_variable_proxy
assert_not_deprecated { @dtc.request.size }
@@ -162,6 +199,17 @@ class DeprecationTest < ActiveSupport::TestCase
assert_not_deprecated { assert_equal Deprecatee::B::C.class, Deprecatee::A.class }
end
+ def test_deprecated_constant_accessor
+ assert_not_deprecated { DeprecateeWithAccessor::B::C }
+ assert_deprecated("DeprecateeWithAccessor::A") { assert_equal DeprecateeWithAccessor::B::C, DeprecateeWithAccessor::A }
+ end
+
+ def test_deprecated_constant_accessor_exception
+ raise DeprecateeWithAccessor::NewException.new("Test")
+ rescue DeprecateeWithAccessor::OldException => e
+ assert_kind_of DeprecateeWithAccessor::NewException, e
+ end
+
def test_assert_deprecated_raises_when_method_not_deprecated
assert_raises(Minitest::Assertion) { assert_deprecated { @dtc.not } }
end
diff --git a/activesupport/test/descendants_tracker_test_cases.rb b/activesupport/test/descendants_tracker_test_cases.rb
index cf349d53ee..1f8b4a8605 100644
--- a/activesupport/test/descendants_tracker_test_cases.rb
+++ b/activesupport/test/descendants_tracker_test_cases.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "set"
module DescendantsTrackerTestCases
diff --git a/activesupport/test/descendants_tracker_with_autoloading_test.rb b/activesupport/test/descendants_tracker_with_autoloading_test.rb
index e202667a8a..7c396b7c8e 100644
--- a/activesupport/test/descendants_tracker_with_autoloading_test.rb
+++ b/activesupport/test/descendants_tracker_with_autoloading_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/descendants_tracker"
require "active_support/dependencies"
diff --git a/activesupport/test/descendants_tracker_without_autoloading_test.rb b/activesupport/test/descendants_tracker_without_autoloading_test.rb
index 72adc30ace..f5c6a3045d 100644
--- a/activesupport/test/descendants_tracker_without_autoloading_test.rb
+++ b/activesupport/test/descendants_tracker_without_autoloading_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/descendants_tracker"
require "descendants_tracker_test_cases"
diff --git a/activesupport/test/digest_test.rb b/activesupport/test/digest_test.rb
new file mode 100644
index 0000000000..83ff2a8d83
--- /dev/null
+++ b/activesupport/test/digest_test.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require "abstract_unit"
+require "openssl"
+
+class DigestTest < ActiveSupport::TestCase
+ class InvalidDigest; end
+ def test_with_default_hash_digest_class
+ assert_equal ::Digest::MD5.hexdigest("hello friend"), ActiveSupport::Digest.hexdigest("hello friend")
+ end
+
+ def test_with_custom_hash_digest_class
+ original_hash_digest_class = ActiveSupport::Digest.hash_digest_class
+
+ ActiveSupport::Digest.hash_digest_class = ::Digest::SHA1
+ digest = ActiveSupport::Digest.hexdigest("hello friend")
+
+ assert_equal 32, digest.length
+ assert_equal ::Digest::SHA1.hexdigest("hello friend")[0...32], digest
+ ensure
+ ActiveSupport::Digest.hash_digest_class = original_hash_digest_class
+ end
+
+ def test_should_raise_argument_error_if_custom_digest_is_missing_hexdigest_method
+ assert_raises(ArgumentError) { ActiveSupport::Digest.hash_digest_class = InvalidDigest }
+ end
+end
diff --git a/activesupport/test/encrypted_configuration_test.rb b/activesupport/test/encrypted_configuration_test.rb
new file mode 100644
index 0000000000..93ccf457de
--- /dev/null
+++ b/activesupport/test/encrypted_configuration_test.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+require "abstract_unit"
+require "active_support/encrypted_configuration"
+
+class EncryptedConfigurationTest < ActiveSupport::TestCase
+ setup do
+ @credentials_config_path = File.join(Dir.tmpdir, "credentials.yml.enc")
+
+ @credentials_key_path = File.join(Dir.tmpdir, "master.key")
+ File.write(@credentials_key_path, ActiveSupport::EncryptedConfiguration.generate_key)
+
+ @credentials = ActiveSupport::EncryptedConfiguration.new(
+ config_path: @credentials_config_path, key_path: @credentials_key_path,
+ env_key: "RAILS_MASTER_KEY", raise_if_missing_key: true
+ )
+ end
+
+ teardown do
+ FileUtils.rm_rf @credentials_config_path
+ FileUtils.rm_rf @credentials_key_path
+ end
+
+ test "reading configuration by env key" do
+ FileUtils.rm_rf @credentials_key_path
+
+ begin
+ ENV["RAILS_MASTER_KEY"] = ActiveSupport::EncryptedConfiguration.generate_key
+ @credentials.write({ something: { good: true, bad: false } }.to_yaml)
+
+ assert @credentials[:something][:good]
+ assert_not @credentials.dig(:something, :bad)
+ assert_nil @credentials.fetch(:nothing, nil)
+ ensure
+ ENV["RAILS_MASTER_KEY"] = nil
+ end
+ end
+
+ test "reading configuration by key file" do
+ @credentials.write({ something: { good: true } }.to_yaml)
+
+ assert @credentials.something[:good]
+ end
+
+ test "change configuration by key file" do
+ @credentials.write({ something: { good: true } }.to_yaml)
+ @credentials.change do |config_file|
+ config = YAML.load(config_file.read)
+ config_file.write config.merge(new: "things").to_yaml
+ end
+
+ assert @credentials.something[:good]
+ assert_equal "things", @credentials[:new]
+ end
+
+ test "raise error when writing an invalid format value" do
+ assert_raise(Psych::SyntaxError) do
+ @credentials.change do |config_file|
+ config_file.write "login: *login\n username: dummy"
+ end
+ end
+ end
+
+ test "raises key error when accessing config via bang method" do
+ assert_raise(KeyError) { @credentials.something! }
+ end
+end
diff --git a/activesupport/test/encrypted_file_test.rb b/activesupport/test/encrypted_file_test.rb
new file mode 100644
index 0000000000..ba3bbef903
--- /dev/null
+++ b/activesupport/test/encrypted_file_test.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+require "abstract_unit"
+require "active_support/encrypted_file"
+
+class EncryptedFileTest < ActiveSupport::TestCase
+ setup do
+ @content = "One little fox jumped over the hedge"
+
+ @content_path = File.join(Dir.tmpdir, "content.txt.enc")
+
+ @key_path = File.join(Dir.tmpdir, "content.txt.key")
+ File.write(@key_path, ActiveSupport::EncryptedFile.generate_key)
+
+ @encrypted_file = ActiveSupport::EncryptedFile.new(
+ content_path: @content_path, key_path: @key_path, env_key: "CONTENT_KEY", raise_if_missing_key: true
+ )
+ end
+
+ teardown do
+ FileUtils.rm_rf @content_path
+ FileUtils.rm_rf @key_path
+ end
+
+ test "reading content by env key" do
+ FileUtils.rm_rf @key_path
+
+ begin
+ ENV["CONTENT_KEY"] = ActiveSupport::EncryptedFile.generate_key
+ @encrypted_file.write @content
+
+ assert_equal @content, @encrypted_file.read
+ ensure
+ ENV["CONTENT_KEY"] = nil
+ end
+ end
+
+ test "reading content by key file" do
+ @encrypted_file.write(@content)
+ assert_equal @content, @encrypted_file.read
+ end
+
+ test "change content by key file" do
+ @encrypted_file.write(@content)
+ @encrypted_file.change do |file|
+ file.write(file.read + " and went by the lake")
+ end
+
+ assert_equal "#{@content} and went by the lake", @encrypted_file.read
+ end
+
+ test "raise MissingKeyError when key is missing" do
+ assert_raise(ActiveSupport::EncryptedFile::MissingKeyError) do
+ ActiveSupport::EncryptedFile.new(
+ content_path: @content_path, key_path: "", env_key: "", raise_if_missing_key: true
+ ).read
+ end
+ end
+end
diff --git a/activesupport/test/evented_file_update_checker_test.rb b/activesupport/test/evented_file_update_checker_test.rb
index f33a5f5764..9b560f7f42 100644
--- a/activesupport/test/evented_file_update_checker_test.rb
+++ b/activesupport/test/evented_file_update_checker_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "pathname"
require "file_update_checker_shared_tests"
diff --git a/activesupport/test/executor_test.rb b/activesupport/test/executor_test.rb
index 7fefc066b3..af441064dd 100644
--- a/activesupport/test/executor_test.rb
+++ b/activesupport/test/executor_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
class ExecutorTest < ActiveSupport::TestCase
diff --git a/activesupport/test/file_update_checker_shared_tests.rb b/activesupport/test/file_update_checker_shared_tests.rb
index 361e7e2349..f8266dac06 100644
--- a/activesupport/test/file_update_checker_shared_tests.rb
+++ b/activesupport/test/file_update_checker_shared_tests.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "fileutils"
module FileUpdateCheckerSharedTests
diff --git a/activesupport/test/file_update_checker_test.rb b/activesupport/test/file_update_checker_test.rb
index 55b0b46644..ec1df0df67 100644
--- a/activesupport/test/file_update_checker_test.rb
+++ b/activesupport/test/file_update_checker_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "file_update_checker_shared_tests"
diff --git a/activesupport/test/fixtures/autoload/another_class.rb b/activesupport/test/fixtures/autoload/another_class.rb
index ab6e075ab4..cf38336cf8 100644
--- a/activesupport/test/fixtures/autoload/another_class.rb
+++ b/activesupport/test/fixtures/autoload/another_class.rb
@@ -1,2 +1,4 @@
+# frozen_string_literal: true
+
class Fixtures::AnotherClass
end
diff --git a/activesupport/test/fixtures/autoload/some_class.rb b/activesupport/test/fixtures/autoload/some_class.rb
index 30d41eb0bf..ff25eb995e 100644
--- a/activesupport/test/fixtures/autoload/some_class.rb
+++ b/activesupport/test/fixtures/autoload/some_class.rb
@@ -1,2 +1,4 @@
+# frozen_string_literal: true
+
class Fixtures::Autoload::SomeClass
end
diff --git a/activesupport/test/gzip_test.rb b/activesupport/test/gzip_test.rb
index f51d3cdf65..05ce12fe86 100644
--- a/activesupport/test/gzip_test.rb
+++ b/activesupport/test/gzip_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/core_ext/object/blank"
@@ -30,4 +32,14 @@ class GzipTest < ActiveSupport::TestCase
assert_equal true, (gzipped_by_best_compression.bytesize < gzipped_by_speed.bytesize)
end
+
+ def test_decompress_checks_crc
+ compressed = ActiveSupport::Gzip.compress("Hello World")
+ first_crc_byte_index = compressed.bytesize - 8
+ compressed.setbyte(first_crc_byte_index, compressed.getbyte(first_crc_byte_index) ^ 0xff)
+
+ assert_raises(Zlib::GzipFile::CRCError) do
+ ActiveSupport::Gzip.decompress(compressed)
+ end
+ end
end
diff --git a/activesupport/test/hash_with_indifferent_access_test.rb b/activesupport/test/hash_with_indifferent_access_test.rb
new file mode 100644
index 0000000000..41d653fa59
--- /dev/null
+++ b/activesupport/test/hash_with_indifferent_access_test.rb
@@ -0,0 +1,803 @@
+# frozen_string_literal: true
+
+require "abstract_unit"
+require "active_support/core_ext/hash"
+require "bigdecimal"
+require "active_support/core_ext/string/access"
+require "active_support/ordered_hash"
+require "active_support/core_ext/object/conversions"
+require "active_support/core_ext/object/deep_dup"
+require "active_support/inflections"
+
+class HashWithIndifferentAccessTest < ActiveSupport::TestCase
+ HashWithIndifferentAccess = ActiveSupport::HashWithIndifferentAccess
+
+ class IndifferentHash < ActiveSupport::HashWithIndifferentAccess
+ end
+
+ class SubclassingArray < Array
+ end
+
+ class SubclassingHash < Hash
+ end
+
+ class NonIndifferentHash < Hash
+ def nested_under_indifferent_access
+ self
+ end
+ end
+
+ class HashByConversion
+ def initialize(hash)
+ @hash = hash
+ end
+
+ def to_hash
+ @hash
+ end
+ end
+
+ def setup
+ @strings = { "a" => 1, "b" => 2 }
+ @nested_strings = { "a" => { "b" => { "c" => 3 } } }
+ @symbols = { a: 1, b: 2 }
+ @nested_symbols = { a: { b: { c: 3 } } }
+ @mixed = { :a => 1, "b" => 2 }
+ @nested_mixed = { "a" => { b: { "c" => 3 } } }
+ @integers = { 0 => 1, 1 => 2 }
+ @nested_integers = { 0 => { 1 => { 2 => 3 } } }
+ @illegal_symbols = { [] => 3 }
+ @nested_illegal_symbols = { [] => { [] => 3 } }
+ end
+
+ def test_symbolize_keys_for_hash_with_indifferent_access
+ assert_instance_of Hash, @symbols.with_indifferent_access.symbolize_keys
+ assert_equal @symbols, @symbols.with_indifferent_access.symbolize_keys
+ assert_equal @symbols, @strings.with_indifferent_access.symbolize_keys
+ assert_equal @symbols, @mixed.with_indifferent_access.symbolize_keys
+ end
+
+ def test_deep_symbolize_keys_for_hash_with_indifferent_access
+ assert_instance_of Hash, @nested_symbols.with_indifferent_access.deep_symbolize_keys
+ assert_equal @nested_symbols, @nested_symbols.with_indifferent_access.deep_symbolize_keys
+ assert_equal @nested_symbols, @nested_strings.with_indifferent_access.deep_symbolize_keys
+ assert_equal @nested_symbols, @nested_mixed.with_indifferent_access.deep_symbolize_keys
+ end
+
+ def test_symbolize_keys_bang_for_hash_with_indifferent_access
+ assert_raise(NoMethodError) { @symbols.with_indifferent_access.dup.symbolize_keys! }
+ assert_raise(NoMethodError) { @strings.with_indifferent_access.dup.symbolize_keys! }
+ assert_raise(NoMethodError) { @mixed.with_indifferent_access.dup.symbolize_keys! }
+ end
+
+ def test_deep_symbolize_keys_bang_for_hash_with_indifferent_access
+ assert_raise(NoMethodError) { @nested_symbols.with_indifferent_access.deep_dup.deep_symbolize_keys! }
+ assert_raise(NoMethodError) { @nested_strings.with_indifferent_access.deep_dup.deep_symbolize_keys! }
+ assert_raise(NoMethodError) { @nested_mixed.with_indifferent_access.deep_dup.deep_symbolize_keys! }
+ end
+
+ def test_symbolize_keys_preserves_keys_that_cant_be_symbolized_for_hash_with_indifferent_access
+ assert_equal @illegal_symbols, @illegal_symbols.with_indifferent_access.symbolize_keys
+ assert_raise(NoMethodError) { @illegal_symbols.with_indifferent_access.dup.symbolize_keys! }
+ end
+
+ def test_deep_symbolize_keys_preserves_keys_that_cant_be_symbolized_for_hash_with_indifferent_access
+ assert_equal @nested_illegal_symbols, @nested_illegal_symbols.with_indifferent_access.deep_symbolize_keys
+ assert_raise(NoMethodError) { @nested_illegal_symbols.with_indifferent_access.deep_dup.deep_symbolize_keys! }
+ end
+
+ def test_symbolize_keys_preserves_integer_keys_for_hash_with_indifferent_access
+ assert_equal @integers, @integers.with_indifferent_access.symbolize_keys
+ assert_raise(NoMethodError) { @integers.with_indifferent_access.dup.symbolize_keys! }
+ end
+
+ def test_deep_symbolize_keys_preserves_integer_keys_for_hash_with_indifferent_access
+ assert_equal @nested_integers, @nested_integers.with_indifferent_access.deep_symbolize_keys
+ assert_raise(NoMethodError) { @nested_integers.with_indifferent_access.deep_dup.deep_symbolize_keys! }
+ end
+
+ def test_stringify_keys_for_hash_with_indifferent_access
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, @symbols.with_indifferent_access.stringify_keys
+ assert_equal @strings, @symbols.with_indifferent_access.stringify_keys
+ assert_equal @strings, @strings.with_indifferent_access.stringify_keys
+ assert_equal @strings, @mixed.with_indifferent_access.stringify_keys
+ end
+
+ def test_deep_stringify_keys_for_hash_with_indifferent_access
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, @nested_symbols.with_indifferent_access.deep_stringify_keys
+ assert_equal @nested_strings, @nested_symbols.with_indifferent_access.deep_stringify_keys
+ assert_equal @nested_strings, @nested_strings.with_indifferent_access.deep_stringify_keys
+ assert_equal @nested_strings, @nested_mixed.with_indifferent_access.deep_stringify_keys
+ end
+
+ def test_stringify_keys_bang_for_hash_with_indifferent_access
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, @symbols.with_indifferent_access.dup.stringify_keys!
+ assert_equal @strings, @symbols.with_indifferent_access.dup.stringify_keys!
+ assert_equal @strings, @strings.with_indifferent_access.dup.stringify_keys!
+ assert_equal @strings, @mixed.with_indifferent_access.dup.stringify_keys!
+ end
+
+ def test_deep_stringify_keys_bang_for_hash_with_indifferent_access
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, @nested_symbols.with_indifferent_access.dup.deep_stringify_keys!
+ assert_equal @nested_strings, @nested_symbols.with_indifferent_access.deep_dup.deep_stringify_keys!
+ assert_equal @nested_strings, @nested_strings.with_indifferent_access.deep_dup.deep_stringify_keys!
+ assert_equal @nested_strings, @nested_mixed.with_indifferent_access.deep_dup.deep_stringify_keys!
+ end
+
+ def test_nested_under_indifferent_access
+ foo = { "foo" => SubclassingHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access
+ assert_kind_of ActiveSupport::HashWithIndifferentAccess, foo["foo"]
+
+ foo = { "foo" => NonIndifferentHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access
+ assert_kind_of NonIndifferentHash, foo["foo"]
+
+ foo = { "foo" => IndifferentHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access
+ assert_kind_of IndifferentHash, foo["foo"]
+ end
+
+ def test_indifferent_assorted
+ @strings = @strings.with_indifferent_access
+ @symbols = @symbols.with_indifferent_access
+ @mixed = @mixed.with_indifferent_access
+
+ assert_equal "a", @strings.__send__(:convert_key, :a)
+
+ assert_equal 1, @strings.fetch("a")
+ assert_equal 1, @strings.fetch(:a.to_s)
+ assert_equal 1, @strings.fetch(:a)
+
+ hashes = { :@strings => @strings, :@symbols => @symbols, :@mixed => @mixed }
+ method_map = { '[]': 1, fetch: 1, values_at: [1],
+ has_key?: true, include?: true, key?: true,
+ member?: true }
+
+ hashes.each do |name, hash|
+ method_map.sort_by(&:to_s).each do |meth, expected|
+ assert_equal(expected, hash.__send__(meth, "a"),
+ "Calling #{name}.#{meth} 'a'")
+ assert_equal(expected, hash.__send__(meth, :a),
+ "Calling #{name}.#{meth} :a")
+ end
+ end
+
+ assert_equal [1, 2], @strings.values_at("a", "b")
+ assert_equal [1, 2], @strings.values_at(:a, :b)
+ assert_equal [1, 2], @symbols.values_at("a", "b")
+ assert_equal [1, 2], @symbols.values_at(:a, :b)
+ assert_equal [1, 2], @mixed.values_at("a", "b")
+ assert_equal [1, 2], @mixed.values_at(:a, :b)
+ end
+
+ def test_indifferent_fetch_values
+ skip unless Hash.method_defined?(:fetch_values)
+
+ @mixed = @mixed.with_indifferent_access
+
+ assert_equal [1, 2], @mixed.fetch_values("a", "b")
+ assert_equal [1, 2], @mixed.fetch_values(:a, :b)
+ assert_equal [1, 2], @mixed.fetch_values(:a, "b")
+ assert_equal [1, "c"], @mixed.fetch_values(:a, :c) { |key| key }
+ assert_raise(KeyError) { @mixed.fetch_values(:a, :c) }
+ end
+
+ def test_indifferent_reading
+ hash = HashWithIndifferentAccess.new
+ hash["a"] = 1
+ hash["b"] = true
+ hash["c"] = false
+ hash["d"] = nil
+
+ assert_equal 1, hash[:a]
+ assert_equal true, hash[:b]
+ assert_equal false, hash[:c]
+ assert_nil hash[:d]
+ assert_nil hash[:e]
+ end
+
+ def test_indifferent_reading_with_nonnil_default
+ hash = HashWithIndifferentAccess.new(1)
+ hash["a"] = 1
+ hash["b"] = true
+ hash["c"] = false
+ hash["d"] = nil
+
+ assert_equal 1, hash[:a]
+ assert_equal true, hash[:b]
+ assert_equal false, hash[:c]
+ assert_nil hash[:d]
+ assert_equal 1, hash[:e]
+ end
+
+ def test_indifferent_writing
+ hash = HashWithIndifferentAccess.new
+ hash[:a] = 1
+ hash["b"] = 2
+ hash[3] = 3
+
+ assert_equal 1, hash["a"]
+ assert_equal 2, hash["b"]
+ assert_equal 1, hash[:a]
+ assert_equal 2, hash[:b]
+ assert_equal 3, hash[3]
+ end
+
+ def test_indifferent_update
+ hash = HashWithIndifferentAccess.new
+ hash[:a] = "a"
+ hash["b"] = "b"
+
+ updated_with_strings = hash.update(@strings)
+ updated_with_symbols = hash.update(@symbols)
+ updated_with_mixed = hash.update(@mixed)
+
+ assert_equal 1, updated_with_strings[:a]
+ assert_equal 1, updated_with_strings["a"]
+ assert_equal 2, updated_with_strings["b"]
+
+ assert_equal 1, updated_with_symbols[:a]
+ assert_equal 2, updated_with_symbols["b"]
+ assert_equal 2, updated_with_symbols[:b]
+
+ assert_equal 1, updated_with_mixed[:a]
+ assert_equal 2, updated_with_mixed["b"]
+
+ assert [updated_with_strings, updated_with_symbols, updated_with_mixed].all? { |h| h.keys.size == 2 }
+ end
+
+ def test_update_with_to_hash_conversion
+ hash = HashWithIndifferentAccess.new
+ hash.update HashByConversion.new(a: 1)
+ assert_equal 1, hash["a"]
+ end
+
+ def test_indifferent_merging
+ hash = HashWithIndifferentAccess.new
+ hash[:a] = "failure"
+ hash["b"] = "failure"
+
+ other = { "a" => 1, :b => 2 }
+
+ merged = hash.merge(other)
+
+ assert_equal HashWithIndifferentAccess, merged.class
+ assert_equal 1, merged[:a]
+ assert_equal 2, merged["b"]
+
+ hash.update(other)
+
+ assert_equal 1, hash[:a]
+ assert_equal 2, hash["b"]
+ end
+
+ def test_merge_with_to_hash_conversion
+ hash = HashWithIndifferentAccess.new
+ merged = hash.merge HashByConversion.new(a: 1)
+ assert_equal 1, merged["a"]
+ end
+
+ def test_indifferent_replace
+ hash = HashWithIndifferentAccess.new
+ hash[:a] = 42
+
+ replaced = hash.replace(b: 12)
+
+ assert hash.key?("b")
+ assert !hash.key?(:a)
+ assert_equal 12, hash[:b]
+ assert_same hash, replaced
+ end
+
+ def test_replace_with_to_hash_conversion
+ hash = HashWithIndifferentAccess.new
+ hash[:a] = 42
+
+ replaced = hash.replace(HashByConversion.new(b: 12))
+
+ assert hash.key?("b")
+ assert !hash.key?(:a)
+ assert_equal 12, hash[:b]
+ assert_same hash, replaced
+ end
+
+ def test_indifferent_merging_with_block
+ hash = HashWithIndifferentAccess.new
+ hash[:a] = 1
+ hash["b"] = 3
+
+ other = { "a" => 4, :b => 2, "c" => 10 }
+
+ merged = hash.merge(other) { |key, old, new| old > new ? old : new }
+
+ assert_equal HashWithIndifferentAccess, merged.class
+ assert_equal 4, merged[:a]
+ assert_equal 3, merged["b"]
+ assert_equal 10, merged[:c]
+
+ other_indifferent = HashWithIndifferentAccess.new("a" => 9, :b => 2)
+
+ merged = hash.merge(other_indifferent) { |key, old, new| old + new }
+
+ assert_equal HashWithIndifferentAccess, merged.class
+ assert_equal 10, merged[:a]
+ assert_equal 5, merged[:b]
+ end
+
+ def test_indifferent_reverse_merging
+ hash = HashWithIndifferentAccess.new key: :old_value
+ hash.reverse_merge! key: :new_value
+ assert_equal :old_value, hash[:key]
+
+ hash = HashWithIndifferentAccess.new("some" => "value", "other" => "value")
+ hash.reverse_merge!(some: "noclobber", another: "clobber")
+ assert_equal "value", hash[:some]
+ assert_equal "clobber", hash[:another]
+ end
+
+ def test_indifferent_with_defaults_aliases_reverse_merge
+ hash = HashWithIndifferentAccess.new key: :old_value
+ actual = hash.with_defaults key: :new_value
+ assert_equal :old_value, actual[:key]
+
+ hash = HashWithIndifferentAccess.new key: :old_value
+ hash.with_defaults! key: :new_value
+ assert_equal :old_value, hash[:key]
+ end
+
+ def test_indifferent_deleting
+ get_hash = proc { { a: "foo" }.with_indifferent_access }
+ hash = get_hash.call
+ assert_equal "foo", hash.delete(:a)
+ assert_nil hash.delete(:a)
+ hash = get_hash.call
+ assert_equal "foo", hash.delete("a")
+ assert_nil hash.delete("a")
+ end
+
+ def test_indifferent_select
+ hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).select { |k, v| v == 1 }
+
+ assert_equal({ "a" => 1 }, hash)
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash
+ end
+
+ def test_indifferent_select_returns_enumerator
+ enum = ActiveSupport::HashWithIndifferentAccess.new(@strings).select
+ assert_instance_of Enumerator, enum
+ end
+
+ def test_indifferent_select_returns_a_hash_when_unchanged
+ hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).select { |k, v| true }
+
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash
+ end
+
+ def test_indifferent_select_bang
+ indifferent_strings = ActiveSupport::HashWithIndifferentAccess.new(@strings)
+ indifferent_strings.select! { |k, v| v == 1 }
+
+ assert_equal({ "a" => 1 }, indifferent_strings)
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, indifferent_strings
+ end
+
+ def test_indifferent_reject
+ hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).reject { |k, v| v != 1 }
+
+ assert_equal({ "a" => 1 }, hash)
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash
+ end
+
+ def test_indifferent_reject_returns_enumerator
+ enum = ActiveSupport::HashWithIndifferentAccess.new(@strings).reject
+ assert_instance_of Enumerator, enum
+ end
+
+ def test_indifferent_reject_bang
+ indifferent_strings = ActiveSupport::HashWithIndifferentAccess.new(@strings)
+ indifferent_strings.reject! { |k, v| v != 1 }
+
+ assert_equal({ "a" => 1 }, indifferent_strings)
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, indifferent_strings
+ end
+
+ def test_indifferent_transform_keys
+ hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).transform_keys { |k| k * 2 }
+
+ assert_equal({ "aa" => 1, "bb" => 2 }, hash)
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash
+ end
+
+ def test_indifferent_transform_keys_bang
+ indifferent_strings = ActiveSupport::HashWithIndifferentAccess.new(@strings)
+ indifferent_strings.transform_keys! { |k| k * 2 }
+
+ assert_equal({ "aa" => 1, "bb" => 2 }, indifferent_strings)
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, indifferent_strings
+ end
+
+ def test_indifferent_transform_values
+ hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).transform_values { |v| v * 2 }
+
+ assert_equal({ "a" => 2, "b" => 4 }, hash)
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash
+ end
+
+ def test_indifferent_transform_values_bang
+ indifferent_strings = ActiveSupport::HashWithIndifferentAccess.new(@strings)
+ indifferent_strings.transform_values! { |v| v * 2 }
+
+ assert_equal({ "a" => 2, "b" => 4 }, indifferent_strings)
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, indifferent_strings
+ end
+
+ def test_indifferent_compact
+ hash_contain_nil_value = @strings.merge("z" => nil)
+ hash = ActiveSupport::HashWithIndifferentAccess.new(hash_contain_nil_value)
+ compacted_hash = hash.compact
+
+ assert_equal(@strings, compacted_hash)
+ assert_equal(hash_contain_nil_value, hash)
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, compacted_hash
+
+ empty_hash = ActiveSupport::HashWithIndifferentAccess.new
+ compacted_hash = empty_hash.compact
+
+ assert_equal compacted_hash, empty_hash
+
+ non_empty_hash = ActiveSupport::HashWithIndifferentAccess.new(foo: :bar)
+ compacted_hash = non_empty_hash.compact
+
+ assert_equal compacted_hash, non_empty_hash
+ end
+
+ def test_indifferent_to_hash
+ # Should convert to a Hash with String keys.
+ assert_equal @strings, @mixed.with_indifferent_access.to_hash
+
+ # Should preserve the default value.
+ mixed_with_default = @mixed.dup
+ mixed_with_default.default = "1234"
+ roundtrip = mixed_with_default.with_indifferent_access.to_hash
+ assert_equal @strings, roundtrip
+ assert_equal "1234", roundtrip.default
+
+ # Ensure nested hashes are not HashWithIndiffereneAccess
+ new_to_hash = @nested_mixed.with_indifferent_access.to_hash
+ assert_not new_to_hash.instance_of?(HashWithIndifferentAccess)
+ assert_not new_to_hash["a"].instance_of?(HashWithIndifferentAccess)
+ assert_not new_to_hash["a"]["b"].instance_of?(HashWithIndifferentAccess)
+ end
+
+ def test_lookup_returns_the_same_object_that_is_stored_in_hash_indifferent_access
+ hash = HashWithIndifferentAccess.new { |h, k| h[k] = [] }
+ hash[:a] << 1
+
+ assert_equal [1], hash[:a]
+ end
+
+ def test_with_indifferent_access_has_no_side_effects_on_existing_hash
+ hash = { content: [{ :foo => :bar, "bar" => "baz" }] }
+ hash.with_indifferent_access
+
+ assert_equal [:foo, "bar"], hash[:content].first.keys
+ end
+
+ def test_indifferent_hash_with_array_of_hashes
+ hash = { "urls" => { "url" => [ { "address" => "1" }, { "address" => "2" } ] } }.with_indifferent_access
+ assert_equal "1", hash[:urls][:url].first[:address]
+
+ hash = hash.to_hash
+ assert_not hash.instance_of?(HashWithIndifferentAccess)
+ assert_not hash["urls"].instance_of?(HashWithIndifferentAccess)
+ assert_not hash["urls"]["url"].first.instance_of?(HashWithIndifferentAccess)
+ end
+
+ def test_should_preserve_array_subclass_when_value_is_array
+ array = SubclassingArray.new
+ array << { "address" => "1" }
+ hash = { "urls" => { "url" => array } }.with_indifferent_access
+ assert_equal SubclassingArray, hash[:urls][:url].class
+ end
+
+ def test_should_preserve_array_class_when_hash_value_is_frozen_array
+ array = SubclassingArray.new
+ array << { "address" => "1" }
+ hash = { "urls" => { "url" => array.freeze } }.with_indifferent_access
+ assert_equal SubclassingArray, hash[:urls][:url].class
+ end
+
+ def test_stringify_and_symbolize_keys_on_indifferent_preserves_hash
+ h = HashWithIndifferentAccess.new
+ h[:first] = 1
+ h = h.stringify_keys
+ assert_equal 1, h["first"]
+ h = HashWithIndifferentAccess.new
+ h["first"] = 1
+ h = h.symbolize_keys
+ assert_equal 1, h[:first]
+ end
+
+ def test_deep_stringify_and_deep_symbolize_keys_on_indifferent_preserves_hash
+ h = HashWithIndifferentAccess.new
+ h[:first] = 1
+ h = h.deep_stringify_keys
+ assert_equal 1, h["first"]
+ h = HashWithIndifferentAccess.new
+ h["first"] = 1
+ h = h.deep_symbolize_keys
+ assert_equal 1, h[:first]
+ end
+
+ def test_to_options_on_indifferent_preserves_hash
+ h = HashWithIndifferentAccess.new
+ h["first"] = 1
+ h.to_options!
+ assert_equal 1, h["first"]
+ end
+
+ def test_to_options_on_indifferent_preserves_works_as_hash_with_dup
+ h = HashWithIndifferentAccess.new(a: { b: "b" })
+ dup = h.dup
+
+ dup[:a][:c] = "c"
+ assert_equal "c", h[:a][:c]
+ end
+
+ def test_indifferent_sub_hashes
+ h = { "user" => { "id" => 5 } }.with_indifferent_access
+ ["user", :user].each { |user| [:id, "id"].each { |id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5" } }
+
+ h = { user: { id: 5 } }.with_indifferent_access
+ ["user", :user].each { |user| [:id, "id"].each { |id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5" } }
+ end
+
+ def test_indifferent_duplication
+ # Should preserve default value
+ h = HashWithIndifferentAccess.new
+ h.default = "1234"
+ assert_equal h.default, h.dup.default
+
+ # Should preserve class for subclasses
+ h = IndifferentHash.new
+ assert_equal h.class, h.dup.class
+ end
+
+ def test_nested_dig_indifferent_access
+ skip if RUBY_VERSION < "2.3.0"
+ data = { "this" => { "views" => 1234 } }.with_indifferent_access
+ assert_equal 1234, data.dig(:this, :views)
+ end
+
+ def test_argless_default_with_existing_nil_key
+ h = Hash.new(:default).merge(nil => "defined").with_indifferent_access
+
+ assert_equal :default, h.default
+ end
+
+ def test_default_with_argument
+ h = Hash.new { 5 }.merge(1 => 2).with_indifferent_access
+
+ assert_equal 5, h.default(1)
+ end
+
+ def test_default_proc
+ h = ActiveSupport::HashWithIndifferentAccess.new { |hash, key| key }
+
+ assert_nil h.default
+ assert_equal "foo", h.default("foo")
+ assert_equal "foo", h.default(:foo)
+ end
+
+ def test_double_conversion_with_nil_key
+ h = { nil => "defined" }.with_indifferent_access.with_indifferent_access
+
+ assert_nil h[:undefined_key]
+ end
+
+ def test_assorted_keys_not_stringified
+ original = { Object.new => 2, 1 => 2, [] => true }
+ indiff = original.with_indifferent_access
+ assert(!indiff.keys.any? { |k| k.kind_of? String }, "A key was converted to a string!")
+ end
+
+ def test_deep_merge_on_indifferent_access
+ hash_1 = HashWithIndifferentAccess.new(a: "a", b: "b", c: { c1: "c1", c2: "c2", c3: { d1: "d1" } })
+ hash_2 = HashWithIndifferentAccess.new(a: 1, c: { c1: 2, c3: { d2: "d2" } })
+ hash_3 = { a: 1, c: { c1: 2, c3: { d2: "d2" } } }
+ expected = { "a" => 1, "b" => "b", "c" => { "c1" => 2, "c2" => "c2", "c3" => { "d1" => "d1", "d2" => "d2" } } }
+ assert_equal expected, hash_1.deep_merge(hash_2)
+ assert_equal expected, hash_1.deep_merge(hash_3)
+
+ hash_1.deep_merge!(hash_2)
+ assert_equal expected, hash_1
+ end
+
+ def test_store_on_indifferent_access
+ hash = HashWithIndifferentAccess.new
+ hash.store(:test1, 1)
+ hash.store("test1", 11)
+ hash[:test2] = 2
+ hash["test2"] = 22
+ expected = { "test1" => 11, "test2" => 22 }
+ assert_equal expected, hash
+ end
+
+ def test_constructor_on_indifferent_access
+ hash = HashWithIndifferentAccess[:foo, 1]
+ assert_equal 1, hash[:foo]
+ assert_equal 1, hash["foo"]
+ hash[:foo] = 3
+ assert_equal 3, hash[:foo]
+ assert_equal 3, hash["foo"]
+ end
+
+ def test_indifferent_slice
+ original = { a: "x", b: "y", c: 10 }.with_indifferent_access
+ expected = { a: "x", b: "y" }.with_indifferent_access
+
+ [["a", "b"], [:a, :b]].each do |keys|
+ # Should return a new hash with only the given keys.
+ assert_equal expected, original.slice(*keys), keys.inspect
+ assert_not_equal expected, original
+ end
+ end
+
+ def test_indifferent_slice_inplace
+ original = { a: "x", b: "y", c: 10 }.with_indifferent_access
+ expected = { c: 10 }.with_indifferent_access
+
+ [["a", "b"], [:a, :b]].each do |keys|
+ # Should replace the hash with only the given keys.
+ copy = original.dup
+ assert_equal expected, copy.slice!(*keys)
+ end
+ end
+
+ def test_indifferent_slice_access_with_symbols
+ original = { "login" => "bender", "password" => "shiny", "stuff" => "foo" }
+ original = original.with_indifferent_access
+
+ slice = original.slice(:login, :password)
+
+ assert_equal "bender", slice[:login]
+ assert_equal "bender", slice["login"]
+ end
+
+ def test_indifferent_extract
+ original = { :a => 1, "b" => 2, :c => 3, "d" => 4 }.with_indifferent_access
+ expected = { a: 1, b: 2 }.with_indifferent_access
+ remaining = { c: 3, d: 4 }.with_indifferent_access
+
+ [["a", "b"], [:a, :b]].each do |keys|
+ copy = original.dup
+ assert_equal expected, copy.extract!(*keys)
+ assert_equal remaining, copy
+ end
+ end
+
+ def test_new_with_to_hash_conversion
+ hash = HashWithIndifferentAccess.new(HashByConversion.new(a: 1))
+ assert hash.key?("a")
+ assert_equal 1, hash[:a]
+ end
+
+ def test_dup_with_default_proc
+ hash = HashWithIndifferentAccess.new
+ hash.default_proc = proc { |h, v| raise "walrus" }
+ assert_nothing_raised { hash.dup }
+ end
+
+ def test_dup_with_default_proc_sets_proc
+ hash = HashWithIndifferentAccess.new
+ hash.default_proc = proc { |h, k| k + 1 }
+ new_hash = hash.dup
+
+ assert_equal 3, new_hash[2]
+
+ new_hash.default = 2
+ assert_equal 2, new_hash[:non_existent]
+ end
+
+ def test_to_hash_with_raising_default_proc
+ hash = HashWithIndifferentAccess.new
+ hash.default_proc = proc { |h, k| raise "walrus" }
+
+ assert_nothing_raised { hash.to_hash }
+ end
+
+ def test_new_with_to_hash_conversion_copies_default
+ normal_hash = Hash.new(3)
+ normal_hash[:a] = 1
+
+ hash = HashWithIndifferentAccess.new(HashByConversion.new(normal_hash))
+ assert_equal 1, hash[:a]
+ assert_equal 3, hash[:b]
+ end
+
+ def test_new_with_to_hash_conversion_copies_default_proc
+ normal_hash = Hash.new { 1 + 2 }
+ normal_hash[:a] = 1
+
+ hash = HashWithIndifferentAccess.new(HashByConversion.new(normal_hash))
+ assert_equal 1, hash[:a]
+ assert_equal 3, hash[:b]
+ end
+
+ def test_inheriting_from_top_level_hash_with_indifferent_access_preserves_ancestors_chain
+ klass = Class.new(::HashWithIndifferentAccess)
+ assert_equal ActiveSupport::HashWithIndifferentAccess, klass.ancestors[1]
+ end
+
+ def test_inheriting_from_hash_with_indifferent_access_properly_dumps_ivars
+ klass = Class.new(::HashWithIndifferentAccess) do
+ def initialize(*)
+ @foo = "bar"
+ super
+ end
+ end
+
+ yaml_output = klass.new.to_yaml
+
+ # `hash-with-ivars` was introduced in 2.0.9 (https://git.io/vyUQW)
+ if Gem::Version.new(Psych::VERSION) >= Gem::Version.new("2.0.9")
+ assert_includes yaml_output, "hash-with-ivars"
+ assert_includes yaml_output, "@foo: bar"
+ else
+ assert_includes yaml_output, "hash"
+ end
+ end
+
+ def test_should_use_default_proc_for_unknown_key
+ hash_wia = HashWithIndifferentAccess.new { 1 + 2 }
+ assert_equal 3, hash_wia[:new_key]
+ end
+
+ def test_should_return_nil_if_no_key_is_supplied
+ hash_wia = HashWithIndifferentAccess.new { 1 + 2 }
+ assert_nil hash_wia.default
+ end
+
+ def test_should_use_default_value_for_unknown_key
+ hash_wia = HashWithIndifferentAccess.new(3)
+ assert_equal 3, hash_wia[:new_key]
+ end
+
+ def test_should_use_default_value_if_no_key_is_supplied
+ hash_wia = HashWithIndifferentAccess.new(3)
+ assert_equal 3, hash_wia.default
+ end
+
+ def test_should_nil_if_no_default_value_is_supplied
+ hash_wia = HashWithIndifferentAccess.new
+ assert_nil hash_wia.default
+ end
+
+ def test_should_return_dup_for_with_indifferent_access
+ hash_wia = HashWithIndifferentAccess.new
+ assert_equal hash_wia, hash_wia.with_indifferent_access
+ assert_not_same hash_wia, hash_wia.with_indifferent_access
+ end
+
+ def test_allows_setting_frozen_array_values_with_indifferent_access
+ value = [1, 2, 3].freeze
+ hash = HashWithIndifferentAccess.new
+ hash[:key] = value
+ assert_equal hash[:key], value
+ end
+
+ def test_should_copy_the_default_value_when_converting_to_hash_with_indifferent_access
+ hash = Hash.new(3)
+ hash_wia = hash.with_indifferent_access
+ assert_equal 3, hash_wia.default
+ end
+
+ def test_should_copy_the_default_proc_when_converting_to_hash_with_indifferent_access
+ hash = Hash.new do
+ 2 + 1
+ end
+ assert_equal 3, hash[:foo]
+
+ hash_wia = hash.with_indifferent_access
+ assert_equal 3, hash_wia[:foo]
+ assert_equal 3, hash_wia[:bar]
+ end
+end
diff --git a/activesupport/test/i18n_test.rb b/activesupport/test/i18n_test.rb
index 7d88d2dc6b..8ad9441f9d 100644
--- a/activesupport/test/i18n_test.rb
+++ b/activesupport/test/i18n_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/time"
require "active_support/core_ext/array/conversions"
diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb
index 03d7b3fe94..0e3e576a70 100644
--- a/activesupport/test/inflector_test.rb
+++ b/activesupport/test/inflector_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/inflector"
@@ -119,6 +121,13 @@ class InflectorTest < ActiveSupport::TestCase
end
end
+ MixtureToTitleCaseWithKeepIdSuffix.each_with_index do |(before, titleized), index|
+ define_method "test_titleize_with_keep_id_suffix_mixture_to_title_case_#{index}" do
+ assert_equal(titleized, ActiveSupport::Inflector.titleize(before, keep_id_suffix: true),
+ "mixture to TitleCase with keep_id_suffix failed for #{before}")
+ end
+ end
+
def test_camelize
CamelToUnderscore.each do |camel, underscore|
assert_equal(camel, ActiveSupport::Inflector.camelize(underscore))
@@ -215,6 +224,12 @@ class InflectorTest < ActiveSupport::TestCase
assert_equal("json_html_api", ActiveSupport::Inflector.underscore("JSONHTMLAPI"))
end
+ def test_acronym_regexp_is_deprecated
+ assert_deprecated do
+ ActiveSupport::Inflector.inflections.acronym_regex
+ end
+ end
+
def test_underscore
CamelToUnderscore.each do |camel, underscore|
assert_equal(underscore, ActiveSupport::Inflector.underscore(camel))
@@ -324,6 +339,12 @@ class InflectorTest < ActiveSupport::TestCase
end
end
+ def test_humanize_with_keep_id_suffix
+ UnderscoreToHumanWithKeepIdSuffix.each do |underscore, human|
+ assert_equal(human, ActiveSupport::Inflector.humanize(underscore, keep_id_suffix: true))
+ end
+ end
+
def test_humanize_by_rule
ActiveSupport::Inflector.inflections do |inflect|
inflect.human(/_cnt$/i, '\1_count')
@@ -341,6 +362,19 @@ class InflectorTest < ActiveSupport::TestCase
assert_equal("Col rpted bugs", ActiveSupport::Inflector.humanize("COL_rpted_bugs"))
end
+ def test_humanize_with_acronyms
+ ActiveSupport::Inflector.inflections do |inflect|
+ inflect.acronym "LAX"
+ inflect.acronym "SFO"
+ end
+ assert_equal("LAX roundtrip to SFO", ActiveSupport::Inflector.humanize("LAX ROUNDTRIP TO SFO"))
+ assert_equal("LAX roundtrip to SFO", ActiveSupport::Inflector.humanize("LAX ROUNDTRIP TO SFO", capitalize: false))
+ assert_equal("LAX roundtrip to SFO", ActiveSupport::Inflector.humanize("lax roundtrip to sfo"))
+ assert_equal("LAX roundtrip to SFO", ActiveSupport::Inflector.humanize("lax roundtrip to sfo", capitalize: false))
+ assert_equal("LAX roundtrip to SFO", ActiveSupport::Inflector.humanize("Lax Roundtrip To Sfo"))
+ assert_equal("LAX roundtrip to SFO", ActiveSupport::Inflector.humanize("Lax Roundtrip To Sfo", capitalize: false))
+ end
+
def test_constantize
run_constantize_tests_on do |string|
ActiveSupport::Inflector.constantize(string)
@@ -407,6 +441,8 @@ class InflectorTest < ActiveSupport::TestCase
inflect.singular(/es$/, "")
inflect.irregular("el", "los")
+
+ inflect.uncountable("agua")
end
assert_equal("hijos", "hijo".pluralize(:es))
@@ -419,12 +455,17 @@ class InflectorTest < ActiveSupport::TestCase
assert_equal("los", "el".pluralize(:es))
assert_equal("els", "el".pluralize)
+ assert_equal("agua", "agua".pluralize(:es))
+ assert_equal("aguas", "agua".pluralize)
+
ActiveSupport::Inflector.inflections(:es) { |inflect| inflect.clear }
assert ActiveSupport::Inflector.inflections(:es).plurals.empty?
assert ActiveSupport::Inflector.inflections(:es).singulars.empty?
+ assert ActiveSupport::Inflector.inflections(:es).uncountables.empty?
assert !ActiveSupport::Inflector.inflections.plurals.empty?
assert !ActiveSupport::Inflector.inflections.singulars.empty?
+ assert !ActiveSupport::Inflector.inflections.uncountables.empty?
end
def test_clear_all
diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb
index b660987d92..689370cccf 100644
--- a/activesupport/test/inflector_test_cases.rb
+++ b/activesupport/test/inflector_test_cases.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module InflectorTestCases
SingularToPlural = {
"search" => "searches",
@@ -219,7 +221,7 @@ module InflectorTestCases
"Test with malformed utf8 \251" => "test_with_malformed_utf8"
}
- StringToParameterizePreserceCaseWithUnderscore = {
+ StringToParameterizePreserveCaseWithUnderscore = {
"Donald E. Knuth" => "Donald_E_Knuth",
"Random text with *(bad)* characters" => "Random_text_with_bad_characters",
"With-some-dashes" => "With-some-dashes",
@@ -248,12 +250,27 @@ module InflectorTestCases
"_external_id" => "External"
}
+ UnderscoreToHumanWithKeepIdSuffix = {
+ "this_is_a_string_ending_with_id" => "This is a string ending with id",
+ "employee_id" => "Employee id",
+ "employee_id_something_else" => "Employee id something else",
+ "underground" => "Underground",
+ "_id" => "Id",
+ "_external_id" => "External id"
+ }
+
UnderscoreToHumanWithoutCapitalize = {
"employee_salary" => "employee salary",
"employee_id" => "employee",
"underground" => "underground"
}
+ MixtureToTitleCaseWithKeepIdSuffix = {
+ "this_is_a_string_ending_with_id" => "This Is A String Ending With Id",
+ "EmployeeId" => "Employee Id",
+ "Author Id" => "Author Id"
+ }
+
MixtureToTitleCase = {
"active_record" => "Active Record",
"ActiveRecord" => "Active Record",
@@ -271,6 +288,7 @@ module InflectorTestCases
"¿por qué?" => "¿Por Qué?",
"Fred’s" => "Fred’s",
"Fred`s" => "Fred`s",
+ "this was 'fake news'" => "This Was 'Fake News'",
ActiveSupport::SafeBuffer.new("confirmation num") => "Confirmation Num"
}
diff --git a/activesupport/test/json/decoding_test.rb b/activesupport/test/json/decoding_test.rb
index 6f5051c312..8d9587f248 100644
--- a/activesupport/test/json/decoding_test.rb
+++ b/activesupport/test/json/decoding_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/json"
require "active_support/time"
diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb
index 6d8f7cfbd0..340a2abf75 100644
--- a/activesupport/test/json/encoding_test.rb
+++ b/activesupport/test/json/encoding_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "securerandom"
require "abstract_unit"
require "active_support/core_ext/string/inflections"
@@ -98,11 +100,11 @@ class TestJSONEncoding < ActiveSupport::TestCase
end
def test_hash_should_allow_key_filtering_with_only
- assert_equal %({"a":1}), ActiveSupport::JSON.encode({ "a" => 1, :b => 2, :c => 3 }, only: "a")
+ assert_equal %({"a":1}), ActiveSupport::JSON.encode({ "a" => 1, :b => 2, :c => 3 }, { only: "a" })
end
def test_hash_should_allow_key_filtering_with_except
- assert_equal %({"b":2}), ActiveSupport::JSON.encode({ "foo" => "bar", :b => 2, :c => 3 }, except: ["foo", :c])
+ assert_equal %({"b":2}), ActiveSupport::JSON.encode({ "foo" => "bar", :b => 2, :c => 3 }, { except: ["foo", :c] })
end
def test_time_to_json_includes_local_offset
@@ -185,7 +187,7 @@ class TestJSONEncoding < ActiveSupport::TestCase
def test_array_should_pass_encoding_options_to_children_in_as_json
people = [
{ name: "John", address: { city: "London", country: "UK" } },
- { name: "Jean", address: { city: "Paris" , country: "France" } }
+ { name: "Jean", address: { city: "Paris", country: "France" } }
]
json = people.as_json only: [:address, :city]
expected = [
@@ -199,7 +201,7 @@ class TestJSONEncoding < ActiveSupport::TestCase
def test_array_should_pass_encoding_options_to_children_in_to_json
people = [
{ name: "John", address: { city: "London", country: "UK" } },
- { name: "Jean", address: { city: "Paris" , country: "France" } }
+ { name: "Jean", address: { city: "Paris", country: "France" } }
]
json = people.to_json only: [:address, :city]
@@ -208,10 +210,10 @@ class TestJSONEncoding < ActiveSupport::TestCase
People = Class.new(BasicObject) do
include Enumerable
- def initialize()
+ def initialize
@people = [
{ name: "John", address: { city: "London", country: "UK" } },
- { name: "Jean", address: { city: "Paris" , country: "France" } }
+ { name: "Jean", address: { city: "Paris", country: "France" } }
]
end
def each(*, &blk)
@@ -452,6 +454,10 @@ EXPECTED
assert_equal '{"number":null}', NaNNumber.new.to_json
end
+ def test_to_json_works_on_io_objects
+ assert_equal STDOUT.to_s.to_json, STDOUT.to_json
+ end
+
private
def object_keys(json_object)
diff --git a/activesupport/test/json/encoding_test_cases.rb b/activesupport/test/json/encoding_test_cases.rb
index b2f0cf3048..5a4700459f 100644
--- a/activesupport/test/json/encoding_test_cases.rb
+++ b/activesupport/test/json/encoding_test_cases.rb
@@ -1,4 +1,10 @@
+# frozen_string_literal: true
+
require "bigdecimal"
+require "date"
+require "time"
+require "pathname"
+require "uri"
module JSONTest
class Foo
diff --git a/activesupport/test/key_generator_test.rb b/activesupport/test/key_generator_test.rb
index 89db9563ac..a948cfbd8e 100644
--- a/activesupport/test/key_generator_test.rb
+++ b/activesupport/test/key_generator_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
begin
diff --git a/activesupport/test/lazy_load_hooks_test.rb b/activesupport/test/lazy_load_hooks_test.rb
index 3b1959a1c9..721d44d0c1 100644
--- a/activesupport/test/lazy_load_hooks_test.rb
+++ b/activesupport/test/lazy_load_hooks_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
class LazyLoadHooksTest < ActiveSupport::TestCase
@@ -18,6 +20,23 @@ class LazyLoadHooksTest < ActiveSupport::TestCase
assert_equal 7, i
end
+ def test_basic_hook_with_two_registrations_only_once
+ i = 0
+ block = proc { i += incr }
+ ActiveSupport.on_load(:basic_hook_with_two_once, run_once: true, &block)
+ ActiveSupport.on_load(:basic_hook_with_two_once) do
+ i += incr
+ end
+
+ ActiveSupport.on_load(:different_hook, run_once: true, &block)
+ ActiveSupport.run_load_hooks(:different_hook, FakeContext.new(2))
+ assert_equal 2, i
+ ActiveSupport.run_load_hooks(:basic_hook_with_two_once, FakeContext.new(2))
+ assert_equal 6, i
+ ActiveSupport.run_load_hooks(:basic_hook_with_two_once, FakeContext.new(5))
+ assert_equal 11, i
+ end
+
def test_hook_registered_after_run
i = 0
ActiveSupport.run_load_hooks(:registered_after)
@@ -35,6 +54,15 @@ class LazyLoadHooksTest < ActiveSupport::TestCase
assert_equal 7, i
end
+ def test_hook_registered_after_run_with_two_registrations_only_once
+ i = 0
+ ActiveSupport.run_load_hooks(:registered_after_with_two_once, FakeContext.new(2))
+ ActiveSupport.run_load_hooks(:registered_after_with_two_once, FakeContext.new(5))
+ assert_equal 0, i
+ ActiveSupport.on_load(:registered_after_with_two_once, run_once: true) { i += incr }
+ assert_equal 2, i
+ end
+
def test_hook_registered_interleaved_run_with_two_registrations
i = 0
ActiveSupport.run_load_hooks(:registered_interleaved_with_two, FakeContext.new(2))
@@ -45,6 +73,22 @@ class LazyLoadHooksTest < ActiveSupport::TestCase
assert_equal 7, i
end
+ def test_hook_registered_interleaved_run_with_two_registrations_once
+ i = 0
+ ActiveSupport
+ .run_load_hooks(:registered_interleaved_with_two_once, FakeContext.new(2))
+ assert_equal 0, i
+
+ ActiveSupport.on_load(:registered_interleaved_with_two_once, run_once: true) do
+ i += incr
+ end
+ assert_equal 2, i
+
+ ActiveSupport
+ .run_load_hooks(:registered_interleaved_with_two_once, FakeContext.new(5))
+ assert_equal 2, i
+ end
+
def test_hook_receives_a_context
i = 0
ActiveSupport.on_load(:contextual) { i += incr }
diff --git a/activesupport/test/log_subscriber_test.rb b/activesupport/test/log_subscriber_test.rb
index f6496ef7d6..2af9b1de30 100644
--- a/activesupport/test/log_subscriber_test.rb
+++ b/activesupport/test/log_subscriber_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/log_subscriber/test_helper"
diff --git a/activesupport/test/logger_test.rb b/activesupport/test/logger_test.rb
index 3f04783401..5efbd10a7d 100644
--- a/activesupport/test/logger_test.rb
+++ b/activesupport/test/logger_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "multibyte_test_helpers"
require "stringio"
@@ -37,7 +39,7 @@ class LoggerTest < ActiveSupport::TestCase
logger = Logger.new f
logger.level = Logger::DEBUG
- str = "\x80"
+ str = "\x80".dup
str.force_encoding("ASCII-8BIT")
logger.add Logger::DEBUG, str
@@ -55,7 +57,7 @@ class LoggerTest < ActiveSupport::TestCase
logger = Logger.new f
logger.level = Logger::DEBUG
- str = "\x80"
+ str = "\x80".dup
str.force_encoding("ASCII-8BIT")
logger.add Logger::DEBUG, str
diff --git a/activesupport/test/message_encryptor_test.rb b/activesupport/test/message_encryptor_test.rb
index 56a436f751..9edf07f762 100644
--- a/activesupport/test/message_encryptor_test.rb
+++ b/activesupport/test/message_encryptor_test.rb
@@ -1,7 +1,10 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "openssl"
require "active_support/time"
require "active_support/json"
+require_relative "metadata/shared_metadata_tests"
class MessageEncryptorTest < ActiveSupport::TestCase
class JSONSerializer
@@ -86,19 +89,104 @@ class MessageEncryptorTest < ActiveSupport::TestCase
assert_equal @data, encryptor.decrypt_and_verify(message)
end
+ def test_aead_mode_with_hmac_cbc_cipher_text
+ encryptor = ActiveSupport::MessageEncryptor.new(@secret, cipher: "aes-256-gcm")
+
+ assert_aead_not_decrypted(encryptor, "eHdGeExnZEwvMSt3U3dKaFl1WFo0TjVvYzA0eGpjbm5WSkt5MXlsNzhpZ0ZnbWhBWFlQZTRwaXE1bVJCS2oxMDZhYVp2dVN3V0lNZUlWQ3c2eVhQbnhnVjFmeVVubmhRKzF3WnZyWHVNMDg9LS1HSisyakJVSFlPb05ISzRMaXRzcFdBPT0=--831a1d54a3cda8a0658dc668a03dedcbce13b5ca")
+ end
+
def test_messing_with_aead_values_causes_failures
encryptor = ActiveSupport::MessageEncryptor.new(@secret, cipher: "aes-256-gcm")
text, iv, auth_tag = encryptor.encrypt_and_sign(@data).split("--")
- assert_not_decrypted([iv, text, auth_tag] * "--")
- assert_not_decrypted([munge(text), iv, auth_tag] * "--")
- assert_not_decrypted([text, munge(iv), auth_tag] * "--")
- assert_not_decrypted([text, iv, munge(auth_tag)] * "--")
- assert_not_decrypted([munge(text), munge(iv), munge(auth_tag)] * "--")
- assert_not_decrypted([text, iv] * "--")
- assert_not_decrypted([text, iv, auth_tag[0..-2]] * "--")
+ assert_aead_not_decrypted(encryptor, [iv, text, auth_tag] * "--")
+ assert_aead_not_decrypted(encryptor, [munge(text), iv, auth_tag] * "--")
+ assert_aead_not_decrypted(encryptor, [text, munge(iv), auth_tag] * "--")
+ assert_aead_not_decrypted(encryptor, [text, iv, munge(auth_tag)] * "--")
+ assert_aead_not_decrypted(encryptor, [munge(text), munge(iv), munge(auth_tag)] * "--")
+ assert_aead_not_decrypted(encryptor, [text, iv] * "--")
+ assert_aead_not_decrypted(encryptor, [text, iv, auth_tag[0..-2]] * "--")
+ end
+
+ def test_backwards_compatibility_decrypt_previously_encrypted_messages_without_metadata
+ secret = "\xB7\xF0\xBCW\xB1\x18`\xAB\xF0\x81\x10\xA4$\xF44\xEC\xA1\xDC\xC1\xDDD\xAF\xA9\xB8\x14\xCD\x18\x9A\x99 \x80)"
+ encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: "aes-256-gcm")
+ encrypted_message = "9cVnFs2O3lL9SPvIJuxBOLS51nDiBMw=--YNI5HAfHEmZ7VDpl--ddFJ6tXA0iH+XGcCgMINYQ=="
+
+ assert_equal "Ruby on Rails", encryptor.decrypt_and_verify(encrypted_message)
+ end
+
+ def test_rotating_secret
+ old_message = ActiveSupport::MessageEncryptor.new(secrets[:old], cipher: "aes-256-gcm").encrypt_and_sign("old")
+
+ encryptor = ActiveSupport::MessageEncryptor.new(@secret, cipher: "aes-256-gcm")
+ encryptor.rotate secrets[:old]
+
+ assert_equal "old", encryptor.decrypt_and_verify(old_message)
+ end
+
+ def test_rotating_serializer
+ old_message = ActiveSupport::MessageEncryptor.new(secrets[:old], cipher: "aes-256-gcm", serializer: JSON).
+ encrypt_and_sign(ahoy: :hoy)
+
+ encryptor = ActiveSupport::MessageEncryptor.new(@secret, cipher: "aes-256-gcm", serializer: JSON)
+ encryptor.rotate secrets[:old]
+
+ assert_equal({ "ahoy" => "hoy" }, encryptor.decrypt_and_verify(old_message))
+ end
+
+ def test_rotating_aes_cbc_secrets
+ old_encryptor = ActiveSupport::MessageEncryptor.new(secrets[:old], "old sign", cipher: "aes-256-cbc")
+ old_message = old_encryptor.encrypt_and_sign("old")
+
+ encryptor = ActiveSupport::MessageEncryptor.new(@secret)
+ encryptor.rotate secrets[:old], "old sign", cipher: "aes-256-cbc"
+
+ assert_equal "old", encryptor.decrypt_and_verify(old_message)
+ end
+
+ def test_multiple_rotations
+ older_message = ActiveSupport::MessageEncryptor.new(secrets[:older], "older sign").encrypt_and_sign("older")
+ old_message = ActiveSupport::MessageEncryptor.new(secrets[:old], "old sign").encrypt_and_sign("old")
+
+ encryptor = ActiveSupport::MessageEncryptor.new(@secret)
+ encryptor.rotate secrets[:old], "old sign"
+ encryptor.rotate secrets[:older], "older sign"
+
+ assert_equal "new", encryptor.decrypt_and_verify(encryptor.encrypt_and_sign("new"))
+ assert_equal "old", encryptor.decrypt_and_verify(old_message)
+ assert_equal "older", encryptor.decrypt_and_verify(older_message)
+ end
+
+ def test_on_rotation_is_called_and_returns_modified_messages
+ older_message = ActiveSupport::MessageEncryptor.new(secrets[:older], "older sign").encrypt_and_sign(encoded: "message")
+
+ encryptor = ActiveSupport::MessageEncryptor.new(@secret)
+ encryptor.rotate secrets[:old]
+ encryptor.rotate secrets[:older], "older sign"
+
+ rotated = false
+ message = encryptor.decrypt_and_verify(older_message, on_rotation: proc { rotated = true })
+
+ assert_equal({ encoded: "message" }, message)
+ assert rotated
+ end
+
+ def test_with_rotated_metadata
+ old_message = ActiveSupport::MessageEncryptor.new(secrets[:old], cipher: "aes-256-gcm").
+ encrypt_and_sign("metadata", purpose: :rotation)
+
+ encryptor = ActiveSupport::MessageEncryptor.new(@secret, cipher: "aes-256-gcm")
+ encryptor.rotate secrets[:old]
+
+ assert_equal "metadata", encryptor.decrypt_and_verify(old_message, purpose: :rotation)
end
private
+ def assert_aead_not_decrypted(encryptor, value)
+ assert_raise(ActiveSupport::MessageEncryptor::InvalidMessage) do
+ encryptor.decrypt_and_verify(value)
+ end
+ end
def assert_not_decrypted(value)
assert_raise(ActiveSupport::MessageEncryptor::InvalidMessage) do
@@ -112,9 +200,47 @@ class MessageEncryptorTest < ActiveSupport::TestCase
end
end
+ def secrets
+ @secrets ||= Hash.new { |h, k| h[k] = SecureRandom.random_bytes(32) }
+ end
+
def munge(base64_string)
bits = ::Base64.strict_decode64(base64_string)
bits.reverse!
::Base64.strict_encode64(bits)
end
end
+
+class MessageEncryptorMetadataTest < ActiveSupport::TestCase
+ include SharedMessageMetadataTests
+
+ setup do
+ @secret = SecureRandom.random_bytes(32)
+ @encryptor = ActiveSupport::MessageEncryptor.new(@secret, encryptor_options)
+ end
+
+ private
+ def generate(message, **options)
+ @encryptor.encrypt_and_sign(message, options)
+ end
+
+ def parse(data, **options)
+ @encryptor.decrypt_and_verify(data, options)
+ end
+
+ def encryptor_options; end
+end
+
+class MessageEncryptorMetadataMarshalTest < MessageEncryptorMetadataTest
+ private
+ def encryptor_options
+ { serializer: Marshal }
+ end
+end
+
+class MessageEncryptorMetadataJSONTest < MessageEncryptorMetadataTest
+ private
+ def encryptor_options
+ { serializer: MessageEncryptorTest::JSONSerializer.new }
+ end
+end
diff --git a/activesupport/test/message_verifier_test.rb b/activesupport/test/message_verifier_test.rb
index d6109c761d..05d5c1cbc3 100644
--- a/activesupport/test/message_verifier_test.rb
+++ b/activesupport/test/message_verifier_test.rb
@@ -1,7 +1,10 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "openssl"
require "active_support/time"
require "active_support/json"
+require_relative "metadata/shared_metadata_tests"
class MessageVerifierTest < ActiveSupport::TestCase
class JSONSerializer
@@ -16,7 +19,8 @@ class MessageVerifierTest < ActiveSupport::TestCase
def setup
@verifier = ActiveSupport::MessageVerifier.new("Hey, I'm a secret!")
- @data = { some: "data", now: Time.local(2010) }
+ @data = { some: "data", now: Time.utc(2010) }
+ @secret = SecureRandom.random_bytes(32)
end
def test_valid_message
@@ -82,4 +86,119 @@ class MessageVerifierTest < ActiveSupport::TestCase
end
assert_equal "Secret should not be nil.", exception.message
end
+
+ def test_backward_compatibility_messages_signed_without_metadata
+ signed_message = "BAh7BzoJc29tZUkiCWRhdGEGOgZFVDoIbm93SXU6CVRpbWUNIIAbgAAAAAAHOgtvZmZzZXRpADoJem9uZUkiCFVUQwY7BkY=--d03c52c91dfe4ccc5159417c660461bcce005e96"
+ assert_equal @data, @verifier.verify(signed_message)
+ end
+
+ def test_rotating_secret
+ old_message = ActiveSupport::MessageVerifier.new("old", digest: "SHA1").generate("old")
+
+ verifier = ActiveSupport::MessageVerifier.new(@secret, digest: "SHA1")
+ verifier.rotate "old"
+
+ assert_equal "old", verifier.verified(old_message)
+ end
+
+ def test_multiple_rotations
+ old_message = ActiveSupport::MessageVerifier.new("old", digest: "SHA256").generate("old")
+ older_message = ActiveSupport::MessageVerifier.new("older", digest: "SHA1").generate("older")
+
+ verifier = ActiveSupport::MessageVerifier.new(@secret, digest: "SHA512")
+ verifier.rotate "old", digest: "SHA256"
+ verifier.rotate "older", digest: "SHA1"
+
+ assert_equal "new", verifier.verified(verifier.generate("new"))
+ assert_equal "old", verifier.verified(old_message)
+ assert_equal "older", verifier.verified(older_message)
+ end
+
+ def test_on_rotation_is_called_and_verified_returns_message
+ older_message = ActiveSupport::MessageVerifier.new("older", digest: "SHA1").generate(encoded: "message")
+
+ verifier = ActiveSupport::MessageVerifier.new(@secret, digest: "SHA512")
+ verifier.rotate "old", digest: "SHA256"
+ verifier.rotate "older", digest: "SHA1"
+
+ rotated = false
+ message = verifier.verified(older_message, on_rotation: proc { rotated = true })
+
+ assert_equal({ encoded: "message" }, message)
+ assert rotated
+ end
+
+ def test_rotations_with_metadata
+ old_message = ActiveSupport::MessageVerifier.new("old").generate("old", purpose: :rotation)
+
+ verifier = ActiveSupport::MessageVerifier.new(@secret)
+ verifier.rotate "old"
+
+ assert_equal "old", verifier.verified(old_message, purpose: :rotation)
+ end
+end
+
+class MessageVerifierMetadataTest < ActiveSupport::TestCase
+ include SharedMessageMetadataTests
+
+ setup do
+ @verifier = ActiveSupport::MessageVerifier.new("Hey, I'm a secret!", verifier_options)
+ end
+
+ def test_verify_raises_when_purpose_differs
+ assert_raise(ActiveSupport::MessageVerifier::InvalidSignature) do
+ @verifier.verify(generate(data, purpose: "payment"), purpose: "shipping")
+ end
+ end
+
+ def test_verify_raises_when_expired
+ signed_message = generate(data, expires_in: 1.month)
+
+ travel 2.months
+ assert_raise(ActiveSupport::MessageVerifier::InvalidSignature) do
+ @verifier.verify(signed_message)
+ end
+ end
+
+ private
+ def generate(message, **options)
+ @verifier.generate(message, options)
+ end
+
+ def parse(message, **options)
+ @verifier.verified(message, options)
+ end
+
+ def verifier_options
+ Hash.new
+ end
+end
+
+class MessageVerifierMetadataMarshalTest < MessageVerifierMetadataTest
+ private
+ def verifier_options
+ { serializer: Marshal }
+ end
+end
+
+class MessageVerifierMetadataJSONTest < MessageVerifierMetadataTest
+ private
+ def verifier_options
+ { serializer: MessageVerifierTest::JSONSerializer.new }
+ end
+end
+
+class MessageEncryptorMetadataNullSerializerTest < MessageVerifierMetadataTest
+ private
+ def data
+ "string message"
+ end
+
+ def null_serializing?
+ true
+ end
+
+ def verifier_options
+ { serializer: ActiveSupport::MessageEncryptor::NullSerializer }
+ end
end
diff --git a/activesupport/test/messages/rotation_configuration_test.rb b/activesupport/test/messages/rotation_configuration_test.rb
new file mode 100644
index 0000000000..2f6824ed21
--- /dev/null
+++ b/activesupport/test/messages/rotation_configuration_test.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+require "abstract_unit"
+require "active_support/messages/rotation_configuration"
+
+class MessagesRotationConfiguration < ActiveSupport::TestCase
+ def setup
+ @config = ActiveSupport::Messages::RotationConfiguration.new
+ end
+
+ def test_signed_configurations
+ @config.rotate :signed, "older secret", salt: "salt", digest: "SHA1"
+ @config.rotate :signed, "old secret", salt: "salt", digest: "SHA256"
+
+ assert_equal [
+ [ "older secret", salt: "salt", digest: "SHA1" ],
+ [ "old secret", salt: "salt", digest: "SHA256" ] ], @config.signed
+ end
+
+ def test_encrypted_configurations
+ @config.rotate :encrypted, "old raw key", cipher: "aes-256-gcm"
+
+ assert_equal [ [ "old raw key", cipher: "aes-256-gcm" ] ], @config.encrypted
+ end
+end
diff --git a/activesupport/test/metadata/shared_metadata_tests.rb b/activesupport/test/metadata/shared_metadata_tests.rb
new file mode 100644
index 0000000000..08bb0c648e
--- /dev/null
+++ b/activesupport/test/metadata/shared_metadata_tests.rb
@@ -0,0 +1,93 @@
+# frozen_string_literal: true
+
+module SharedMessageMetadataTests
+ def teardown
+ travel_back
+ super
+ end
+
+ def null_serializing?
+ false
+ end
+
+ def test_encryption_and_decryption_with_same_purpose
+ assert_equal data, parse(generate(data, purpose: "checkout"), purpose: "checkout")
+ assert_equal data, parse(generate(data))
+
+ string_message = "address: #23, main street"
+ assert_equal string_message, parse(generate(string_message, purpose: "shipping"), purpose: "shipping")
+ end
+
+ def test_verifies_array_when_purpose_matches
+ unless null_serializing?
+ data = [ "credit_card_no: 5012-6748-9087-5678", { "card_holder" => "Donald", "issued_on" => Time.local(2017) }, 12345 ]
+ assert_equal data, parse(generate(data, purpose: :registration), purpose: :registration)
+ end
+ end
+
+ def test_encryption_and_decryption_with_different_purposes_returns_nil
+ assert_nil parse(generate(data, purpose: "payment"), purpose: "sign up")
+ assert_nil parse(generate(data, purpose: "payment"))
+ assert_nil parse(generate(data), purpose: "sign up")
+ end
+
+ def test_purpose_using_symbols
+ assert_equal data, parse(generate(data, purpose: :checkout), purpose: :checkout)
+ assert_equal data, parse(generate(data, purpose: :checkout), purpose: "checkout")
+ assert_equal data, parse(generate(data, purpose: "checkout"), purpose: :checkout)
+ end
+
+ def test_passing_expires_at_sets_expiration_date
+ encrypted_message = generate(data, expires_at: 1.hour.from_now)
+
+ travel 59.minutes
+ assert_equal data, parse(encrypted_message)
+
+ travel 2.minutes
+ assert_nil parse(encrypted_message)
+ end
+
+ def test_set_relative_expiration_date_by_passing_expires_in
+ encrypted_message = generate(data, expires_in: 2.hours)
+
+ travel 1.hour
+ assert_equal data, parse(encrypted_message)
+
+ travel 1.hour + 1.second
+ assert_nil parse(encrypted_message)
+ end
+
+ def test_passing_expires_in_less_than_a_second_is_not_expired
+ freeze_time do
+ encrypted_message = generate(data, expires_in: 1.second)
+
+ travel 0.5.seconds
+ assert_equal data, parse(encrypted_message)
+
+ travel 1.second
+ assert_nil parse(encrypted_message)
+ end
+ end
+
+ def test_favor_expires_at_over_expires_in
+ payment_related_message = generate(data, purpose: "payment", expires_at: 2.year.from_now, expires_in: 1.second)
+
+ travel 1.year
+ assert_equal data, parse(payment_related_message, purpose: :payment)
+
+ travel 1.year + 1.day
+ assert_nil parse(payment_related_message, purpose: "payment")
+ end
+
+ def test_skip_expires_at_and_expires_in_to_disable_expiration_check
+ payment_related_message = generate(data, purpose: "payment")
+
+ travel 100.years
+ assert_equal data, parse(payment_related_message, purpose: "payment")
+ end
+
+ private
+ def data
+ { "credit_card_no" => "5012-6784-9087-5678", "card_holder" => { "name" => "Donald" } }
+ end
+end
diff --git a/activesupport/test/multibyte_chars_test.rb b/activesupport/test/multibyte_chars_test.rb
index 3c8494fe52..f51fbe2671 100644
--- a/activesupport/test/multibyte_chars_test.rb
+++ b/activesupport/test/multibyte_chars_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "multibyte_test_helpers"
require "active_support/core_ext/string/multibyte"
@@ -51,7 +53,7 @@ class MultibyteCharsTest < ActiveSupport::TestCase
end
def test_forwarded_method_with_non_string_result_should_be_returned_verbatim
- str = ""
+ str = "".dup
str.singleton_class.class_eval { def __method_for_multibyte_testing_with_integer_result; 1; end }
@chars.wrapped_string.singleton_class.class_eval { def __method_for_multibyte_testing_with_integer_result; 1; end }
@@ -59,14 +61,14 @@ class MultibyteCharsTest < ActiveSupport::TestCase
end
def test_should_concatenate
- mb_a = "a".mb_chars
- mb_b = "b".mb_chars
+ mb_a = "a".dup.mb_chars
+ mb_b = "b".dup.mb_chars
assert_equal "ab", mb_a + "b"
assert_equal "ab", "a" + mb_b
assert_equal "ab", mb_a + mb_b
assert_equal "ab", mb_a << "b"
- assert_equal "ab", "a" << mb_b
+ assert_equal "ab", "a".dup << mb_b
assert_equal "abb", mb_a << mb_b
end
@@ -78,7 +80,7 @@ class MultibyteCharsTest < ActiveSupport::TestCase
def test_concatenation_should_return_a_proxy_class_instance
assert_equal ActiveSupport::Multibyte.proxy_class, ("a".mb_chars + "b").class
- assert_equal ActiveSupport::Multibyte.proxy_class, ("a".mb_chars << "b").class
+ assert_equal ActiveSupport::Multibyte.proxy_class, ("a".dup.mb_chars << "b").class
end
def test_ascii_strings_are_treated_at_utf8_strings
@@ -88,8 +90,8 @@ class MultibyteCharsTest < ActiveSupport::TestCase
def test_concatenate_should_return_proxy_instance
assert(("a".mb_chars + "b").kind_of?(@proxy_class))
assert(("a".mb_chars + "b".mb_chars).kind_of?(@proxy_class))
- assert(("a".mb_chars << "b").kind_of?(@proxy_class))
- assert(("a".mb_chars << "b".mb_chars).kind_of?(@proxy_class))
+ assert(("a".dup.mb_chars << "b").kind_of?(@proxy_class))
+ assert(("a".dup.mb_chars << "b".mb_chars).kind_of?(@proxy_class))
end
def test_should_return_string_as_json
@@ -115,12 +117,12 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase
%w{capitalize downcase lstrip reverse rstrip swapcase upcase}.each do |method|
class_eval(<<-EOTESTS, __FILE__, __LINE__ + 1)
def test_#{method}_bang_should_return_self_when_modifying_wrapped_string
- chars = ' él piDió Un bUen café '
+ chars = ' él piDió Un bUen café '.dup
assert_equal chars.object_id, chars.send("#{method}!").object_id
end
def test_#{method}_bang_should_change_wrapped_string
- original = ' él piDió Un bUen café '
+ original = ' él piDió Un bUen café '.dup
proxy = chars(original.dup)
proxy.send("#{method}!")
assert_not_equal original, proxy.to_s
@@ -133,7 +135,7 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase
end
def test_tidy_bytes_bang_should_change_wrapped_string
- original = " Un bUen café \x92"
+ original = " Un bUen café \x92".dup
proxy = chars(original.dup)
proxy.tidy_bytes!
assert_not_equal original, proxy.to_s
@@ -150,7 +152,7 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase
end
def test_string_methods_are_chainable
- assert chars("").insert(0, "").kind_of?(ActiveSupport::Multibyte.proxy_class)
+ assert chars("".dup).insert(0, "").kind_of?(ActiveSupport::Multibyte.proxy_class)
assert chars("").rjust(1).kind_of?(ActiveSupport::Multibyte.proxy_class)
assert chars("").ljust(1).kind_of?(ActiveSupport::Multibyte.proxy_class)
assert chars("").center(1).kind_of?(ActiveSupport::Multibyte.proxy_class)
@@ -195,7 +197,7 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase
end
def test_should_use_character_offsets_for_insert_offsets
- assert_equal "", "".mb_chars.insert(0, "")
+ assert_equal "", "".dup.mb_chars.insert(0, "")
assert_equal "こわにちわ", @chars.insert(1, "わ")
assert_equal "こわわわにちわ", @chars.insert(2, "わわ")
assert_equal "わこわわわにちわ", @chars.insert(0, "わ")
@@ -418,13 +420,13 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase
end
def test_slice_bang_removes_the_slice_from_the_receiver
- chars = "úüù".mb_chars
+ chars = "úüù".dup.mb_chars
chars.slice!(0, 2)
assert_equal "ù", chars
end
def test_slice_bang_returns_nil_and_does_not_modify_receiver_if_out_of_bounds
- string = "úüù"
+ string = "úüù".dup
chars = string.mb_chars
assert_nil chars.slice!(4, 5)
assert_equal "úüù", chars
@@ -664,7 +666,6 @@ class MultibyteCharsExtrasTest < ActiveSupport::TestCase
end
def test_tidy_bytes_should_tidy_bytes
-
single_byte_cases = {
"\x21" => "!", # Valid ASCII byte, low
"\x41" => "A", # Valid ASCII byte, mid
diff --git a/activesupport/test/multibyte_conformance_test.rb b/activesupport/test/multibyte_conformance_test.rb
index ef1a26135f..748e8d16e1 100644
--- a/activesupport/test/multibyte_conformance_test.rb
+++ b/activesupport/test/multibyte_conformance_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "multibyte_test_helpers"
diff --git a/activesupport/test/multibyte_grapheme_break_conformance_test.rb b/activesupport/test/multibyte_grapheme_break_conformance_test.rb
index b3328987ae..fac74cd80f 100644
--- a/activesupport/test/multibyte_grapheme_break_conformance_test.rb
+++ b/activesupport/test/multibyte_grapheme_break_conformance_test.rb
@@ -1,4 +1,4 @@
-# encoding: utf-8
+# frozen_string_literal: true
require "abstract_unit"
require "multibyte_test_helpers"
diff --git a/activesupport/test/multibyte_normalization_conformance_test.rb b/activesupport/test/multibyte_normalization_conformance_test.rb
index ebc9f92d23..1173a94e81 100644
--- a/activesupport/test/multibyte_normalization_conformance_test.rb
+++ b/activesupport/test/multibyte_normalization_conformance_test.rb
@@ -1,4 +1,4 @@
-# encoding: utf-8
+# frozen_string_literal: true
require "abstract_unit"
require "multibyte_test_helpers"
diff --git a/activesupport/test/multibyte_proxy_test.rb b/activesupport/test/multibyte_proxy_test.rb
index c303097f80..ecedab2569 100644
--- a/activesupport/test/multibyte_proxy_test.rb
+++ b/activesupport/test/multibyte_proxy_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
class MultibyteProxyText < ActiveSupport::TestCase
diff --git a/activesupport/test/multibyte_test_helpers.rb b/activesupport/test/multibyte_test_helpers.rb
index a70516bb08..f7cf993100 100644
--- a/activesupport/test/multibyte_test_helpers.rb
+++ b/activesupport/test/multibyte_test_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module MultibyteTestHelpers
class Downloader
def self.download(from, to)
@@ -23,7 +25,7 @@ module MultibyteTestHelpers
UNICODE_STRING = "こにちわ".freeze
ASCII_STRING = "ohayo".freeze
- BYTE_STRING = "\270\236\010\210\245".force_encoding("ASCII-8BIT").freeze
+ BYTE_STRING = "\270\236\010\210\245".dup.force_encoding("ASCII-8BIT").freeze
def chars(str)
ActiveSupport::Multibyte::Chars.new(str)
diff --git a/activesupport/test/multibyte_unicode_database_test.rb b/activesupport/test/multibyte_unicode_database_test.rb
index 3724782930..540a34493d 100644
--- a/activesupport/test/multibyte_unicode_database_test.rb
+++ b/activesupport/test/multibyte_unicode_database_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
class MultibyteUnicodeDatabaseTest < ActiveSupport::TestCase
diff --git a/activesupport/test/notifications/evented_notification_test.rb b/activesupport/test/notifications/evented_notification_test.rb
index 24c5befec3..4beb8194b9 100644
--- a/activesupport/test/notifications/evented_notification_test.rb
+++ b/activesupport/test/notifications/evented_notification_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
module ActiveSupport
diff --git a/activesupport/test/notifications/instrumenter_test.rb b/activesupport/test/notifications/instrumenter_test.rb
index 7eacc5cbe7..1d76c91d30 100644
--- a/activesupport/test/notifications/instrumenter_test.rb
+++ b/activesupport/test/notifications/instrumenter_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/notifications/instrumenter"
diff --git a/activesupport/test/notifications_test.rb b/activesupport/test/notifications_test.rb
index 11f743519f..5cfbd60131 100644
--- a/activesupport/test/notifications_test.rb
+++ b/activesupport/test/notifications_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/core_ext/module/delegation"
diff --git a/activesupport/test/number_helper_i18n_test.rb b/activesupport/test/number_helper_i18n_test.rb
index a1d1c41dc2..365fa96f4d 100644
--- a/activesupport/test/number_helper_i18n_test.rb
+++ b/activesupport/test/number_helper_i18n_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/number_helper"
require "active_support/core_ext/hash/keys"
@@ -33,7 +35,7 @@ module ActiveSupport
thousand: "t",
million: "m",
billion: "b",
- trillion: "t" ,
+ trillion: "t",
quadrillion: "q"
}
}
@@ -75,10 +77,10 @@ module ActiveSupport
end
def test_number_with_i18n_precision
- #Delimiter was set to ""
+ # Delimiter was set to ""
assert_equal("10000", number_to_rounded(10000, locale: "ts"))
- #Precision inherited and significant was set
+ # Precision inherited and significant was set
assert_equal("1.00", number_to_rounded(1.0, locale: "ts"))
end
@@ -88,7 +90,7 @@ module ActiveSupport
end
def test_number_with_i18n_delimiter
- #Delimiter "," and separator "."
+ # Delimiter "," and separator "."
assert_equal("1,000,000.234", number_to_delimited(1000000.234, locale: "ts"))
end
@@ -112,7 +114,7 @@ module ActiveSupport
end
def test_number_to_i18n_human_size
- #b for bytes and k for kbytes
+ # b for bytes and k for kbytes
assert_equal("2 k", number_to_human_size(2048, locale: "ts"))
assert_equal("42 b", number_to_human_size(42, locale: "ts"))
end
@@ -123,11 +125,11 @@ module ActiveSupport
end
def test_number_to_human_with_default_translation_scope
- #Using t for thousand
+ # Using t for thousand
assert_equal "2 t", number_to_human(2000, locale: "ts")
- #Significant was set to true with precision 2, using b for billion
+ # Significant was set to true with precision 2, using b for billion
assert_equal "1.2 b", number_to_human(1234567890, locale: "ts")
- #Using pluralization (Ten/Tens and Tenth/Tenths)
+ # Using pluralization (Ten/Tens and Tenth/Tenths)
assert_equal "1 Tenth", number_to_human(0.1, locale: "ts")
assert_equal "1.3 Tenth", number_to_human(0.134, locale: "ts")
assert_equal "2 Tenths", number_to_human(0.2, locale: "ts")
@@ -142,7 +144,7 @@ module ActiveSupport
end
def test_number_to_human_with_custom_translation_scope
- #Significant was set to true with precision 2, with custom translated units
+ # Significant was set to true with precision 2, with custom translated units
assert_equal "4.3 cm", number_to_human(0.0432, locale: "ts", units: :custom_units_for_number_to_human)
end
end
diff --git a/activesupport/test/number_helper_test.rb b/activesupport/test/number_helper_test.rb
index dc0c34d4e2..16ccc5572c 100644
--- a/activesupport/test/number_helper_test.rb
+++ b/activesupport/test/number_helper_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/number_helper"
require "active_support/core_ext/string/output_safety"
@@ -258,7 +260,7 @@ module ActiveSupport
assert_equal "40 KB", number_helper.number_to_human_size(41100, precision: 2)
assert_equal "1.0 KB", number_helper.number_to_human_size(kilobytes(1.0123), precision: 2, strip_insignificant_zeros: false)
assert_equal "1.012 KB", number_helper.number_to_human_size(kilobytes(1.0123), precision: 3, significant: false)
- assert_equal "1 KB", number_helper.number_to_human_size(kilobytes(1.0123), precision: 0, significant: true) #ignores significant it precision is 0
+ assert_equal "1 KB", number_helper.number_to_human_size(kilobytes(1.0123), precision: 0, significant: true) # ignores significant it precision is 0
end
end
@@ -290,7 +292,7 @@ module ActiveSupport
assert_equal "489.0 Thousand", number_helper.number_to_human(489000, precision: 4, strip_insignificant_zeros: false)
assert_equal "1.2346 Million", number_helper.number_to_human(1234567, precision: 4, significant: false)
assert_equal "1,2 Million", number_helper.number_to_human(1234567, precision: 1, significant: false, separator: ",")
- assert_equal "1 Million", number_helper.number_to_human(1234567, precision: 0, significant: true, separator: ",") #significant forced to false
+ assert_equal "1 Million", number_helper.number_to_human(1234567, precision: 0, significant: true, separator: ",") # significant forced to false
assert_equal "1 Million", number_helper.number_to_human(999999)
assert_equal "1 Billion", number_helper.number_to_human(999999999)
end
@@ -298,13 +300,13 @@ module ActiveSupport
def test_number_to_human_with_custom_units
[@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
- #Only integers
+ # Only integers
volume = { unit: "ml", thousand: "lt", million: "m3" }
assert_equal "123 lt", number_helper.number_to_human(123456, units: volume)
assert_equal "12 ml", number_helper.number_to_human(12, units: volume)
assert_equal "1.23 m3", number_helper.number_to_human(1234567, units: volume)
- #Including fractionals
+ # Including fractionals
distance = { mili: "mm", centi: "cm", deci: "dm", unit: "m", ten: "dam", hundred: "hm", thousand: "km" }
assert_equal "1.23 mm", number_helper.number_to_human(0.00123, units: distance)
assert_equal "1.23 cm", number_helper.number_to_human(0.0123, units: distance)
@@ -317,16 +319,22 @@ module ActiveSupport
assert_equal "1.23 km", number_helper.number_to_human(1230, units: distance)
assert_equal "12.3 km", number_helper.number_to_human(12300, units: distance)
- #The quantifiers don't need to be a continuous sequence
+ # The quantifiers don't need to be a continuous sequence
gangster = { hundred: "hundred bucks", million: "thousand quids" }
assert_equal "1 hundred bucks", number_helper.number_to_human(100, units: gangster)
assert_equal "25 hundred bucks", number_helper.number_to_human(2500, units: gangster)
+ assert_equal "1000 hundred bucks", number_helper.number_to_human(100_000, units: gangster)
+ assert_equal "1 thousand quids", number_helper.number_to_human(999_999, units: gangster)
+ assert_equal "1 thousand quids", number_helper.number_to_human(1_000_000, units: gangster)
assert_equal "25 thousand quids", number_helper.number_to_human(25000000, units: gangster)
assert_equal "12300 thousand quids", number_helper.number_to_human(12345000000, units: gangster)
- #Spaces are stripped from the resulting string
+ # Spaces are stripped from the resulting string
assert_equal "4", number_helper.number_to_human(4, units: { unit: "", ten: "tens " })
assert_equal "4.5 tens", number_helper.number_to_human(45, units: { unit: "", ten: " tens " })
+
+ # Uses only the provided units and does not try to use larger ones
+ assert_equal "1000 kilometers", number_helper.number_to_human(1_000_000, units: { unit: "meter", thousand: "kilometers" })
end
end
diff --git a/activesupport/test/option_merger_test.rb b/activesupport/test/option_merger_test.rb
index c5a6d304ee..935e2aee63 100644
--- a/activesupport/test/option_merger_test.rb
+++ b/activesupport/test/option_merger_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/core_ext/object/with_options"
diff --git a/activesupport/test/ordered_hash_test.rb b/activesupport/test/ordered_hash_test.rb
index 2cefab3832..c70d3b4c37 100644
--- a/activesupport/test/ordered_hash_test.rb
+++ b/activesupport/test/ordered_hash_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/json"
require "active_support/core_ext/object/json"
diff --git a/activesupport/test/ordered_options_test.rb b/activesupport/test/ordered_options_test.rb
index 0417911289..2c67bb02ac 100644
--- a/activesupport/test/ordered_options_test.rb
+++ b/activesupport/test/ordered_options_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/ordered_options"
@@ -100,4 +102,17 @@ class OrderedOptionsTest < ActiveSupport::TestCase
end
assert_raises(KeyError) { a.non_existing_key! }
end
+
+ def test_inheritable_options_with_bang
+ a = ActiveSupport::InheritableOptions.new(foo: :bar)
+
+ assert_nothing_raised { a.foo! }
+ assert_equal a.foo, a.foo!
+
+ assert_raises(KeyError) do
+ a.foo = nil
+ a.foo!
+ end
+ assert_raises(KeyError) { a.non_existing_key! }
+ end
end
diff --git a/activesupport/test/reloader_test.rb b/activesupport/test/reloader_test.rb
index bdd80307c7..3e4229eaf7 100644
--- a/activesupport/test/reloader_test.rb
+++ b/activesupport/test/reloader_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
class ReloaderTest < ActiveSupport::TestCase
diff --git a/activesupport/test/rescuable_test.rb b/activesupport/test/rescuable_test.rb
index f7eb047d44..b1b8a25c5b 100644
--- a/activesupport/test/rescuable_test.rb
+++ b/activesupport/test/rescuable_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
class WraithAttack < StandardError
@@ -43,7 +45,9 @@ class Stargate
def dispatch(method)
send(method)
rescue Exception => e
- rescue_with_handler(e)
+ unless rescue_with_handler(e)
+ @result = "unhandled"
+ end
end
def attack
@@ -58,6 +62,26 @@ class Stargate
raise MadRonon.new("dex")
end
+ def crash
+ raise "unhandled RuntimeError"
+ end
+
+ def looped_crash
+ ex1 = StandardError.new("error 1")
+ ex2 = StandardError.new("error 2")
+ begin
+ begin
+ raise ex1
+ rescue
+ # sets the cause on ex2 to be ex1
+ raise ex2
+ end
+ rescue
+ # sets the cause on ex1 to be ex2
+ raise ex1
+ end
+ end
+
def fall_back_to_cause
# This exception is the cause and has a handler.
ronanize
@@ -139,4 +163,14 @@ class RescuableTest < ActiveSupport::TestCase
@stargate.dispatch :fall_back_to_cause
assert_equal "dex", @stargate.result
end
+
+ def test_unhandled_exceptions
+ @stargate.dispatch(:crash)
+ assert_equal "unhandled", @stargate.result
+ end
+
+ def test_rescue_handles_loops_in_exception_cause_chain
+ @stargate.dispatch :looped_crash
+ assert_equal "unhandled", @stargate.result
+ end
end
diff --git a/activesupport/test/safe_buffer_test.rb b/activesupport/test/safe_buffer_test.rb
index 36c068b91f..05c2fb59be 100644
--- a/activesupport/test/safe_buffer_test.rb
+++ b/activesupport/test/safe_buffer_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/core_ext/string/inflections"
require "yaml"
diff --git a/activesupport/test/security_utils_test.rb b/activesupport/test/security_utils_test.rb
index e8f762da22..0a607594a2 100644
--- a/activesupport/test/security_utils_test.rb
+++ b/activesupport/test/security_utils_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/security_utils"
@@ -7,8 +9,14 @@ class SecurityUtilsTest < ActiveSupport::TestCase
assert_not ActiveSupport::SecurityUtils.secure_compare("a", "b")
end
- def test_variable_size_secure_compare_should_perform_string_comparison
- assert ActiveSupport::SecurityUtils.variable_size_secure_compare("a", "a")
- assert_not ActiveSupport::SecurityUtils.variable_size_secure_compare("a", "b")
+ def test_fixed_length_secure_compare_should_perform_string_comparison
+ assert ActiveSupport::SecurityUtils.fixed_length_secure_compare("a", "a")
+ assert !ActiveSupport::SecurityUtils.fixed_length_secure_compare("a", "b")
+ end
+
+ def test_fixed_length_secure_compare_raise_on_length_mismatch
+ assert_raises(ArgumentError, "string length mismatch.") do
+ ActiveSupport::SecurityUtils.fixed_length_secure_compare("a", "ab")
+ end
end
end
diff --git a/activesupport/test/share_lock_test.rb b/activesupport/test/share_lock_test.rb
index a5970591fa..42fd5eefc1 100644
--- a/activesupport/test/share_lock_test.rb
+++ b/activesupport/test/share_lock_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "concurrent/atomic/count_down_latch"
require "active_support/concurrency/share_lock"
diff --git a/activesupport/test/string_inquirer_test.rb b/activesupport/test/string_inquirer_test.rb
index 79a715349c..bf8f878a32 100644
--- a/activesupport/test/string_inquirer_test.rb
+++ b/activesupport/test/string_inquirer_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
class StringInquirerTest < ActiveSupport::TestCase
diff --git a/activesupport/test/subscriber_test.rb b/activesupport/test/subscriber_test.rb
index 9127da35d4..6b012e43af 100644
--- a/activesupport/test/subscriber_test.rb
+++ b/activesupport/test/subscriber_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/subscriber"
diff --git a/activesupport/test/tagged_logging_test.rb b/activesupport/test/tagged_logging_test.rb
index 2469e827d4..5fd6a6b316 100644
--- a/activesupport/test/tagged_logging_test.rb
+++ b/activesupport/test/tagged_logging_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/logger"
require "active_support/tagged_logging"
diff --git a/activesupport/test/test_case_test.rb b/activesupport/test/test_case_test.rb
index af7fc44d66..84e4953fe2 100644
--- a/activesupport/test/test_case_test.rb
+++ b/activesupport/test/test_case_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
class AssertDifferenceTest < ActiveSupport::TestCase
@@ -85,7 +87,8 @@ class AssertDifferenceTest < ActiveSupport::TestCase
def test_expression_is_evaluated_in_the_appropriate_scope
silence_warnings do
- local_scope = local_scope = "foo"
+ local_scope = "foo"
+ local_scope = local_scope # to suppress unused variable warning
assert_difference("local_scope; @object.num") { @object.increment }
end
end
@@ -176,6 +179,7 @@ class AssertDifferenceTest < ActiveSupport::TestCase
end
def test_assert_changes_works_with_any_object
+ # Silences: instance variable @new_object not initialized.
retval = silence_warnings do
assert_changes :@new_object, from: nil, to: 42 do
@new_object = 42
@@ -198,7 +202,7 @@ class AssertDifferenceTest < ActiveSupport::TestCase
def test_assert_changes_with_to_and_case_operator
token = nil
- assert_changes "token", to: /\w{32}/ do
+ assert_changes -> { token }, to: /\w{32}/ do
token = SecureRandom.hex
end
end
@@ -206,7 +210,7 @@ class AssertDifferenceTest < ActiveSupport::TestCase
def test_assert_changes_with_to_and_from_and_case_operator
token = SecureRandom.hex
- assert_changes "token", from: /\w{32}/, to: /\w{32}/ do
+ assert_changes -> { token }, from: /\w{32}/, to: /\w{32}/ do
token = SecureRandom.hex
end
end
@@ -233,13 +237,10 @@ class AssertDifferenceTest < ActiveSupport::TestCase
end
end
- assert_equal "@object.num should not change.\n\"@object.num\" did change to 1.\nExpected: 0\n Actual: 1", error.message
+ assert_equal "@object.num should not change.\n\"@object.num\" did change to 1", error.message
end
end
-class AlsoDoingNothingTest < ActiveSupport::TestCase
-end
-
# Setup and teardown callbacks.
class SetupAndTeardownTest < ActiveSupport::TestCase
setup :reset_callback_record, :foo
diff --git a/activesupport/test/testing/constant_lookup_test.rb b/activesupport/test/testing/constant_lookup_test.rb
index 00e69fcdb5..37b7822950 100644
--- a/activesupport/test/testing/constant_lookup_test.rb
+++ b/activesupport/test/testing/constant_lookup_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "dependencies_test_helpers"
diff --git a/activesupport/test/testing/file_fixtures_test.rb b/activesupport/test/testing/file_fixtures_test.rb
index e44fe58ce9..35be8f5206 100644
--- a/activesupport/test/testing/file_fixtures_test.rb
+++ b/activesupport/test/testing/file_fixtures_test.rb
@@ -1,14 +1,16 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "pathname"
class FileFixturesTest < ActiveSupport::TestCase
- self.file_fixture_path = File.expand_path("../../file_fixtures", __FILE__)
+ self.file_fixture_path = File.expand_path("../file_fixtures", __dir__)
test "#file_fixture returns Pathname to file fixture" do
path = file_fixture("sample.txt")
assert_kind_of Pathname, path
- assert_match %r{.*/test/file_fixtures/sample.txt$}, path.to_s
+ assert_match %r{.*/test/file_fixtures/sample\.txt$}, path.to_s
end
test "raises an exception when the fixture file does not exist" do
@@ -20,11 +22,11 @@ class FileFixturesTest < ActiveSupport::TestCase
end
class FileFixturesPathnameDirectoryTest < ActiveSupport::TestCase
- self.file_fixture_path = Pathname.new(File.expand_path("../../file_fixtures", __FILE__))
+ self.file_fixture_path = Pathname.new(File.expand_path("../file_fixtures", __dir__))
test "#file_fixture_path returns Pathname to file fixture" do
path = file_fixture("sample.txt")
assert_kind_of Pathname, path
- assert_match %r{.*/test/file_fixtures/sample.txt$}, path.to_s
+ assert_match %r{.*/test/file_fixtures/sample\.txt$}, path.to_s
end
end
diff --git a/activesupport/test/testing/method_call_assertions_test.rb b/activesupport/test/testing/method_call_assertions_test.rb
index 7887933b15..4af000bb7e 100644
--- a/activesupport/test/testing/method_call_assertions_test.rb
+++ b/activesupport/test/testing/method_call_assertions_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/testing/method_call_assertions"
diff --git a/activesupport/test/time_travel_test.rb b/activesupport/test/time_travel_test.rb
index e0d3fb0cf5..9c2c635f43 100644
--- a/activesupport/test/time_travel_test.rb
+++ b/activesupport/test/time_travel_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/core_ext/date_time"
require "active_support/core_ext/numeric/time"
@@ -96,10 +98,10 @@ class TimeTravelTest < ActiveSupport::TestCase
travel_to outer_expected_time do
e = assert_raises(RuntimeError) do
travel_to(inner_expected_time) do
- #noop
+ # noop
end
end
- assert_match(/Calling `travel_to` with a block, when we have previously already made a call to `travel_to`, can lead to confusing time stubbing./, e.message)
+ assert_match(/Calling `travel_to` with a block, when we have previously already made a call to `travel_to`, can lead to confusing time stubbing\./, e.message)
end
end
end
@@ -162,4 +164,26 @@ class TimeTravelTest < ActiveSupport::TestCase
assert_equal DateTime.now.to_s, DateTimeSubclass.now.to_s
end
end
+
+ def test_time_helper_freeze_time
+ expected_time = Time.now
+ freeze_time
+ sleep(1)
+
+ assert_equal expected_time.to_s(:db), Time.now.to_s(:db)
+ ensure
+ travel_back
+ end
+
+ def test_time_helper_freeze_time_with_block
+ expected_time = Time.now
+
+ freeze_time do
+ sleep(1)
+
+ assert_equal expected_time.to_s(:db), Time.now.to_s(:db)
+ end
+
+ assert_operator expected_time.to_s(:db), :<, Time.now.to_s(:db)
+ end
end
diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb
index 4794b55742..405c8f315b 100644
--- a/activesupport/test/time_zone_test.rb
+++ b/activesupport/test/time_zone_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/time"
require "time_zone_test_helpers"
@@ -30,6 +32,12 @@ class TimeZoneTest < ActiveSupport::TestCase
end
end
+ def test_period_for_local_with_ambigiuous_time
+ zone = ActiveSupport::TimeZone["Moscow"]
+ period = zone.period_for_local(Time.utc(2015, 1, 1))
+ assert_equal period, zone.period_for_local(Time.utc(2014, 10, 26, 1, 0, 0))
+ end
+
def test_from_integer_to_map
assert_instance_of ActiveSupport::TimeZone, ActiveSupport::TimeZone[-28800] # PST
end
@@ -101,7 +109,6 @@ class TimeZoneTest < ActiveSupport::TestCase
assert_equal Date.new(2000, 1, 1), ActiveSupport::TimeZone["Eastern Time (US & Canada)"].today
travel_to(Time.utc(2000, 1, 2, 5)) # midnight Jan 2 EST
assert_equal Date.new(2000, 1, 2), ActiveSupport::TimeZone["Eastern Time (US & Canada)"].today
- travel_back
end
def test_tomorrow
@@ -113,7 +120,6 @@ class TimeZoneTest < ActiveSupport::TestCase
assert_equal Date.new(2000, 1, 2), ActiveSupport::TimeZone["Eastern Time (US & Canada)"].tomorrow
travel_to(Time.utc(2000, 1, 2, 5)) # midnight Jan 2 EST
assert_equal Date.new(2000, 1, 3), ActiveSupport::TimeZone["Eastern Time (US & Canada)"].tomorrow
- travel_back
end
def test_yesterday
@@ -125,7 +131,6 @@ class TimeZoneTest < ActiveSupport::TestCase
assert_equal Date.new(1999, 12, 31), ActiveSupport::TimeZone["Eastern Time (US & Canada)"].yesterday
travel_to(Time.utc(2000, 1, 2, 5)) # midnight Jan 2 EST
assert_equal Date.new(2000, 1, 1), ActiveSupport::TimeZone["Eastern Time (US & Canada)"].yesterday
- travel_back
end
def test_travel_to_a_date
@@ -196,6 +201,11 @@ class TimeZoneTest < ActiveSupport::TestCase
assert_equal "EDT", twz.zone
end
+ def test_local_with_ambiguous_time
+ zone = ActiveSupport::TimeZone["Moscow"]
+ assert_equal Time.utc(2014, 10, 25, 22, 0, 0), zone.local(2014, 10, 26, 1, 0, 0)
+ end
+
def test_at
zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
secs = 946684800.0
@@ -215,6 +225,100 @@ class TimeZoneTest < ActiveSupport::TestCase
assert_equal secs, twz.to_f
end
+ def test_iso8601
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
+ twz = zone.iso8601("1999-12-31T19:00:00")
+ assert_equal Time.utc(1999, 12, 31, 19), twz.time
+ assert_equal Time.utc(2000), twz.utc
+ assert_equal zone, twz.time_zone
+ end
+
+ def test_iso8601_with_fractional_seconds
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
+ twz = zone.iso8601("1999-12-31T19:00:00.750")
+ assert_equal 750000, twz.time.usec
+ assert_equal Time.utc(1999, 12, 31, 19, 0, 0 + Rational(3, 4)), twz.time
+ assert_equal Time.utc(2000, 1, 1, 0, 0, 0 + Rational(3, 4)), twz.utc
+ assert_equal zone, twz.time_zone
+ end
+
+ def test_iso8601_with_zone
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
+ twz = zone.iso8601("1999-12-31T14:00:00-10:00")
+ assert_equal Time.utc(1999, 12, 31, 19), twz.time
+ assert_equal Time.utc(2000), twz.utc
+ assert_equal zone, twz.time_zone
+ end
+
+ def test_iso8601_with_invalid_string
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
+
+ exception = assert_raises(ArgumentError) do
+ zone.iso8601("foobar")
+ end
+
+ assert_equal "invalid date", exception.message
+ end
+
+ def test_iso8601_with_missing_time_components
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
+ twz = zone.iso8601("1999-12-31")
+ assert_equal Time.utc(1999, 12, 31, 0, 0, 0), twz.time
+ assert_equal Time.utc(1999, 12, 31, 5, 0, 0), twz.utc
+ assert_equal zone, twz.time_zone
+ end
+
+ def test_iso8601_with_old_date
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
+ twz = zone.iso8601("1883-12-31T19:00:00")
+ assert_equal [0, 0, 19, 31, 12, 1883], twz.to_a[0, 6]
+ assert_equal zone, twz.time_zone
+ end
+
+ def test_iso8601_far_future_date_with_time_zone_offset_in_string
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
+ twz = zone.iso8601("2050-12-31T19:00:00-10:00") # i.e., 2050-01-01 05:00:00 UTC
+ assert_equal [0, 0, 0, 1, 1, 2051], twz.to_a[0, 6]
+ assert_equal zone, twz.time_zone
+ end
+
+ def test_iso8601_should_not_black_out_system_timezone_dst_jump
+ with_env_tz("EET") do
+ zone = ActiveSupport::TimeZone["Pacific Time (US & Canada)"]
+ twz = zone.iso8601("2012-03-25T03:29:00")
+ assert_equal [0, 29, 3, 25, 3, 2012], twz.to_a[0, 6]
+ end
+ end
+
+ def test_iso8601_should_black_out_app_timezone_dst_jump
+ with_env_tz("EET") do
+ zone = ActiveSupport::TimeZone["Pacific Time (US & Canada)"]
+ twz = zone.iso8601("2012-03-11T02:29:00")
+ assert_equal [0, 29, 3, 11, 3, 2012], twz.to_a[0, 6]
+ end
+ end
+
+ def test_iso8601_doesnt_use_local_dst
+ with_env_tz "US/Eastern" do
+ zone = ActiveSupport::TimeZone["UTC"]
+ twz = zone.iso8601("2013-03-10T02:00:00")
+ assert_equal Time.utc(2013, 3, 10, 2, 0, 0), twz.time
+ end
+ end
+
+ def test_iso8601_handles_dst_jump
+ with_env_tz "US/Eastern" do
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
+ twz = zone.iso8601("2013-03-10T02:00:00")
+ assert_equal Time.utc(2013, 3, 10, 3, 0, 0), twz.time
+ end
+ end
+
+ def test_iso8601_with_ambiguous_time
+ zone = ActiveSupport::TimeZone["Moscow"]
+ assert_equal Time.utc(2014, 10, 25, 22, 0, 0), zone.parse("2014-10-26T01:00:00")
+ end
+
def test_parse
zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
twz = zone.parse("1999-12-31 19:00:00")
@@ -314,6 +418,114 @@ class TimeZoneTest < ActiveSupport::TestCase
end
end
+ def test_parse_with_invalid_date
+ zone = ActiveSupport::TimeZone["UTC"]
+
+ exception = assert_raises(ArgumentError) do
+ zone.parse("9000")
+ end
+
+ assert_equal "argument out of range", exception.message
+ end
+
+ def test_parse_with_ambiguous_time
+ zone = ActiveSupport::TimeZone["Moscow"]
+ assert_equal Time.utc(2014, 10, 25, 22, 0, 0), zone.parse("2014-10-26 01:00:00")
+ end
+
+ def test_rfc3339
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
+ twz = zone.rfc3339("1999-12-31T14:00:00-10:00")
+ assert_equal Time.utc(1999, 12, 31, 19), twz.time
+ assert_equal Time.utc(2000), twz.utc
+ assert_equal zone, twz.time_zone
+ end
+
+ def test_rfc3339_with_fractional_seconds
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
+ twz = zone.iso8601("1999-12-31T14:00:00.750-10:00")
+ assert_equal 750000, twz.time.usec
+ assert_equal Time.utc(1999, 12, 31, 19, 0, 0 + Rational(3, 4)), twz.time
+ assert_equal Time.utc(2000, 1, 1, 0, 0, 0 + Rational(3, 4)), twz.utc
+ assert_equal zone, twz.time_zone
+ end
+
+ def test_rfc3339_with_missing_time
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
+
+ exception = assert_raises(ArgumentError) do
+ zone.rfc3339("1999-12-31")
+ end
+
+ assert_equal "invalid date", exception.message
+ end
+
+ def test_rfc3339_with_missing_offset
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
+
+ exception = assert_raises(ArgumentError) do
+ zone.rfc3339("1999-12-31T19:00:00")
+ end
+
+ assert_equal "invalid date", exception.message
+ end
+
+ def test_rfc3339_with_invalid_string
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
+
+ exception = assert_raises(ArgumentError) do
+ zone.rfc3339("foobar")
+ end
+
+ assert_equal "invalid date", exception.message
+ end
+
+ def test_rfc3339_with_old_date
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
+ twz = zone.rfc3339("1883-12-31T19:00:00-05:00")
+ assert_equal [0, 0, 19, 31, 12, 1883], twz.to_a[0, 6]
+ assert_equal zone, twz.time_zone
+ end
+
+ def test_rfc3339_far_future_date_with_time_zone_offset_in_string
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
+ twz = zone.rfc3339("2050-12-31T19:00:00-10:00") # i.e., 2050-01-01 05:00:00 UTC
+ assert_equal [0, 0, 0, 1, 1, 2051], twz.to_a[0, 6]
+ assert_equal zone, twz.time_zone
+ end
+
+ def test_rfc3339_should_not_black_out_system_timezone_dst_jump
+ with_env_tz("EET") do
+ zone = ActiveSupport::TimeZone["Pacific Time (US & Canada)"]
+ twz = zone.rfc3339("2012-03-25T03:29:00-07:00")
+ assert_equal [0, 29, 3, 25, 3, 2012], twz.to_a[0, 6]
+ end
+ end
+
+ def test_rfc3339_should_black_out_app_timezone_dst_jump
+ with_env_tz("EET") do
+ zone = ActiveSupport::TimeZone["Pacific Time (US & Canada)"]
+ twz = zone.rfc3339("2012-03-11T02:29:00-08:00")
+ assert_equal [0, 29, 3, 11, 3, 2012], twz.to_a[0, 6]
+ end
+ end
+
+ def test_rfc3339_doesnt_use_local_dst
+ with_env_tz "US/Eastern" do
+ zone = ActiveSupport::TimeZone["UTC"]
+ twz = zone.rfc3339("2013-03-10T02:00:00Z")
+ assert_equal Time.utc(2013, 3, 10, 2, 0, 0), twz.time
+ end
+ end
+
+ def test_rfc3339_handles_dst_jump
+ with_env_tz "US/Eastern" do
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
+ twz = zone.iso8601("2013-03-10T02:00:00-05:00")
+ assert_equal Time.utc(2013, 3, 10, 3, 0, 0), twz.time
+ end
+ end
+
def test_strptime
zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
twz = zone.strptime("1999-12-31 12:00:00", "%Y-%m-%d %H:%M:%S")
@@ -413,6 +625,11 @@ class TimeZoneTest < ActiveSupport::TestCase
end
end
+ def test_strptime_with_ambiguous_time
+ zone = ActiveSupport::TimeZone["Moscow"]
+ assert_equal Time.utc(2014, 10, 25, 22, 0, 0), zone.strptime("2014-10-26 01:00:00", "%Y-%m-%d %H:%M:%S")
+ end
+
def test_utc_offset_lazy_loaded_from_tzinfo_when_not_passed_in_to_initialize
tzinfo = TZInfo::Timezone.get("America/New_York")
zone = ActiveSupport::TimeZone.create(tzinfo.name, nil, tzinfo)
@@ -501,6 +718,13 @@ class TimeZoneTest < ActiveSupport::TestCase
end
end
+ def test_all_uninfluenced_by_time_zone_lookups_delegated_to_tzinfo
+ ActiveSupport::TimeZone.clear
+ galapagos = ActiveSupport::TimeZone["Pacific/Galapagos"]
+ all_zones = ActiveSupport::TimeZone.all
+ assert_not_includes all_zones, galapagos
+ end
+
def test_index
assert_nil ActiveSupport::TimeZone["bogus"]
assert_instance_of ActiveSupport::TimeZone, ActiveSupport::TimeZone["Central Time (US & Canada)"]
@@ -532,6 +756,10 @@ class TimeZoneTest < ActiveSupport::TestCase
assert_not_includes ActiveSupport::TimeZone.country_zones(:ru), ActiveSupport::TimeZone["Kuala Lumpur"]
end
+ def test_country_zones_without_mappings
+ assert_includes ActiveSupport::TimeZone.country_zones(:sv), ActiveSupport::TimeZone["America/El_Salvador"]
+ end
+
def test_to_yaml
assert_equal("--- !ruby/object:ActiveSupport::TimeZone\nname: Pacific/Honolulu\n", ActiveSupport::TimeZone["Hawaii"].to_yaml)
assert_equal("--- !ruby/object:ActiveSupport::TimeZone\nname: Europe/London\n", ActiveSupport::TimeZone["Europe/London"].to_yaml)
diff --git a/activesupport/test/time_zone_test_helpers.rb b/activesupport/test/time_zone_test_helpers.rb
index e1d615d154..051703a781 100644
--- a/activesupport/test/time_zone_test_helpers.rb
+++ b/activesupport/test/time_zone_test_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module TimeZoneTestHelpers
def with_tz_default(tz = nil)
old_tz = Time.zone
diff --git a/activesupport/test/transliterate_test.rb b/activesupport/test/transliterate_test.rb
index 466b69bcef..7d19447598 100644
--- a/activesupport/test/transliterate_test.rb
+++ b/activesupport/test/transliterate_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/inflector/transliterate"
diff --git a/activesupport/test/xml_mini/jdom_engine_test.rb b/activesupport/test/xml_mini/jdom_engine_test.rb
index e783cea67c..97a533aafb 100644
--- a/activesupport/test/xml_mini/jdom_engine_test.rb
+++ b/activesupport/test/xml_mini/jdom_engine_test.rb
@@ -1,8 +1,10 @@
+# frozen_string_literal: true
+
require_relative "xml_mini_engine_test"
XMLMiniEngineTest.run_with_platform("java") do
class JDOMEngineTest < XMLMiniEngineTest
- FILES_DIR = File.dirname(__FILE__) + "/../fixtures/xml"
+ FILES_DIR = File.expand_path("../fixtures/xml", __dir__)
def test_not_allowed_to_expand_entities_to_files
attack_xml = <<-EOT
diff --git a/activesupport/test/xml_mini/libxml_engine_test.rb b/activesupport/test/xml_mini/libxml_engine_test.rb
index f3394ad7f2..3eef3946a3 100644
--- a/activesupport/test/xml_mini/libxml_engine_test.rb
+++ b/activesupport/test/xml_mini/libxml_engine_test.rb
@@ -1,10 +1,12 @@
+# frozen_string_literal: true
+
require_relative "xml_mini_engine_test"
XMLMiniEngineTest.run_with_gem("libxml") do
class LibxmlEngineTest < XMLMiniEngineTest
def setup
super
- LibXML::XML::Error.set_handler(&lambda { |error| }) #silence libxml, exceptions will do
+ LibXML::XML::Error.set_handler(&lambda { |error| }) # silence libxml, exceptions will do
end
private
diff --git a/activesupport/test/xml_mini/libxmlsax_engine_test.rb b/activesupport/test/xml_mini/libxmlsax_engine_test.rb
index f457e160d6..8e4a30a48e 100644
--- a/activesupport/test/xml_mini/libxmlsax_engine_test.rb
+++ b/activesupport/test/xml_mini/libxmlsax_engine_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require_relative "xml_mini_engine_test"
XMLMiniEngineTest.run_with_gem("libxml") do
diff --git a/activesupport/test/xml_mini/nokogiri_engine_test.rb b/activesupport/test/xml_mini/nokogiri_engine_test.rb
index 3151e75fc0..f1584bcedf 100644
--- a/activesupport/test/xml_mini/nokogiri_engine_test.rb
+++ b/activesupport/test/xml_mini/nokogiri_engine_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require_relative "xml_mini_engine_test"
XMLMiniEngineTest.run_with_gem("nokogiri") do
diff --git a/activesupport/test/xml_mini/nokogirisax_engine_test.rb b/activesupport/test/xml_mini/nokogirisax_engine_test.rb
index 7dafbdaf48..f38a56e83b 100644
--- a/activesupport/test/xml_mini/nokogirisax_engine_test.rb
+++ b/activesupport/test/xml_mini/nokogirisax_engine_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require_relative "xml_mini_engine_test"
XMLMiniEngineTest.run_with_gem("nokogiri") do
diff --git a/activesupport/test/xml_mini/rexml_engine_test.rb b/activesupport/test/xml_mini/rexml_engine_test.rb
index c51f0d3c20..34bf81fa75 100644
--- a/activesupport/test/xml_mini/rexml_engine_test.rb
+++ b/activesupport/test/xml_mini/rexml_engine_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require_relative "xml_mini_engine_test"
class REXMLEngineTest < XMLMiniEngineTest
diff --git a/activesupport/test/xml_mini/xml_mini_engine_test.rb b/activesupport/test/xml_mini/xml_mini_engine_test.rb
index 5be9084c9d..5c4c28d9b7 100644
--- a/activesupport/test/xml_mini/xml_mini_engine_test.rb
+++ b/activesupport/test/xml_mini/xml_mini_engine_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/xml_mini"
require "active_support/core_ext/hash/conversions"
@@ -75,6 +77,11 @@ class XMLMiniEngineTest < ActiveSupport::TestCase
assert_equal({}, ActiveSupport::XmlMini.parse(""))
end
+ def test_parse_from_frozen_string
+ xml_string = "<root/>".freeze
+ assert_equal({ "root" => {} }, ActiveSupport::XmlMini.parse(xml_string))
+ end
+
def test_array_type_makes_an_array
assert_equal_rexml(<<-eoxml)
<blog>
diff --git a/activesupport/test/xml_mini_test.rb b/activesupport/test/xml_mini_test.rb
index b8caa1d74c..18a3f2ca66 100644
--- a/activesupport/test/xml_mini_test.rb
+++ b/activesupport/test/xml_mini_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "abstract_unit"
require "active_support/xml_mini"
require "active_support/builder"
@@ -112,7 +114,7 @@ module XmlMiniTest
end
test "#to_tag accepts decimal types" do
- @xml.to_tag(:b, ::BigDecimal.new("1.2"), @options)
+ @xml.to_tag(:b, BigDecimal("1.2"), @options)
assert_xml("<b type=\"decimal\">1.2</b>")
end