aboutsummaryrefslogtreecommitdiffstats
path: root/activesupport
diff options
context:
space:
mode:
Diffstat (limited to 'activesupport')
-rw-r--r--activesupport/CHANGELOG.md238
-rw-r--r--activesupport/MIT-LICENSE2
-rw-r--r--activesupport/README.rdoc7
-rw-r--r--activesupport/Rakefile2
-rw-r--r--activesupport/activesupport.gemspec12
-rwxr-xr-xactivesupport/bin/generate_tables8
-rw-r--r--activesupport/lib/active_support.rb11
-rw-r--r--activesupport/lib/active_support/all.rb1
-rw-r--r--activesupport/lib/active_support/backtrace_cleaner.rb10
-rw-r--r--activesupport/lib/active_support/cache.rb111
-rw-r--r--activesupport/lib/active_support/cache/file_store.rb55
-rw-r--r--activesupport/lib/active_support/cache/mem_cache_store.rb14
-rw-r--r--activesupport/lib/active_support/cache/memory_store.rb3
-rw-r--r--activesupport/lib/active_support/cache/strategy/local_cache.rb41
-rw-r--r--activesupport/lib/active_support/cache/strategy/local_cache_middleware.rb44
-rw-r--r--activesupport/lib/active_support/callbacks.rb330
-rw-r--r--activesupport/lib/active_support/concern.rb14
-rw-r--r--activesupport/lib/active_support/configurable.rb3
-rw-r--r--activesupport/lib/active_support/core_ext.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/array/access.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/array/conversions.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/array/grouping.rb17
-rw-r--r--activesupport/lib/active_support/core_ext/array/prepend_and_append.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/big_decimal/conversions.rb28
-rw-r--r--activesupport/lib/active_support/core_ext/class.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/class/attribute.rb5
-rw-r--r--activesupport/lib/active_support/core_ext/class/attribute_accessors.rb174
-rw-r--r--activesupport/lib/active_support/core_ext/class/delegating_attributes.rb21
-rw-r--r--activesupport/lib/active_support/core_ext/date/calculations.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/date/conversions.rb11
-rw-r--r--activesupport/lib/active_support/core_ext/date_and_time/calculations.rb91
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/calculations.rb26
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/conversions.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/digest/uuid.rb51
-rw-r--r--activesupport/lib/active_support/core_ext/enumerable.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/file/atomic.rb9
-rw-r--r--activesupport/lib/active_support/core_ext/hash.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/hash/compact.rb20
-rw-r--r--activesupport/lib/active_support/core_ext/hash/conversions.rb15
-rw-r--r--activesupport/lib/active_support/core_ext/hash/deep_merge.rb33
-rw-r--r--activesupport/lib/active_support/core_ext/hash/except.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/hash/indifferent_access.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/hash/keys.rb106
-rw-r--r--activesupport/lib/active_support/core_ext/hash/slice.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/hash/transform_values.rb23
-rw-r--r--activesupport/lib/active_support/core_ext/integer/multiple.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/kernel.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/kernel/concern.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/kernel/debugger.rb13
-rw-r--r--activesupport/lib/active_support/core_ext/kernel/reporting.rb69
-rw-r--r--activesupport/lib/active_support/core_ext/load_error.rb7
-rw-r--r--activesupport/lib/active_support/core_ext/module.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/module/aliasing.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/module/attr_internal.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/module/attribute_accessors.rb174
-rw-r--r--activesupport/lib/active_support/core_ext/module/concerning.rb135
-rw-r--r--activesupport/lib/active_support/core_ext/module/delegation.rb55
-rw-r--r--activesupport/lib/active_support/core_ext/module/deprecation.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/module/method_transplanting.rb14
-rw-r--r--activesupport/lib/active_support/core_ext/module/remove_method.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/name_error.rb14
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/bytes.rb20
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/conversions.rb28
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/time.rb38
-rw-r--r--activesupport/lib/active_support/core_ext/object.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/object/blank.rb60
-rw-r--r--activesupport/lib/active_support/core_ext/object/deep_dup.rb14
-rw-r--r--activesupport/lib/active_support/core_ext/object/duplicable.rb20
-rw-r--r--activesupport/lib/active_support/core_ext/object/inclusion.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/object/instance_variables.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/object/json.rb197
-rw-r--r--activesupport/lib/active_support/core_ext/object/to_json.rb27
-rw-r--r--activesupport/lib/active_support/core_ext/object/to_param.rb59
-rw-r--r--activesupport/lib/active_support/core_ext/object/to_query.rb73
-rw-r--r--activesupport/lib/active_support/core_ext/object/try.rb56
-rw-r--r--activesupport/lib/active_support/core_ext/object/with_options.rb33
-rw-r--r--activesupport/lib/active_support/core_ext/range.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/range/each.rb23
-rw-r--r--activesupport/lib/active_support/core_ext/securerandom.rb23
-rw-r--r--activesupport/lib/active_support/core_ext/string/access.rb70
-rw-r--r--activesupport/lib/active_support/core_ext/string/conversions.rb16
-rw-r--r--activesupport/lib/active_support/core_ext/string/exclude.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/string/filters.rb51
-rw-r--r--activesupport/lib/active_support/core_ext/string/inflections.rb25
-rw-r--r--activesupport/lib/active_support/core_ext/string/multibyte.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/string/output_safety.rb131
-rw-r--r--activesupport/lib/active_support/core_ext/struct.rb9
-rw-r--r--activesupport/lib/active_support/core_ext/thread.rb74
-rw-r--r--activesupport/lib/active_support/core_ext/time.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/time/calculations.rb57
-rw-r--r--activesupport/lib/active_support/core_ext/time/conversions.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/time/marshal.rb31
-rw-r--r--activesupport/lib/active_support/core_ext/time/zones.rb18
-rw-r--r--activesupport/lib/active_support/dependencies.rb100
-rw-r--r--activesupport/lib/active_support/dependencies/autoload.rb2
-rw-r--r--activesupport/lib/active_support/deprecation.rb2
-rw-r--r--activesupport/lib/active_support/deprecation/behaviors.rb2
-rw-r--r--activesupport/lib/active_support/duration.rb39
-rw-r--r--activesupport/lib/active_support/file_update_checker.rb2
-rw-r--r--activesupport/lib/active_support/file_watcher.rb36
-rw-r--r--activesupport/lib/active_support/gem_version.rb15
-rw-r--r--activesupport/lib/active_support/hash_with_indifferent_access.rb38
-rw-r--r--activesupport/lib/active_support/i18n_railtie.rb19
-rw-r--r--activesupport/lib/active_support/inflections.rb6
-rw-r--r--activesupport/lib/active_support/inflector/inflections.rb38
-rw-r--r--activesupport/lib/active_support/inflector/methods.rb193
-rw-r--r--activesupport/lib/active_support/inflector/transliterate.rb14
-rw-r--r--activesupport/lib/active_support/json/decoding.rb16
-rw-r--r--activesupport/lib/active_support/json/encoding.rb373
-rw-r--r--activesupport/lib/active_support/key_generator.rb16
-rw-r--r--activesupport/lib/active_support/logger.rb2
-rw-r--r--activesupport/lib/active_support/message_encryptor.rb11
-rw-r--r--activesupport/lib/active_support/message_verifier.rb90
-rw-r--r--activesupport/lib/active_support/multibyte/chars.rb3
-rw-r--r--activesupport/lib/active_support/multibyte/unicode.rb84
-rw-r--r--activesupport/lib/active_support/notifications.rb13
-rw-r--r--activesupport/lib/active_support/notifications/fanout.rb28
-rw-r--r--activesupport/lib/active_support/notifications/instrumenter.rb15
-rw-r--r--activesupport/lib/active_support/number_helper.rb364
-rw-r--r--activesupport/lib/active_support/number_helper/number_converter.rb182
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_currency_converter.rb46
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb23
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_human_converter.rb66
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb58
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_percentage_converter.rb12
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_phone_converter.rb49
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb90
-rw-r--r--activesupport/lib/active_support/option_merger.rb2
-rw-r--r--activesupport/lib/active_support/ordered_hash.rb8
-rw-r--r--activesupport/lib/active_support/ordered_options.rb2
-rw-r--r--activesupport/lib/active_support/per_thread_registry.rb17
-rw-r--r--activesupport/lib/active_support/railtie.rb7
-rw-r--r--activesupport/lib/active_support/rescuable.rb4
-rw-r--r--activesupport/lib/active_support/security_utils.rb20
-rw-r--r--activesupport/lib/active_support/subscriber.rb38
-rw-r--r--activesupport/lib/active_support/tagged_logging.rb5
-rw-r--r--activesupport/lib/active_support/test_case.rb68
-rw-r--r--activesupport/lib/active_support/testing/assertions.rb4
-rw-r--r--activesupport/lib/active_support/testing/constant_lookup.rb6
-rw-r--r--activesupport/lib/active_support/testing/declarative.rb19
-rw-r--r--activesupport/lib/active_support/testing/deprecation.rb21
-rw-r--r--activesupport/lib/active_support/testing/file_fixtures.rb34
-rw-r--r--activesupport/lib/active_support/testing/isolation.rb37
-rw-r--r--activesupport/lib/active_support/testing/stream.rb42
-rw-r--r--activesupport/lib/active_support/testing/tagged_logging.rb2
-rw-r--r--activesupport/lib/active_support/testing/time_helpers.rb132
-rw-r--r--activesupport/lib/active_support/time.rb2
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb130
-rw-r--r--activesupport/lib/active_support/values/time_zone.rb201
-rw-r--r--activesupport/lib/active_support/values/unicode_tables.datbin904483 -> 1001806 bytes
-rw-r--r--activesupport/lib/active_support/version.rb11
-rw-r--r--activesupport/lib/active_support/xml_mini.rb6
-rw-r--r--activesupport/lib/active_support/xml_mini/jdom.rb2
-rw-r--r--activesupport/lib/active_support/xml_mini/libxml.rb4
-rw-r--r--activesupport/lib/active_support/xml_mini/nokogiri.rb4
-rw-r--r--activesupport/test/abstract_unit.rb21
-rw-r--r--activesupport/test/autoload_test.rb23
-rw-r--r--activesupport/test/autoloading_fixtures/html/some_class.rb4
-rw-r--r--activesupport/test/autoloading_fixtures/typo.rb2
-rw-r--r--activesupport/test/caching_test.rb141
-rw-r--r--activesupport/test/callbacks_test.rb150
-rw-r--r--activesupport/test/clean_backtrace_test.rb5
-rw-r--r--activesupport/test/concern_test.rb20
-rw-r--r--activesupport/test/configurable_test.rb22
-rw-r--r--activesupport/test/constantize_test_cases.rb41
-rw-r--r--activesupport/test/core_ext/array/access_test.rb30
-rw-r--r--activesupport/test/core_ext/array/conversions_test.rb197
-rw-r--r--activesupport/test/core_ext/array/extract_options_test.rb45
-rw-r--r--activesupport/test/core_ext/array/grouping_test.rb126
-rw-r--r--activesupport/test/core_ext/array/prepend_append_test.rb12
-rw-r--r--activesupport/test/core_ext/array/wrap_test.rb77
-rw-r--r--activesupport/test/core_ext/array_ext_test.rb445
-rw-r--r--activesupport/test/core_ext/bigdecimal_test.rb13
-rw-r--r--activesupport/test/core_ext/class/attribute_accessor_test.rb61
-rw-r--r--activesupport/test/core_ext/class/delegating_attributes_test.rb39
-rw-r--r--activesupport/test/core_ext/date_and_time_behavior.rb70
-rw-r--r--activesupport/test/core_ext/date_ext_test.rb35
-rw-r--r--activesupport/test/core_ext/date_time_ext_test.rb17
-rw-r--r--activesupport/test/core_ext/digest/uuid_test.rb24
-rw-r--r--activesupport/test/core_ext/duplicable_test.rb38
-rw-r--r--activesupport/test/core_ext/duration_test.rb105
-rw-r--r--activesupport/test/core_ext/enumerable_test.rb29
-rw-r--r--activesupport/test/core_ext/file_test.rb10
-rw-r--r--activesupport/test/core_ext/hash/transform_keys_test.rb32
-rw-r--r--activesupport/test/core_ext/hash/transform_values_test.rb61
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb201
-rw-r--r--activesupport/test/core_ext/kernel/concern_test.rb13
-rw-r--r--activesupport/test/core_ext/kernel_test.rb57
-rw-r--r--activesupport/test/core_ext/load_error_test.rb16
-rw-r--r--activesupport/test/core_ext/module/attribute_accessor_test.rb26
-rw-r--r--activesupport/test/core_ext/module/concerning_test.rb65
-rw-r--r--activesupport/test/core_ext/module_test.rb29
-rw-r--r--activesupport/test/core_ext/name_error_test.rb12
-rw-r--r--activesupport/test/core_ext/numeric_ext_test.rb67
-rw-r--r--activesupport/test/core_ext/object/acts_like_test.rb33
-rw-r--r--activesupport/test/core_ext/object/blank_test.rb (renamed from activesupport/test/core_ext/blank_test.rb)23
-rw-r--r--activesupport/test/core_ext/object/deep_dup_test.rb (renamed from activesupport/test/core_ext/deep_dup_test.rb)0
-rw-r--r--activesupport/test/core_ext/object/duplicable_test.rb22
-rw-r--r--activesupport/test/core_ext/object/inclusion_test.rb9
-rw-r--r--activesupport/test/core_ext/object/instance_variables_test.rb31
-rw-r--r--activesupport/test/core_ext/object/json_cherry_pick_test.rb42
-rw-r--r--activesupport/test/core_ext/object/to_param_test.rb18
-rw-r--r--activesupport/test/core_ext/object/to_query_test.rb29
-rw-r--r--activesupport/test/core_ext/object/try_test.rb99
-rw-r--r--activesupport/test/core_ext/object_and_class_ext_test.rb181
-rw-r--r--activesupport/test/core_ext/range_ext_test.rb36
-rw-r--r--activesupport/test/core_ext/securerandom.rb20
-rw-r--r--activesupport/test/core_ext/string_ext_test.rb276
-rw-r--r--activesupport/test/core_ext/struct_test.rb10
-rw-r--r--activesupport/test/core_ext/thread_test.rb77
-rw-r--r--activesupport/test/core_ext/time_ext_test.rb26
-rw-r--r--activesupport/test/core_ext/time_with_zone_test.rb119
-rw-r--r--activesupport/test/core_ext/uri_ext_test.rb3
-rw-r--r--activesupport/test/dependencies_test.rb395
-rw-r--r--activesupport/test/dependencies_test_helpers.rb1
-rw-r--r--activesupport/test/deprecation_test.rb15
-rw-r--r--activesupport/test/descendants_tracker_without_autoloading_test.rb10
-rw-r--r--activesupport/test/empty_bool.rb7
-rw-r--r--activesupport/test/file_fixtures/sample.txt1
-rw-r--r--activesupport/test/fixtures/custom.rb2
-rw-r--r--activesupport/test/hash_with_indifferent_access_test.rb11
-rw-r--r--activesupport/test/i18n_test.rb1
-rw-r--r--activesupport/test/inflector_test.rb57
-rw-r--r--activesupport/test/inflector_test_cases.rb19
-rw-r--r--activesupport/test/json/decoding_test.rb56
-rw-r--r--activesupport/test/json/encoding_test.rb303
-rw-r--r--activesupport/test/key_generator_test.rb30
-rw-r--r--activesupport/test/message_encryptor_test.rb26
-rw-r--r--activesupport/test/message_verifier_test.rb70
-rw-r--r--activesupport/test/multibyte_chars_test.rb36
-rw-r--r--activesupport/test/multibyte_conformance_test.rb (renamed from activesupport/test/multibyte_conformance.rb)19
-rw-r--r--activesupport/test/multibyte_proxy_test.rb33
-rw-r--r--activesupport/test/multibyte_test_helpers.rb7
-rw-r--r--activesupport/test/multibyte_unicode_database_test.rb1
-rw-r--r--activesupport/test/number_helper_i18n_test.rb16
-rw-r--r--activesupport/test/number_helper_test.rb31
-rw-r--r--activesupport/test/option_merger_test.rb9
-rw-r--r--activesupport/test/ordered_hash_test.rb7
-rw-r--r--activesupport/test/rescuable_test.rb33
-rw-r--r--activesupport/test/safe_buffer_test.rb30
-rw-r--r--activesupport/test/security_utils_test.rb9
-rw-r--r--activesupport/test/subscriber_test.rb54
-rw-r--r--activesupport/test/tagged_logging_test.rb13
-rw-r--r--activesupport/test/test_case_test.rb (renamed from activesupport/test/test_test.rb)75
-rw-r--r--activesupport/test/testing/constant_lookup_test.rb9
-rw-r--r--activesupport/test/testing/file_fixtures_test.rb28
-rw-r--r--activesupport/test/time_travel_test.rb83
-rw-r--r--activesupport/test/time_zone_test.rb94
-rw-r--r--activesupport/test/time_zone_test_helpers.rb16
-rw-r--r--activesupport/test/transliterate_test.rb3
-rw-r--r--activesupport/test/xml_mini/nokogiri_engine_test.rb20
-rw-r--r--activesupport/test/xml_mini/nokogirisax_engine_test.rb20
-rw-r--r--activesupport/test/xml_mini/rexml_engine_test.rb14
-rw-r--r--activesupport/test/xml_mini_test.rb183
254 files changed, 7230 insertions, 4215 deletions
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 7825e69394..13d5fe3411 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,192 +1,178 @@
-* Add String#remove(pattern) as a short-hand for the common pattern of String#gsub(pattern, '')
-
- *DHH*
-
-* Adds a new deprecation behaviour that raises an exception. Throwing this
- line into +config/environments/development.rb+
-
- ActiveSupport::Deprecation.behavior = :raise
-
- will cause the application to raise an +ActiveSupport::DeprecationException+
- on deprecations.
-
- Use this for aggressive deprecation cleanups.
-
- *Xavier Noria*
-
-* Remove 'cow' => 'kine' irregular inflection from default inflections.
-
- *Andrew White*
-
-* Add `DateTime#to_s(:iso8601)` and `Date#to_s(:iso8601)` for consistency.
-
- *Andrew White*
-
-* Add `Time#to_s(:iso8601)` for easy conversion of times to the iso8601 format for easy Javascript date parsing.
-
- *DHH*
-
-* Improve `ActiveSupport::Cache::MemoryStore` cache size calculation.
- The memory used by a key/entry pair is calculated via `#cached_size`:
-
- def cached_size(key, entry)
- key.to_s.bytesize + entry.size + PER_ENTRY_OVERHEAD
- end
-
- The value of `PER_ENTRY_OVERHEAD` is 240 bytes based on an [empirical
- estimation](https://gist.github.com/ssimeonov/6047200) for 64-bit MRI on
- 1.9.3 and 2.0. GH#11512
+* Enable number_to_percentage to keep the number's precision by allowing :precision to be nil
- *Simeon Simeonov*
+ *Jack Xu*
-* Only raise `Module::DelegationError` if it's the source of the exception.
+* config_accessor became a private method, as with Ruby's attr_accessor.
- Fixes #10559
+ *Akira Matsuda*
- *Andrew White*
+* `AS::Testing::TimeHelpers#travel_to` now changes `DateTime.now` as well as
+ `Time.now` and `Date.today`.
-* Make `Time.at_with_coercion` retain the second fraction and return local time.
+ *Yuki Nishijima*
- Fixes #11350
+* Add `file_fixture` to `ActiveSupport::TestCase`.
+ It provides a simple mechanism to access sample files in your test cases.
- *Neer Friedman*, *Andrew White*
+ By default file fixtures are stored in `test/fixtures/files`. This can be
+ configured per test-case using the `file_fixture_path` class attribute.
-* Make `HashWithIndifferentAccess#select` always return the hash, even when
- `Hash#select!` returns `nil`, to allow further chaining.
+ *Yves Senn*
- *Marc Schütz*
+* Return value of yielded block in `File.atomic_write`.
-* Remove deprecated `String#encoding_aware?` core extensions (`core_ext/string/encoding`).
+ *Ian Ker-Seymer*
- *Arun Agrawal*
+* Duplicate frozen array when assigning it to a HashWithIndifferentAccess so
+ that it doesn't raise a `RuntimeError` when calling `map!` on it in `convert_value`.
-* Remove deprecated `Module#local_constant_names` in favor of `Module#local_constants`.
+ Fixes #18550.
- *Arun Agrawal*
+ *Aditya Kapoor*
-* Remove deprecated `DateTime.local_offset` in favor of `DateTime.civil_from_fromat`.
+* Add missing time zone definitions for Russian Federation and sync them
+ with `zone.tab` file from tzdata version 2014j (latest).
- *Arun Agrawal*
+ *Andrey Novikov*
-* Remove deprecated `Logger` core extensions (`core_ext/logger.rb`).
+* Add `SecureRandom.base58` for generation of random base58 strings.
- *Carlos Antonio da Silva*
+ *Matthew Draper*, *Guillermo Iguaran*
-* Remove deprecated `Time#time_with_datetime_fallback`, `Time#utc_time`
- and `Time#local_time` in favor of `Time#utc` and `Time#local`.
+* Add `#prev_day` and `#next_day` counterparts to `#yesterday` and
+ `#tomorrow` for `Date`, `Time`, and `DateTime`.
- *Vipul A M*
+ *George Claghorn*
-* Remove deprecated `Hash#diff` with no replacement.
+* Add `same_time` option to `#next_week` and `#prev_week` for `Date`, `Time`,
+ and `DateTime`.
- If you're using it to compare hashes for the purpose of testing, please use
- MiniTest's `assert_equal` instead.
+ *George Claghorn*
- *Carlos Antonio da Silva*
+* Add `#on_weekend?`, `#next_weekday`, `#prev_weekday` methods to `Date`,
+ `Time`, and `DateTime`.
-* Remove deprecated `Date#to_time_in_current_zone` in favor of `Date#in_time_zone`.
+ `#on_weekend?` returns true if the receiving date/time falls on a Saturday
+ or Sunday.
- *Vipul A M*
+ `#next_weekday` returns a new date/time representing the next day that does
+ not fall on a Saturday or Sunday.
-* Remove deprecated `Proc#bind` with no replacement.
+ `#prev_weekday` returns a new date/time representing the previous day that
+ does not fall on a Saturday or Sunday.
- *Carlos Antonio da Silva*
+ *George Claghorn*
-* Remove deprecated `Array#uniq_by` and `Array#uniq_by!`, use native
- `Array#uniq` and `Array#uniq!` instead.
+* Change the default test order from `:sorted` to `:random`.
- *Carlos Antonio da Silva*
+ *Rafael Mendonça França*
-* Remove deprecated `ActiveSupport::BasicObject`, use `ActiveSupport::ProxyObject` instead.
+* Remove deprecated `ActiveSupport::JSON::Encoding::CircularReferenceError`.
- *Carlos Antonio da Silva*
+ *Rafael Mendonça França*
-* Remove deprecated `BufferedLogger`.
+* Remove deprecated methods `ActiveSupport::JSON::Encoding.encode_big_decimal_as_string=`
+ and `ActiveSupport::JSON::Encoding.encode_big_decimal_as_string`.
- *Yves Senn*
+ *Rafael Mendonça França*
-* Remove deprecated `assert_present` and `assert_blank` methods.
+* Remove deprecated `ActiveSupport::SafeBuffer#prepend`.
- *Yves Senn*
+ *Rafael Mendonça França*
-* Fix return value from `BacktraceCleaner#noise` when the cleaner is configured
- with multiple silencers.
+* Remove deprecated methods at `Kernel`.
- Fixes #11030
+ `silence_stderr`, `silence_stream`, `capture` and `quietly`.
- *Mark J. Titorenko*
+ *Rafael Mendonça França*
-* `HashWithIndifferentAccess#select` now returns a `HashWithIndifferentAccess`
- instance instead of a `Hash` instance.
+* Remove deprecated `active_support/core_ext/big_decimal/yaml_conversions`
+ file.
- Fixes #10723
+ *Rafael Mendonça França*
- *Albert Llop*
+* Remove deprecated methods `ActiveSupport::Cache::Store.instrument` and
+ `ActiveSupport::Cache::Store.instrument=`.
-* Add `DateTime#usec` and `DateTime#nsec` so that `ActiveSupport::TimeWithZone` keeps
- sub-second resolution when wrapping a `DateTime` value.
+ *Rafael Mendonça França*
- Fixes #10855
+* Change the way in which callback chains can be halted.
- *Andrew White*
+ The preferred method to halt a callback chain from now on is to explicitly
+ `throw(:abort)`.
+ In the past, returning `false` in an ActiveSupport callback had the side
+ effect of halting the callback chain. This is not recommended anymore and,
+ depending on the value of
+ `Callbacks::CallbackChain.halt_and_display_warning_on_return_false`, will
+ either not work at all or display a deprecation warning.
-* Fix `ActiveSupport::Dependencies::Loadable#load_dependency` calling
- `#blame_file!` on Exceptions that do not have the Blamable mixin
+* Add Callbacks::CallbackChain.halt_and_display_warning_on_return_false
- *Andrew Kreiling*
+ Setting `Callbacks::CallbackChain.halt_and_display_warning_on_return_false`
+ to true will let an app support the deprecated way of halting callback
+ chains by returning `false`.
-* Override `Time.at` to support the passing of Time-like values when called with a single argument.
+ Setting the value to false will tell the app to ignore any `false` value
+ returned by callbacks, and only halt the chain upon `throw(:abort)`.
- *Andrew White*
+ The value can also be set with the Rails configuration option
+ `config.active_support.halt_callback_chains_on_return_false`.
-* Prevent side effects to hashes inside arrays when
- `Hash#with_indifferent_access` is called.
+ When the configuration option is missing, its value is `true`, so older apps
+ ported to Rails 5.0 will not break (but display a deprecation warning).
+ For new Rails 5.0 apps, its value is set to `false` in an initializer, so
+ these apps will support the new behavior by default.
- Fixes #10526
+ *claudiob*
- *Yves Senn*
+* Changes arguments and default value of CallbackChain's :terminator option
-* Raise an error when multiple `included` blocks are defined for a Concern.
- The old behavior would silently discard previously defined blocks, running
- only the last one.
+ Chains of callbacks defined without an explicit `:terminator` option will
+ now be halted as soon as a `before_` callback throws `:abort`.
- *Mike Dillon*
+ Chains of callbacks defined with a `:terminator` option will maintain their
+ existing behavior of halting as soon as a `before_` callback matches the
+ terminator's expectation.
-* Replace `multi_json` with `json`.
+ *claudiob*
- Since Rails requires Ruby 1.9 and since Ruby 1.9 includes `json` in the standard library,
- `multi_json` is no longer necessary.
+* Deprecate `MissingSourceFile` in favor of `LoadError`.
- *Erik Michaels-Ober*
+ `MissingSourceFile` was just an alias to `LoadError` and was not being
+ raised inside the framework.
-* Added escaping of U+2028 and U+2029 inside the json encoder.
- These characters are legal in JSON but break the Javascript interpreter.
- After escaping them, the JSON is still legal and can be parsed by Javascript.
+ *Rafael Mendonça França*
- *Mario Caropreso + Viktor Kelemen + zackham*
+* Add support for error dispatcher classes in `ActiveSupport::Rescuable`.
+ Now it acts closer to Ruby's rescue.
-* Fix skipping object callbacks using metadata fetched via callback chain
- inspection methods (`_*_callbacks`)
+ Example:
- *Sean Walbran*
+ class BaseController < ApplicationController
+ module ErrorDispatcher
+ def self.===(other)
+ Exception === other && other.respond_to?(:status)
+ end
+ end
-* Add a `fetch_multi` method to the cache stores. The method provides
- an easy to use API for fetching multiple values from the cache.
+ rescue_from ErrorDispatcher do |error|
+ render status: error.status, json: { error: error.to_s }
+ end
+ end
- Example:
+ *Genadi Samokovarov*
- # Calculating scores is expensive, so we only do it for posts
- # that have been updated. Cache keys are automatically extracted
- # from objects that define a #cache_key method.
- scores = Rails.cache.fetch_multi(*posts) do |post|
- calculate_score(post)
- end
+* Add `#verified` and `#valid_message?` methods to `ActiveSupport::MessageVerifier`
- *Daniel Schierbeck*
+ Previously, the only way to decode a message with `ActiveSupport::MessageVerifier`
+ was to use `#verify`, which would raise an exception on invalid messages. Now
+ `#verified` can also be used, which returns `nil` on messages that cannot be
+ decoded.
-* Enable number_to_percentage to keep the number's precision by allowing :precision to be nil
-
+ Previously, there was no way to check if a message's format was valid without
+ attempting to decode it. `#valid_message?` is a boolean convenience method that
+ checks whether the message is valid without actually decoding it.
- *Jack Xu*
+ *Logan Leger*
-Please check [4-0-stable](https://github.com/rails/rails/blob/4-0-stable/activesupport/CHANGELOG.md) for previous changes.
+Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/activesupport/CHANGELOG.md) for previous changes.
diff --git a/activesupport/MIT-LICENSE b/activesupport/MIT-LICENSE
index 6aeeb7132d..7bffebb076 100644
--- a/activesupport/MIT-LICENSE
+++ b/activesupport/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2005-2013 David Heinemeier Hansson
+Copyright (c) 2005-2015 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 f3582767c0..a6424a353a 100644
--- a/activesupport/README.rdoc
+++ b/activesupport/README.rdoc
@@ -30,6 +30,11 @@ API documentation is at:
* http://api.rubyonrails.org
-Bug reports and feature requests can be filed with the rest for the Ruby on Rails project here:
+Bug reports can be filed for the Ruby on Rails project here:
* https://github.com/rails/rails/issues
+
+Feature requests should be discussed on the rails-core mailing list here:
+
+* https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core
+
diff --git a/activesupport/Rakefile b/activesupport/Rakefile
index 5ba153662a..7c40df8dc8 100644
--- a/activesupport/Rakefile
+++ b/activesupport/Rakefile
@@ -7,9 +7,9 @@ Rake::TestTask.new do |t|
t.pattern = 'test/**/*_test.rb'
t.warning = true
t.verbose = true
+ t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION)
end
-
namespace :test do
task :isolated do
Dir.glob("test/**/*_test.rb").all? do |file|
diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec
index ffc2c2074e..a5339e6475 100644
--- a/activesupport/activesupport.gemspec
+++ b/activesupport/activesupport.gemspec
@@ -7,7 +7,7 @@ Gem::Specification.new do |s|
s.summary = 'A toolkit of support libraries and Ruby core extensions extracted from the Rails framework.'
s.description = 'A toolkit of support libraries and Ruby core extensions extracted from the Rails framework. Rich support for multibyte strings, internationalization, time zones, and testing.'
- s.required_ruby_version = '>= 1.9.3'
+ s.required_ruby_version = '>= 2.2.0'
s.license = 'MIT'
@@ -20,9 +20,9 @@ Gem::Specification.new do |s|
s.rdoc_options.concat ['--encoding', 'UTF-8']
- s.add_dependency('i18n', '~> 0.6', '>= 0.6.4')
- s.add_dependency 'json', '~> 1.7'
- s.add_dependency 'tzinfo', '~> 0.3.37'
- s.add_dependency 'minitest', '~> 5.0'
- s.add_dependency 'thread_safe','~> 0.1'
+ s.add_dependency 'i18n', '~> 0.7'
+ s.add_dependency 'json', '~> 1.7', '>= 1.7.7'
+ s.add_dependency 'tzinfo', '~> 1.1'
+ s.add_dependency 'minitest', '~> 5.1'
+ s.add_dependency 'thread_safe','~> 0.3', '>= 0.3.4'
end
diff --git a/activesupport/bin/generate_tables b/activesupport/bin/generate_tables
index 5fefa429df..71a6b78652 100755
--- a/activesupport/bin/generate_tables
+++ b/activesupport/bin/generate_tables
@@ -28,12 +28,6 @@ module ActiveSupport
def initialize
@ucd = Unicode::UnicodeDatabase.new
-
- default = Codepoint.new
- default.combining_class = 0
- default.uppercase_mapping = 0
- default.lowercase_mapping = 0
- @ucd.codepoints = Hash.new(default)
end
def parse_codepoints(line)
@@ -61,7 +55,7 @@ module ActiveSupport
codepoint.combining_class = Integer($4)
#codepoint.bidi_class = $5
codepoint.decomp_type = $7
- codepoint.decomp_mapping = ($8=='') ? nil : $8.split.collect { |element| element.hex }
+ codepoint.decomp_mapping = ($8=='') ? nil : $8.split.collect(&:hex)
#codepoint.bidi_mirrored = ($13=='Y') ? true : false
codepoint.uppercase_mapping = ($16=='') ? 0 : $16.hex
codepoint.lowercase_mapping = ($17=='') ? 0 : $17.hex
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index 5e1fe9e556..290920dbf8 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2005-2013 David Heinemeier Hansson
+# Copyright (c) 2005-2015 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -52,6 +52,7 @@ module ActiveSupport
autoload :MessageEncryptor
autoload :MessageVerifier
autoload :Multibyte
+ autoload :NumberHelper
autoload :OptionMerger
autoload :OrderedHash
autoload :OrderedOptions
@@ -63,6 +64,14 @@ module ActiveSupport
autoload :Rescuable
autoload :SafeBuffer, "active_support/core_ext/string/output_safety"
autoload :TestCase
+
+ def self.eager_load!
+ super
+
+ NumberHelper.eager_load!
+ end
+
+ cattr_accessor :test_order # :nodoc:
end
autoload :I18n, "active_support/i18n"
diff --git a/activesupport/lib/active_support/all.rb b/activesupport/lib/active_support/all.rb
index 151008bbaa..f537818300 100644
--- a/activesupport/lib/active_support/all.rb
+++ b/activesupport/lib/active_support/all.rb
@@ -1,4 +1,3 @@
require 'active_support'
-require 'active_support/deprecation'
require 'active_support/time'
require 'active_support/core_ext'
diff --git a/activesupport/lib/active_support/backtrace_cleaner.rb b/activesupport/lib/active_support/backtrace_cleaner.rb
index c88ae3e661..d06f22ad5c 100644
--- a/activesupport/lib/active_support/backtrace_cleaner.rb
+++ b/activesupport/lib/active_support/backtrace_cleaner.rb
@@ -13,7 +13,7 @@ module ActiveSupport
# can focus on the rest.
#
# bc = BacktraceCleaner.new
- # bc.add_filter { |line| line.gsub(Rails.root, '') } # strip the Rails.root prefix
+ # bc.add_filter { |line| line.gsub(Rails.root.to_s, '') } # strip the Rails.root prefix
# bc.add_silencer { |line| line =~ /mongrel|rubygems/ } # skip any lines from mongrel or rubygems
# bc.clean(exception.backtrace) # perform the cleanup
#
@@ -22,7 +22,7 @@ module ActiveSupport
# <tt>BacktraceCleaner#remove_silencers!</tt>, which will restore the
# backtrace to a pristine state. If you need to reconfigure an existing
# BacktraceCleaner so that it does not filter or modify the paths of any lines
- # of the backtrace, you can call <tt>BacktraceCleaner#remove_filters!<tt>
+ # of the backtrace, you can call <tt>BacktraceCleaner#remove_filters!</tt>
# These two methods will give you a completely untouched backtrace.
#
# Inspired by the Quiet Backtrace gem by Thoughtbot.
@@ -65,14 +65,14 @@ module ActiveSupport
@silencers << block
end
- # Will remove all silencers, but leave in the filters. This is useful if
- # your context of debugging suddenly expands as you suspect a bug in one of
+ # Removes all silencers, but leaves in the filters. Useful if your
+ # context of debugging suddenly expands as you suspect a bug in one of
# the libraries you use.
def remove_silencers!
@silencers = []
end
- # Removes all filters, but leaves in silencers. Useful if you suddenly
+ # Removes all filters, but leaves in the silencers. Useful if you suddenly
# need to see entire filepaths in the backtrace that you had already
# filtered out.
def remove_filters!
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index 308a1b2cd9..3a1e1ac3a1 100644
--- a/activesupport/lib/active_support/cache.rb
+++ b/activesupport/lib/active_support/cache.rb
@@ -3,7 +3,7 @@ require 'zlib'
require 'active_support/core_ext/array/extract_options'
require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/benchmark'
-require 'active_support/core_ext/class/attribute_accessors'
+require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/core_ext/numeric/bytes'
require 'active_support/core_ext/numeric/time'
require 'active_support/core_ext/object/to_param'
@@ -12,10 +12,10 @@ require 'active_support/core_ext/string/inflections'
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 :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 :NullStore, 'active_support/cache/null_store'
# These options mean something to all cache implementations. Individual cache
# implementations may support additional options.
@@ -88,25 +88,24 @@ module ActiveSupport
end
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
+ end.to_s
+ end
- 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
- 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 "active_support/cache/#{store}"
- rescue LoadError => e
- raise "Could not find cache store adapter for #{store} (#{e})"
- else
- ActiveSupport::Cache.const_get(store.to_s.camelize)
- 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 "active_support/cache/#{store}"
+ rescue LoadError => e
+ raise "Could not find cache store adapter for #{store} (#{e})"
+ else
+ ActiveSupport::Cache.const_get(store.to_s.camelize)
+ end
end
# An abstract cache store class. There are multiple cache store
@@ -153,7 +152,6 @@ module ActiveSupport
# or +write+. To specify the threshold at which to compress values, set the
# <tt>:compress_threshold</tt> option. The default threshold is 16K.
class Store
-
cattr_accessor :logger, :instance_writer => true
attr_reader :silence, :options
@@ -180,16 +178,6 @@ module ActiveSupport
@silence = previous_silence
end
- # Set to +true+ if cache stores should be instrumented.
- # Default is +false+.
- def self.instrument=(boolean)
- Thread.current[:instrument_cache_store] = boolean
- end
-
- def self.instrument
- Thread.current[:instrument_cache_store] || false
- end
-
# Fetches data from the cache, using the given key. If there is data in
# the cache with the given key, then that data is returned.
#
@@ -234,9 +222,9 @@ module ActiveSupport
# bump the cache expiration time by the value set in <tt>:race_condition_ttl</tt>.
# Yes, this process is extending the time for a stale value by another few
# seconds. Because of extended life of the previous cache, other processes
- # will continue to use slightly stale data for a just a big longer. In the
+ # will continue to use slightly stale data for a just a bit longer. In the
# meantime that first process will go ahead and will write into cache the
- # new value. After that all the processes will start getting new value.
+ # new value. After that all the processes will start getting the new value.
# The key is to keep <tt>:race_condition_ttl</tt> small.
#
# If the process regenerating the entry errors out, the entry will be
@@ -359,20 +347,19 @@ module ActiveSupport
#
# Options are passed to the underlying cache implementation.
#
- # Returns an array with the data for each of the names. For example:
+ # Returns a hash with the data for each of the names. For example:
#
# cache.write("bim", "bam")
- # cache.fetch_multi("bim", "boom") {|key| key * 2 }
- # #=> ["bam", "boomboom"]
+ # cache.fetch_multi("bim", "boom") { |key| key * 2 }
+ # # => { "bam" => "bam", "boom" => "boomboom" }
#
def fetch_multi(*names)
options = names.extract_options!
options = merged_options(options)
-
results = read_multi(*names, options)
- names.map do |name|
- results.fetch(name) do
+ names.each_with_object({}) do |name, memo|
+ memo[name] = results.fetch(name) do
value = yield name
write(name, value, options)
value
@@ -385,6 +372,7 @@ module ActiveSupport
# Options are passed to the underlying cache implementation.
def write(name, value, options = nil)
options = merged_options(options)
+
instrument(:write, name, options) do
entry = Entry.new(value, options)
write_entry(namespaced_key(name, options), entry, options)
@@ -396,16 +384,18 @@ module ActiveSupport
# Options are passed to the underlying cache implementation.
def delete(name, options = nil)
options = merged_options(options)
+
instrument(:delete, name) do
delete_entry(namespaced_key(name, options), options)
end
end
- # Return +true+ if the cache contains an entry for the given key.
+ # Returns +true+ if the cache contains an entry for the given key.
#
# Options are passed to the underlying cache implementation.
def exist?(name, options = nil)
options = merged_options(options)
+
instrument(:exist?, name) do
entry = read_entry(namespaced_key(name, options), options)
(entry && !entry.expired?) || false
@@ -451,7 +441,7 @@ module ActiveSupport
# Clear the entire cache. Be careful with this method since it could
# affect other processes if shared cache is being used.
#
- # Options are passed to the underlying cache implementation.
+ # The options hash is passed to the underlying cache implementation.
#
# All implementations may not support this method.
def clear(options = nil)
@@ -539,13 +529,9 @@ module ActiveSupport
def instrument(operation, key, options = nil)
log(operation, key, options)
- if self.class.instrument
- payload = { :key => key }
- payload.merge!(options) if options.is_a?(Hash)
- ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload){ yield(payload) }
- else
- yield(nil)
- end
+ payload = { :key => key }
+ payload.merge!(options) if options.is_a?(Hash)
+ ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload){ yield(payload) }
end
def log(operation, key, options = nil)
@@ -585,6 +571,7 @@ module ActiveSupport
result = instrument(:generate, name, options) do |payload|
yield(name)
end
+
write(name, result, options)
result
end
@@ -608,20 +595,19 @@ module ActiveSupport
else
@value = value
end
+
@created_at = Time.now.to_f
@expires_in = options[:expires_in]
@expires_in = @expires_in.to_f if @expires_in
end
def value
- convert_version_4beta1_entry! if defined?(@v)
compressed? ? uncompress(@value) : @value
end
# Check if the entry is expired. The +expires_in+ parameter can override
# the value set when the entry was created.
def expired?
- convert_version_4beta1_entry! if defined?(@value)
@expires_in && @created_at + @expires_in <= Time.now.to_f
end
@@ -657,7 +643,6 @@ module ActiveSupport
# Duplicate the value in a class. This is used by cache implementations that don't natively
# serialize entries to protect against accidental cache modifications.
def dup_value!
- convert_version_4beta1_entry! if defined?(@v)
if @value && !compressed? && !(@value.is_a?(Numeric) || @value == true || @value == false)
if @value.is_a?(String)
@value = @value.dup
@@ -672,8 +657,10 @@ module ActiveSupport
if value && options[:compress]
compress_threshold = options[: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
end
+
false
end
@@ -688,24 +675,6 @@ module ActiveSupport
def uncompress(value)
Marshal.load(Zlib::Inflate.inflate(value))
end
-
- # The internals of this method changed between Rails 3.x and 4.0. This method provides the glue
- # to ensure that cache entries created under the old version still work with the new class definition.
- def convert_version_4beta1_entry!
- if defined?(@v)
- @value = @v
- remove_instance_variable(:@v)
- end
- if defined?(@c)
- @compressed = @c
- remove_instance_variable(:@c)
- end
- if defined?(@x) && @x
- @created_at ||= Time.now.to_f
- @expires_in = @x - @created_at
- remove_instance_variable(:@x)
- end
- end
end
end
end
diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb
index 472f23c1c5..d08ecd2f7d 100644
--- a/activesupport/lib/active_support/cache/file_store.rb
+++ b/activesupport/lib/active_support/cache/file_store.rb
@@ -14,6 +14,7 @@ module ActiveSupport
DIR_FORMATTER = "%03X"
FILENAME_MAX_SIZE = 228 # max filename size on file system is 255, minus room for timestamp and random characters appended by Tempfile (used by atomic write)
+ FILEPATH_MAX_SIZE = 900 # max is 1024, plus some room
EXCLUDED_DIRS = ['.', '..'].freeze
def initialize(cache_path, options = nil)
@@ -22,18 +23,19 @@ module ActiveSupport
extend Strategy::LocalCache
end
- # Deletes all items from the cache. In this case it deletes all the entries in the specified
- # file store directory except for .gitkeep. Be careful which directory is specified in your
+ # Deletes all items from the cache. In this case it deletes all the entries in the specified
+ # file store directory except for .gitkeep. Be careful which directory is specified in your
# config file when using +FileStore+ because everything in that directory will be deleted.
def clear(options = nil)
root_dirs = Dir.entries(cache_path).reject {|f| (EXCLUDED_DIRS + [".gitkeep"]).include?(f)}
FileUtils.rm_r(root_dirs.collect{|f| File.join(cache_path, f)})
end
- # Premptively iterates through all stored keys and removes the ones which have expired.
+ # Preemptively iterates through all stored keys and removes the ones which have expired.
def cleanup(options = nil)
options = merged_options(options)
- each_key(options) do |key|
+ search_dir(cache_path) do |fname|
+ key = file_path_key(fname)
entry = read_entry(key, options)
delete_entry(key, options) if entry && entry.expired?
end
@@ -42,33 +44,13 @@ module ActiveSupport
# Increments an already existing integer value that is stored in the cache.
# If the key is not found nothing is done.
def increment(name, amount = 1, options = nil)
- file_name = key_file_path(namespaced_key(name, options))
- lock_file(file_name) do
- options = merged_options(options)
- if num = read(name, options)
- num = num.to_i + amount
- write(name, num, options)
- num
- else
- nil
- end
- end
+ modify_value(name, amount, options)
end
# Decrements an already existing integer value that is stored in the cache.
# If the key is not found nothing is done.
def decrement(name, amount = 1, options = nil)
- file_name = key_file_path(namespaced_key(name, options))
- lock_file(file_name) do
- options = merged_options(options)
- if num = read(name, options)
- num = num.to_i - amount
- write(name, num, options)
- num
- else
- nil
- end
- end
+ modify_value(name, -amount, options)
end
def delete_matched(matcher, options = nil)
@@ -96,6 +78,7 @@ module ActiveSupport
def write_entry(key, entry, options)
file_name = key_file_path(key)
+ return false if options[:unless_exist] && File.exist?(file_name)
ensure_cache_path(File.dirname(file_name))
File.atomic_write(file_name, cache_path) {|f| Marshal.dump(entry, f)}
true
@@ -135,6 +118,10 @@ module ActiveSupport
# Translate a key into a file path.
def key_file_path(key)
+ if key.size > FILEPATH_MAX_SIZE
+ key = Digest::MD5.hexdigest(key)
+ end
+
fname = URI.encode_www_form_component(key)
hash = Zlib.adler32(fname)
hash, dir_1 = hash.divmod(0x1000)
@@ -182,6 +169,22 @@ module ActiveSupport
end
end
end
+
+ # Modifies the amount of an already existing integer value that is stored in the cache.
+ # If the key is not found nothing is done.
+ def modify_value(name, amount, options)
+ file_name = key_file_path(namespaced_key(name, options))
+
+ lock_file(file_name) do
+ options = merged_options(options)
+
+ if num = read(name, options)
+ num = num.to_i + amount
+ write(name, num, options)
+ num
+ end
+ end
+ end
end
end
end
diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb
index 512296554f..61b4f0b8b0 100644
--- a/activesupport/lib/active_support/cache/mem_cache_store.rb
+++ b/activesupport/lib/active_support/cache/mem_cache_store.rb
@@ -41,17 +41,15 @@ module ActiveSupport
#
# If no addresses are specified, then MemCacheStore will connect to
# localhost port 11211 (the default memcached port).
- #
- # Instead of addresses one can pass in a MemCache-like object. For example:
- #
- # require 'memcached' # gem install memcached; uses C bindings to libmemcached
- # ActiveSupport::Cache::MemCacheStore.new(Memcached::Rails.new("localhost:11211"))
def initialize(*addresses)
addresses = addresses.flatten
options = addresses.extract_options!
super(options)
- if addresses.first.respond_to?(:get)
+ unless [String, Dalli::Client, NilClass].include?(addresses.first.class)
+ raise ArgumentError, "First argument must be an empty array, an array of hosts or a Dalli::Client instance."
+ end
+ if addresses.first.is_a?(Dalli::Client)
@data = addresses.first
else
mem_cache_options = options.dup
@@ -87,7 +85,7 @@ module ActiveSupport
instrument(:increment, name, :amount => amount) do
@data.incr(escape_key(namespaced_key(name, options)), amount)
end
- rescue Dalli::DalliError
+ rescue Dalli::DalliError => e
logger.error("DalliError (#{e}): #{e.message}") if logger
nil
end
@@ -101,7 +99,7 @@ module ActiveSupport
instrument(:decrement, name, :amount => amount) do
@data.decr(escape_key(namespaced_key(name, options)), amount)
end
- rescue Dalli::DalliError
+ rescue Dalli::DalliError => e
logger.error("DalliError (#{e}): #{e.message}") if logger
nil
end
diff --git a/activesupport/lib/active_support/cache/memory_store.rb b/activesupport/lib/active_support/cache/memory_store.rb
index b979521c99..8a0523d0e2 100644
--- a/activesupport/lib/active_support/cache/memory_store.rb
+++ b/activesupport/lib/active_support/cache/memory_store.rb
@@ -36,7 +36,7 @@ module ActiveSupport
end
end
- # Premptively iterates through all stored keys and removes the ones which have expired.
+ # Preemptively iterates through all stored keys and removes the ones which have expired.
def cleanup(options = nil)
options = merged_options(options)
instrument(:cleanup, :size => @data.size) do
@@ -124,7 +124,6 @@ module ActiveSupport
protected
- # See https://gist.github.com/ssimeonov/6047200
PER_ENTRY_OVERHEAD = 240
def cached_size(key, entry)
diff --git a/activesupport/lib/active_support/cache/strategy/local_cache.rb b/activesupport/lib/active_support/cache/strategy/local_cache.rb
index fb42c4a41e..73c6b3cb88 100644
--- a/activesupport/lib/active_support/cache/strategy/local_cache.rb
+++ b/activesupport/lib/active_support/cache/strategy/local_cache.rb
@@ -1,5 +1,6 @@
require 'active_support/core_ext/object/duplicable'
require 'active_support/core_ext/string/inflections'
+require 'active_support/per_thread_registry'
module ActiveSupport
module Cache
@@ -8,6 +9,8 @@ module ActiveSupport
# duration of a block. Repeated calls to the cache for the same key will hit the
# in-memory cache for faster access.
module LocalCache
+ autoload :Middleware, 'active_support/cache/strategy/local_cache_middleware'
+
# Class for storing and registering the local caches.
class LocalCacheRegistry # :nodoc:
extend ActiveSupport::PerThreadRegistry
@@ -23,6 +26,9 @@ module ActiveSupport
def set_cache_for(local_cache_key, value)
@registry[local_cache_key] = value
end
+
+ def self.set_cache_for(l, v); instance.set_cache_for l, v; end
+ def self.cache_for(l); instance.cache_for l; end
end
# Simple memory backed cache. This cache is not thread safe and is intended only
@@ -60,32 +66,6 @@ module ActiveSupport
def with_local_cache
use_temporary_local_cache(LocalStore.new) { yield }
end
-
- #--
- # This class wraps up local storage for middlewares. Only the middleware method should
- # construct them.
- class Middleware # :nodoc:
- attr_reader :name, :local_cache_key
-
- def initialize(name, local_cache_key)
- @name = name
- @local_cache_key = local_cache_key
- @app = nil
- end
-
- def new(app)
- @app = app
- self
- end
-
- def call(env)
- LocalCacheRegistry.set_cache_for(local_cache_key, LocalStore.new)
- @app.call(env)
- ensure
- LocalCacheRegistry.set_cache_for(local_cache_key, nil)
- end
- end
-
# Middleware class can be inserted as a Rack handler to be local cache for the
# duration of request.
def middleware
@@ -106,13 +86,13 @@ module ActiveSupport
def increment(name, amount = 1, options = nil) # :nodoc:
value = bypass_local_cache{super}
- increment_or_decrement(value, name, amount, options)
+ set_cache_value(value, name, amount, options)
value
end
def decrement(name, amount = 1, options = nil) # :nodoc:
value = bypass_local_cache{super}
- increment_or_decrement(value, name, amount, options)
+ set_cache_value(value, name, amount, options)
value
end
@@ -140,8 +120,7 @@ module ActiveSupport
super
end
- private
- def increment_or_decrement(value, name, amount, options)
+ def set_cache_value(value, name, amount, options)
if local_cache
local_cache.mute do
if value
@@ -153,6 +132,8 @@ module ActiveSupport
end
end
+ private
+
def local_cache_key
@local_cache_key ||= "#{self.class.name.underscore}_local_cache_#{object_id}".gsub(/[\/-]/, '_').to_sym
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
new file mode 100644
index 0000000000..a6f24b1a3c
--- /dev/null
+++ b/activesupport/lib/active_support/cache/strategy/local_cache_middleware.rb
@@ -0,0 +1,44 @@
+require 'rack/body_proxy'
+require 'rack/utils'
+
+module ActiveSupport
+ module Cache
+ module Strategy
+ module LocalCache
+
+ #--
+ # This class wraps up local storage for middlewares. Only the middleware method should
+ # construct them.
+ class Middleware # :nodoc:
+ attr_reader :name, :local_cache_key
+
+ def initialize(name, local_cache_key)
+ @name = name
+ @local_cache_key = local_cache_key
+ @app = nil
+ end
+
+ def new(app)
+ @app = app
+ self
+ end
+
+ def call(env)
+ LocalCacheRegistry.set_cache_for(local_cache_key, LocalStore.new)
+ response = @app.call(env)
+ response[2] = ::Rack::BodyProxy.new(response[2]) do
+ LocalCacheRegistry.set_cache_for(local_cache_key, nil)
+ end
+ 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
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index 5c738572a8..0d5035f637 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -4,17 +4,18 @@ 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/string/filters'
require 'thread'
module ActiveSupport
- # Callbacks are code hooks that are run at key points in an object's lifecycle.
+ # Callbacks are code hooks that are run at key points in an object's life cycle.
# The typical use case is to have a base class define a set of callbacks
# relevant to the other functionality it supplies, so that subclasses can
# install callbacks that enhance or modify the base functionality without
# needing to override or redefine methods of the base class.
#
# Mixing in this module allows you to define the events in the object's
- # lifecycle that will support callbacks (via +ClassMethods.define_callbacks+),
+ # life cycle that will support callbacks (via +ClassMethods.define_callbacks+),
# set the instance methods, procs, or callback objects to be called (via
# +ClassMethods.set_callback+), and run the installed callbacks at the
# appropriate times (via +run_callbacks+).
@@ -71,25 +72,29 @@ module ActiveSupport
# order.
#
# If the callback chain was halted, returns +false+. Otherwise returns the
- # result of the block, or +true+ if no block is given.
+ # result of the block, +nil+ if no callbacks have been set, or +true+
+ # if callbacks have been set but no block is given.
#
# run_callbacks :save do
# save
# end
def run_callbacks(kind, &block)
- cbs = send("_#{kind}_callbacks")
- if cbs.empty?
- yield if block_given?
+ send "_run_#{kind}_callbacks", &block
+ end
+
+ private
+
+ def _run_callbacks(callbacks, &block)
+ if callbacks.empty?
+ block.call if block
else
- runner = cbs.compile
+ runner = callbacks.compile
e = Filters::Environment.new(self, false, nil, block)
runner.call(e).value
end
end
- private
-
- # A hook invoked everytime a before callback is halted.
+ # A hook invoked every time a before callback is halted.
# This can be overridden in AS::Callback implementors in order
# to provide better debugging/logging.
def halted_callback_hook(filter)
@@ -117,102 +122,106 @@ module ActiveSupport
ENDING = End.new
class Before
- def self.build(next_callback, user_callback, user_conditions, chain_config, filter)
+ def self.build(callback_sequence, user_callback, user_conditions, chain_config, filter)
halted_lambda = chain_config[:terminator]
if chain_config.key?(:terminator) && user_conditions.any?
- halting_and_conditional(next_callback, user_callback, user_conditions, halted_lambda, filter)
+ halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter)
elsif chain_config.key? :terminator
- halting(next_callback, user_callback, halted_lambda, filter)
+ halting(callback_sequence, user_callback, halted_lambda, filter)
elsif user_conditions.any?
- conditional(next_callback, user_callback, user_conditions)
+ conditional(callback_sequence, user_callback, user_conditions)
else
- simple next_callback, user_callback
+ simple callback_sequence, user_callback
end
end
- private
-
- def self.halting_and_conditional(next_callback, user_callback, user_conditions, halted_lambda, filter)
- lambda { |env|
+ def self.halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter)
+ callback_sequence.before do |env|
target = env.target
value = env.value
halted = env.halted
if !halted && user_conditions.all? { |c| c.call(target, value) }
- result = user_callback.call target, value
- env.halted = halted_lambda.call(target, result)
+ result_lambda = -> { user_callback.call target, value }
+ env.halted = halted_lambda.call(target, result_lambda)
if env.halted
target.send :halted_callback_hook, filter
end
end
- next_callback.call env
- }
+
+ env
+ end
end
+ private_class_method :halting_and_conditional
- def self.halting(next_callback, user_callback, halted_lambda, filter)
- lambda { |env|
+ def self.halting(callback_sequence, user_callback, halted_lambda, filter)
+ callback_sequence.before do |env|
target = env.target
value = env.value
halted = env.halted
unless halted
- result = user_callback.call target, value
- env.halted = halted_lambda.call(target, result)
+ result_lambda = -> { user_callback.call target, value }
+ env.halted = halted_lambda.call(target, result_lambda)
+
if env.halted
target.send :halted_callback_hook, filter
end
end
- next_callback.call env
- }
+
+ env
+ end
end
+ private_class_method :halting
- def self.conditional(next_callback, user_callback, user_conditions)
- lambda { |env|
+ def self.conditional(callback_sequence, user_callback, user_conditions)
+ callback_sequence.before do |env|
target = env.target
value = env.value
if user_conditions.all? { |c| c.call(target, value) }
user_callback.call target, value
end
- next_callback.call env
- }
+
+ env
+ end
end
+ private_class_method :conditional
- def self.simple(next_callback, user_callback)
- lambda { |env|
+ def self.simple(callback_sequence, user_callback)
+ callback_sequence.before do |env|
user_callback.call env.target, env.value
- next_callback.call env
- }
+
+ env
+ end
end
+ private_class_method :simple
end
class After
- def self.build(next_callback, user_callback, user_conditions, chain_config)
+ def self.build(callback_sequence, user_callback, user_conditions, chain_config)
if chain_config[:skip_after_callbacks_if_terminated]
if chain_config.key?(:terminator) && user_conditions.any?
- halting_and_conditional(next_callback, user_callback, user_conditions)
+ halting_and_conditional(callback_sequence, user_callback, user_conditions)
elsif chain_config.key?(:terminator)
- halting(next_callback, user_callback)
+ halting(callback_sequence, user_callback)
elsif user_conditions.any?
- conditional next_callback, user_callback, user_conditions
+ conditional callback_sequence, user_callback, user_conditions
else
- simple next_callback, user_callback
+ simple callback_sequence, user_callback
end
else
if user_conditions.any?
- conditional next_callback, user_callback, user_conditions
+ conditional callback_sequence, user_callback, user_conditions
else
- simple next_callback, user_callback
+ simple callback_sequence, user_callback
end
end
end
- private
-
- def self.halting_and_conditional(next_callback, user_callback, user_conditions)
- lambda { |env|
- env = next_callback.call env
+ def self.halting_and_conditional(callback_sequence, user_callback, user_conditions)
+ callback_sequence.after do |env|
target = env.target
value = env.value
halted = env.halted
@@ -220,118 +229,126 @@ module ActiveSupport
if !halted && user_conditions.all? { |c| c.call(target, value) }
user_callback.call target, value
end
+
env
- }
+ end
end
+ private_class_method :halting_and_conditional
- def self.halting(next_callback, user_callback)
- lambda { |env|
- env = next_callback.call env
+ def self.halting(callback_sequence, user_callback)
+ callback_sequence.after do |env|
unless env.halted
user_callback.call env.target, env.value
end
+
env
- }
+ end
end
+ private_class_method :halting
- def self.conditional(next_callback, user_callback, user_conditions)
- lambda { |env|
- env = next_callback.call env
+ def self.conditional(callback_sequence, user_callback, user_conditions)
+ callback_sequence.after do |env|
target = env.target
value = env.value
if user_conditions.all? { |c| c.call(target, value) }
user_callback.call target, value
end
+
env
- }
+ end
end
+ private_class_method :conditional
- def self.simple(next_callback, user_callback)
- lambda { |env|
- env = next_callback.call env
+ def self.simple(callback_sequence, user_callback)
+ callback_sequence.after do |env|
user_callback.call env.target, env.value
+
env
- }
+ end
end
+ private_class_method :simple
end
class Around
- def self.build(next_callback, user_callback, user_conditions, chain_config)
+ def self.build(callback_sequence, user_callback, user_conditions, chain_config)
if chain_config.key?(:terminator) && user_conditions.any?
- halting_and_conditional(next_callback, user_callback, user_conditions)
+ halting_and_conditional(callback_sequence, user_callback, user_conditions)
elsif chain_config.key? :terminator
- halting(next_callback, user_callback)
+ halting(callback_sequence, user_callback)
elsif user_conditions.any?
- conditional(next_callback, user_callback, user_conditions)
+ conditional(callback_sequence, user_callback, user_conditions)
else
- simple(next_callback, user_callback)
+ simple(callback_sequence, user_callback)
end
end
- private
-
- def self.halting_and_conditional(next_callback, user_callback, user_conditions)
- lambda { |env|
+ def self.halting_and_conditional(callback_sequence, user_callback, user_conditions)
+ callback_sequence.around do |env, &run|
target = env.target
value = env.value
halted = env.halted
if !halted && user_conditions.all? { |c| c.call(target, value) }
user_callback.call(target, value) {
- env = next_callback.call env
+ env = run.call env
env.value
}
+
env
else
- next_callback.call env
+ run.call env
end
- }
+ end
end
+ private_class_method :halting_and_conditional
- def self.halting(next_callback, user_callback)
- lambda { |env|
+ def self.halting(callback_sequence, user_callback)
+ callback_sequence.around do |env, &run|
target = env.target
value = env.value
- unless env.halted
+ if env.halted
+ run.call env
+ else
user_callback.call(target, value) {
- env = next_callback.call env
+ env = run.call env
env.value
}
env
- else
- next_callback.call env
end
- }
+ end
end
+ private_class_method :halting
- def self.conditional(next_callback, user_callback, user_conditions)
- lambda { |env|
+ def self.conditional(callback_sequence, user_callback, user_conditions)
+ callback_sequence.around do |env, &run|
target = env.target
value = env.value
if user_conditions.all? { |c| c.call(target, value) }
user_callback.call(target, value) {
- env = next_callback.call env
+ env = run.call env
env.value
}
env
else
- next_callback.call env
+ run.call env
end
- }
+ end
end
+ private_class_method :conditional
- def self.simple(next_callback, user_callback)
- lambda { |env|
+ def self.simple(callback_sequence, user_callback)
+ callback_sequence.around do |env, &run|
user_callback.call(env.target, env.value) {
- env = next_callback.call env
+ env = run.call env
env.value
}
env
- }
+ end
end
+ private_class_method :simple
end
end
@@ -356,14 +373,14 @@ module ActiveSupport
def filter; @key; end
def raw_filter; @filter; end
- def merge(chain, new_options)
+ def merge_conditional_options(chain, if_option:, unless_option:)
options = {
:if => @if.dup,
:unless => @unless.dup
}
- options[:if].concat Array(new_options.fetch(:unless, []))
- options[:unless].concat Array(new_options.fetch(:if, []))
+ options[:if].concat Array(unless_option)
+ options[:unless].concat Array(if_option)
self.class.build chain, @filter, @kind, options
end
@@ -382,17 +399,17 @@ module ActiveSupport
end
# Wraps code with filter
- def apply(next_callback)
+ def apply(callback_sequence)
user_conditions = conditions_lambdas
user_callback = make_lambda @filter
case kind
when :before
- Filters::Before.build(next_callback, user_callback, user_conditions, chain_config, @filter)
+ Filters::Before.build(callback_sequence, user_callback, user_conditions, chain_config, @filter)
when :after
- Filters::After.build(next_callback, user_callback, user_conditions, chain_config)
+ Filters::After.build(callback_sequence, user_callback, user_conditions, chain_config)
when :around
- Filters::Around.build(next_callback, user_callback, user_conditions, chain_config)
+ Filters::Around.build(callback_sequence, user_callback, user_conditions, chain_config)
end
end
@@ -409,15 +426,8 @@ module ActiveSupport
# Procs:: A proc to call with the object.
# Objects:: An object with a <tt>before_foo</tt> method on it to call.
#
- # All of these objects are compiled into methods and handled
- # the same after this point:
- #
- # Symbols:: Already methods.
- # Strings:: class_eval'd into methods.
- # Procs:: using define_method compiled into methods.
- # Objects::
- # a method is created that calls the before_foo method
- # on the object.
+ # All of these objects are converted into a lambda and handled
+ # the same after this point.
def make_lambda(filter)
case filter
when Symbol
@@ -464,16 +474,59 @@ module ActiveSupport
end
end
+ # Execute before and after filters in a sequence instead of
+ # chaining them with nested lambda calls, see:
+ # https://github.com/rails/rails/issues/18011
+ class CallbackSequence
+ def initialize(&call)
+ @call = call
+ @before = []
+ @after = []
+ end
+
+ def before(&before)
+ @before.unshift(before)
+ self
+ end
+
+ def after(&after)
+ @after.push(after)
+ self
+ end
+
+ def around(&around)
+ CallbackSequence.new do |*args|
+ around.call(*args) {
+ self.call(*args)
+ }
+ end
+ end
+
+ def call(*args)
+ @before.each { |b| b.call(*args) }
+ value = @call.call(*args)
+ @after.each { |a| a.call(*args) }
+ value
+ end
+ end
+
# An Array with a compile method.
class CallbackChain #:nodoc:#
include Enumerable
attr_reader :name, :config
+ # If true, any callback returning +false+ will halt the entire callback
+ # chain and display a deprecation message. If false, callback chains will
+ # only be halted by calling +throw :abort+. Defaults to +true+.
+ class_attribute :halt_and_display_warning_on_return_false
+ self.halt_and_display_warning_on_return_false = true
+
def initialize(name, config)
@name = name
@config = {
- :scope => [ :kind ]
+ scope: [:kind],
+ terminator: default_terminator
}.merge!(config)
@chain = []
@callbacks = nil
@@ -508,8 +561,9 @@ module ActiveSupport
def compile
@callbacks || @mutex.synchronize do
- @callbacks ||= @chain.reverse.inject(Filters::ENDING) do |chain, callback|
- callback.apply chain
+ final_sequence = CallbackSequence.new { |env| Filters::ENDING.call(env) }
+ @callbacks ||= @chain.reverse.inject(final_sequence) do |callback_sequence, callback|
+ callback.apply callback_sequence
end
end
end
@@ -543,6 +597,28 @@ module ActiveSupport
@callbacks = nil
@chain.delete_if { |c| callback.duplicates?(c) }
end
+
+ def default_terminator
+ Proc.new do |target, result_lambda|
+ terminate = true
+ catch(:abort) do
+ result = result_lambda.call if result_lambda.is_a?(Proc)
+ if halt_and_display_warning_on_return_false && result == false
+ display_deprecation_warning_for_false_terminator
+ else
+ terminate = false
+ end
+ end
+ terminate
+ end
+ end
+
+ def display_deprecation_warning_for_false_terminator
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Returning `false` in a callback will not implicitly halt a callback chain in the next release of Rails.
+ To explicitly halt a callback chain, please use `throw :abort` instead.
+ MSG
+ end
end
module ClassMethods
@@ -556,7 +632,7 @@ module ActiveSupport
# This is used internally to append, prepend and skip callbacks to the
# CallbackChain.
def __update_callbacks(name) #:nodoc:
- ([self] + ActiveSupport::DescendantsTracker.descendants(self)).reverse.each do |target|
+ ([self] + ActiveSupport::DescendantsTracker.descendants(self)).reverse_each do |target|
chain = target.get_callbacks name
yield target, chain.dup
end
@@ -566,7 +642,7 @@ module ActiveSupport
#
# set_callback :save, :before, :before_meth
# set_callback :save, :after, :after_meth, if: :condition
- # set_callback :save, :around, ->(r, &block) { stuff; result = block.call; stuff }
+ # set_callback :save, :around, ->(r, block) { stuff; result = block.call; stuff }
#
# The second arguments indicates whether the callback is to be run +:before+,
# +:after+, or +:around+ the event. If omitted, +:before+ is assumed. This
@@ -574,10 +650,10 @@ module ActiveSupport
#
# set_callback :save, :before_meth
#
- # The callback can specified as a symbol naming an instance method; as a
+ # The callback can be specified as a symbol naming an instance method; as a
# proc, lambda, or block; as a string to be instance evaluated; or as an
# object that responds to a certain method determined by the <tt>:scope</tt>
- # argument to +define_callback+.
+ # argument to +define_callbacks+.
#
# If a proc, lambda, or block is given, its body is evaluated in the context
# of the current object. It can also optionally accept the current object as
@@ -625,7 +701,7 @@ module ActiveSupport
filter = chain.find {|c| c.matches?(type, filter) }
if filter && options.any?
- new_filter = filter.merge(chain, options)
+ new_filter = filter.merge_conditional_options(chain, if_option: options[:if], unless_option: options[:unless])
chain.insert(chain.index(filter), new_filter)
end
@@ -648,7 +724,7 @@ module ActiveSupport
self.set_callbacks name, callbacks.dup.clear
end
- # Define sets of events in the object lifecycle that support callbacks.
+ # Define sets of events in the object life cycle that support callbacks.
#
# define_callbacks :validate
# define_callbacks :initialize, :save, :destroy
@@ -656,16 +732,17 @@ module ActiveSupport
# ===== Options
#
# * <tt>:terminator</tt> - Determines when a before filter will halt the
- # callback chain, preventing following callbacks from being called and
- # the event from being triggered. This should be a lambda to be executed.
+ # callback chain, preventing following before and around callbacks from
+ # being called and the event from being triggered.
+ # This should be a lambda to be executed.
# The current object and the return result of the callback will be called
# with the lambda.
#
# define_callbacks :validate, terminator: ->(target, result) { result == false }
#
# In this example, if any before validate callbacks returns +false+,
- # other callbacks are not executed. Defaults to +false+, meaning no value
- # halts the chain.
+ # any successive before and around callback is not executed.
+ # Defaults to +false+, meaning no value halts the chain.
#
# * <tt>:skip_after_callbacks_if_terminated</tt> - Determines if after
# callbacks should be terminated by the <tt>:terminator</tt> option. By
@@ -716,18 +793,21 @@ module ActiveSupport
# define_callbacks :save, scope: [:name]
#
# would call <tt>Audit#save</tt>.
+ #
+ # NOTE: +method_name+ passed to `define_model_callbacks` must not end with
+ # `!`, `?` or `=`.
def define_callbacks(*names)
options = names.extract_options!
- if options.key?(:terminator) && String === options[:terminator]
- ActiveSupport::Deprecation.warn "String based terminators are deprecated, please use a lambda"
- value = options[:terminator]
- line = class_eval "lambda { |result| #{value} }", __FILE__, __LINE__
- options[:terminator] = lambda { |target, result| target.instance_exec(result, &line) }
- end
names.each do |name|
class_attribute "_#{name}_callbacks"
set_callbacks name, CallbackChain.new(name, options)
+
+ module_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def _run_#{name}_callbacks(&block)
+ _run_callbacks(_#{name}_callbacks, &block)
+ end
+ RUBY
end
end
diff --git a/activesupport/lib/active_support/concern.rb b/activesupport/lib/active_support/concern.rb
index b796d01dfd..4082d2d464 100644
--- a/activesupport/lib/active_support/concern.rb
+++ b/activesupport/lib/active_support/concern.rb
@@ -26,7 +26,7 @@ module ActiveSupport
# scope :disabled, -> { where(disabled: true) }
# end
#
- # module ClassMethods
+ # class_methods do
# ...
# end
# end
@@ -95,7 +95,7 @@ module ActiveSupport
# end
#
# class Host
- # include Bar # works, Bar takes care now of its dependencies
+ # include Bar # It works, now Bar takes care of its dependencies
# end
module Concern
class MultipleIncludedBlocks < StandardError #:nodoc:
@@ -114,7 +114,7 @@ module ActiveSupport
return false
else
return false if base < self
- @_dependencies.each { |dep| base.send(:include, dep) }
+ @_dependencies.each { |dep| base.include(dep) }
super
base.extend const_get(:ClassMethods) if const_defined?(:ClassMethods)
base.class_eval(&@_included_block) if instance_variable_defined?(:@_included_block)
@@ -130,5 +130,13 @@ module ActiveSupport
super
end
end
+
+ def class_methods(&class_methods_module_definition)
+ mod = const_defined?(:ClassMethods) ?
+ const_get(:ClassMethods) :
+ const_set(:ClassMethods, Module.new)
+
+ mod.module_eval(&class_methods_module_definition)
+ end
end
end
diff --git a/activesupport/lib/active_support/configurable.rb b/activesupport/lib/active_support/configurable.rb
index e0d39d509f..8256c325af 100644
--- a/activesupport/lib/active_support/configurable.rb
+++ b/activesupport/lib/active_support/configurable.rb
@@ -107,7 +107,7 @@ module ActiveSupport
options = names.extract_options!
names.each do |name|
- raise NameError.new('invalid config attribute name') unless name =~ /^[_A-Za-z]\w*$/
+ raise NameError.new('invalid config attribute name') unless name =~ /\A[_A-Za-z]\w*\z/
reader, reader_line = "def #{name}; config.#{name}; end", __LINE__
writer, writer_line = "def #{name}=(value); config.#{name} = value; end", __LINE__
@@ -122,6 +122,7 @@ module ActiveSupport
send("#{name}=", yield) if block_given?
end
end
+ private :config_accessor
end
# Reads and writes attributes from a configuration <tt>OrderedHash</tt>.
diff --git a/activesupport/lib/active_support/core_ext.rb b/activesupport/lib/active_support/core_ext.rb
index 199aa91020..52706c3d7a 100644
--- a/activesupport/lib/active_support/core_ext.rb
+++ b/activesupport/lib/active_support/core_ext.rb
@@ -1,3 +1,4 @@
-Dir["#{File.dirname(__FILE__)}/core_ext/*.rb"].each do |path|
+DEPRECATED_FILES = ["#{File.dirname(__FILE__)}/core_ext/struct.rb"]
+(Dir["#{File.dirname(__FILE__)}/core_ext/*.rb"] - DEPRECATED_FILES).each do |path|
require path
end
diff --git a/activesupport/lib/active_support/core_ext/array/access.rb b/activesupport/lib/active_support/core_ext/array/access.rb
index 4f1e432b61..ca66d806ef 100644
--- a/activesupport/lib/active_support/core_ext/array/access.rb
+++ b/activesupport/lib/active_support/core_ext/array/access.rb
@@ -5,6 +5,8 @@ class Array
# %w( a b c d ).from(2) # => ["c", "d"]
# %w( a b c d ).from(10) # => []
# %w().from(0) # => []
+ # %w( a b c d ).from(-2) # => ["c", "d"]
+ # %w( a b c ).from(-10) # => []
def from(position)
self[position, length] || []
end
@@ -15,8 +17,14 @@ class Array
# %w( a b c d ).to(2) # => ["a", "b", "c"]
# %w( a b c d ).to(10) # => ["a", "b", "c", "d"]
# %w().to(0) # => []
+ # %w( a b c d ).to(-2) # => ["a", "b", "c"]
+ # %w( a b c ).to(-10) # => []
def to(position)
- first position + 1
+ if position >= 0
+ take position + 1
+ else
+ self[0..position]
+ end
end
# Equal to <tt>self[1]</tt>.
@@ -48,6 +56,8 @@ class Array
end
# Equal to <tt>self[41]</tt>. Also known as accessing "the reddit".
+ #
+ # (1..42).to_a.forty_two # => 42
def forty_two
self[41]
end
diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb
index 76ffd23ed1..080e3b5ef7 100644
--- a/activesupport/lib/active_support/core_ext/array/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/array/conversions.rb
@@ -92,7 +92,7 @@ class Array
if empty?
'null'
else
- collect { |element| element.id }.join(',')
+ collect(&:id).join(',')
end
else
to_default_s
diff --git a/activesupport/lib/active_support/core_ext/array/grouping.rb b/activesupport/lib/active_support/core_ext/array/grouping.rb
index 37f007c751..87ae052eb0 100644
--- a/activesupport/lib/active_support/core_ext/array/grouping.rb
+++ b/activesupport/lib/active_support/core_ext/array/grouping.rb
@@ -18,6 +18,11 @@ class Array
# ["3", "4"]
# ["5"]
def in_groups_of(number, fill_with = nil)
+ if number.to_i <= 0
+ raise ArgumentError,
+ "Group size must be a positive integer, was #{number.inspect}"
+ end
+
if fill_with == false
collection = self
else
@@ -53,7 +58,7 @@ class Array
# ["4", "5"]
# ["6", "7"]
def in_groups(number, fill_with = nil)
- # size / number gives minor group size;
+ # size.div number gives minor group size;
# size % number gives how many objects need extra accommodation;
# each group hold either division or division + 1 items.
division = size.div number
@@ -83,10 +88,10 @@ class Array
#
# [1, 2, 3, 4, 5].split(3) # => [[1, 2], [4, 5]]
# (1..10).to_a.split { |i| i % 3 == 0 } # => [[1, 2], [4, 5], [7, 8], [10]]
- def split(value = nil, &block)
- if block
+ def split(value = nil)
+ if block_given?
inject([[]]) do |results, element|
- if block.call(element)
+ if yield(element)
results << []
else
results.last << element
@@ -95,9 +100,9 @@ class Array
results
end
else
- results, arr = [[]], self
+ results, arr = [[]], self.dup
until arr.empty?
- if (idx = index(value))
+ if (idx = arr.index(value))
results.last.concat(arr.shift(idx))
arr.shift
results << []
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 27718f19d4..f8d48b69df 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,7 @@
class Array
- # The human way of thinking about adding stuff to the end of a list is with append
+ # The human way of thinking about adding stuff to the end of a list is with append.
alias_method :append, :<<
- # The human way of thinking about adding stuff to the beginning of a list is with prepend
+ # The human way of thinking about adding stuff to the beginning of a list is with prepend.
alias_method :prepend, :unshift
end \ No newline at end of file
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 39b8cea807..234283e792 100644
--- a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
@@ -1,31 +1,15 @@
require 'bigdecimal'
require 'bigdecimal/util'
-require 'yaml'
class BigDecimal
- YAML_MAPPING = { 'Infinity' => '.Inf', '-Infinity' => '-.Inf', 'NaN' => '.NaN' }
-
- def encode_with(coder)
- string = to_s
- coder.represent_scalar(nil, YAML_MAPPING[string] || string)
- end
-
- # Backport this method if it doesn't exist
- unless method_defined?(:to_d)
- def to_d
- self
- end
- end
-
DEFAULT_STRING_FORMAT = 'F'
- def to_formatted_s(*args)
- if args[0].is_a?(Symbol)
- super
+ alias_method :to_default_s, :to_s
+
+ def to_s(format = nil, options = nil)
+ if format.is_a?(Symbol)
+ to_formatted_s(format, options || {})
else
- format = args[0] || DEFAULT_STRING_FORMAT
- _original_to_s(format)
+ to_default_s(format || DEFAULT_STRING_FORMAT)
end
end
- alias_method :_original_to_s, :to_s
- alias_method :to_s, :to_formatted_s
end
diff --git a/activesupport/lib/active_support/core_ext/class.rb b/activesupport/lib/active_support/core_ext/class.rb
index 86b752c2f3..c750a10bb2 100644
--- a/activesupport/lib/active_support/core_ext/class.rb
+++ b/activesupport/lib/active_support/core_ext/class.rb
@@ -1,4 +1,3 @@
require 'active_support/core_ext/class/attribute'
-require 'active_support/core_ext/class/attribute_accessors'
require 'active_support/core_ext/class/delegating_attributes'
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 83038f9da5..f2b7bb3ef1 100644
--- a/activesupport/lib/active_support/core_ext/class/attribute.rb
+++ b/activesupport/lib/active_support/core_ext/class/attribute.rb
@@ -116,9 +116,4 @@ class Class
attr_writer name if instance_writer
end
end
-
- private
- def singleton_class?
- ancestors.first != self
- 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 34859617c9..84d5e95e7a 100644
--- a/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb
+++ b/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb
@@ -1,170 +1,4 @@
-require 'active_support/core_ext/array/extract_options'
-
-# Extends the class object with class and instance accessors for class attributes,
-# just like the native attr* accessors for instance attributes.
-class Class
- # Defines a class attribute if it's not defined and creates a reader method that
- # returns the attribute value.
- #
- # class Person
- # cattr_reader :hair_colors
- # end
- #
- # Person.class_variable_set("@@hair_colors", [:brown, :black])
- # Person.hair_colors # => [:brown, :black]
- # Person.new.hair_colors # => [:brown, :black]
- #
- # The attribute name must be a valid method name in Ruby.
- #
- # class Person
- # cattr_reader :"1_Badname "
- # end
- # # => NameError: invalid attribute name
- #
- # If you want to opt out the instance reader method, you can pass <tt>instance_reader: false</tt>
- # or <tt>instance_accessor: false</tt>.
- #
- # class Person
- # cattr_reader :hair_colors, instance_reader: false
- # end
- #
- # Person.new.hair_colors # => NoMethodError
- def cattr_reader(*syms)
- options = syms.extract_options!
- syms.each do |sym|
- raise NameError.new("invalid class attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/
- class_eval(<<-EOS, __FILE__, __LINE__ + 1)
- unless defined? @@#{sym}
- @@#{sym} = nil
- end
-
- def self.#{sym}
- @@#{sym}
- end
- EOS
-
- unless options[:instance_reader] == false || options[:instance_accessor] == false
- class_eval(<<-EOS, __FILE__, __LINE__ + 1)
- def #{sym}
- @@#{sym}
- end
- EOS
- end
- end
- end
-
- # Defines a class attribute if it's not defined and creates a writer method to allow
- # assignment to the attribute.
- #
- # class Person
- # cattr_writer :hair_colors
- # end
- #
- # Person.hair_colors = [:brown, :black]
- # Person.class_variable_get("@@hair_colors") # => [:brown, :black]
- # Person.new.hair_colors = [:blonde, :red]
- # Person.class_variable_get("@@hair_colors") # => [:blonde, :red]
- #
- # The attribute name must be a valid method name in Ruby.
- #
- # class Person
- # cattr_writer :"1_Badname "
- # end
- # # => NameError: invalid attribute name
- #
- # If you want to opt out the instance writer method, pass <tt>instance_writer: false</tt>
- # or <tt>instance_accessor: false</tt>.
- #
- # class Person
- # cattr_writer :hair_colors, instance_writer: false
- # end
- #
- # Person.new.hair_colors = [:blonde, :red] # => NoMethodError
- #
- # Also, you can pass a block to set up the attribute with a default value.
- #
- # class Person
- # cattr_writer :hair_colors do
- # [:brown, :black, :blonde, :red]
- # end
- # end
- #
- # Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red]
- def cattr_writer(*syms)
- options = syms.extract_options!
- syms.each do |sym|
- raise NameError.new("invalid class attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/
- class_eval(<<-EOS, __FILE__, __LINE__ + 1)
- unless defined? @@#{sym}
- @@#{sym} = nil
- end
-
- def self.#{sym}=(obj)
- @@#{sym} = obj
- end
- EOS
-
- unless options[:instance_writer] == false || options[:instance_accessor] == false
- class_eval(<<-EOS, __FILE__, __LINE__ + 1)
- def #{sym}=(obj)
- @@#{sym} = obj
- end
- EOS
- end
- send("#{sym}=", yield) if block_given?
- end
- end
-
- # Defines both class and instance accessors for class attributes.
- #
- # class Person
- # cattr_accessor :hair_colors
- # end
- #
- # Person.hair_colors = [:brown, :black, :blonde, :red]
- # Person.hair_colors # => [:brown, :black, :blonde, :red]
- # Person.new.hair_colors # => [:brown, :black, :blonde, :red]
- #
- # If a subclass changes the value then that would also change the value for
- # parent class. Similarly if parent class changes the value then that would
- # change the value of subclasses too.
- #
- # class Male < Person
- # end
- #
- # Male.hair_colors << :blue
- # Person.hair_colors # => [:brown, :black, :blonde, :red, :blue]
- #
- # To opt out of the instance writer method, pass <tt>instance_writer: false</tt>.
- # To opt out of the instance reader method, pass <tt>instance_reader: false</tt>.
- #
- # class Person
- # cattr_accessor :hair_colors, instance_writer: false, instance_reader: false
- # end
- #
- # Person.new.hair_colors = [:brown] # => NoMethodError
- # Person.new.hair_colors # => NoMethodError
- #
- # Or pass <tt>instance_accessor: false</tt>, to opt out both instance methods.
- #
- # class Person
- # cattr_accessor :hair_colors, instance_accessor: false
- # end
- #
- # 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.
- #
- # class Person
- # cattr_accessor :hair_colors do
- # [:brown, :black, :blonde, :red]
- # end
- # end
- #
- # Person.class_variable_get("@@hair_colors") #=> [:brown, :black, :blonde, :red]
- def cattr_accessor(*syms, &blk)
- cattr_reader(*syms)
- cattr_writer(*syms, &blk)
- end
-end
+# 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.
+require 'active_support/core_ext/module/attribute_accessors'
diff --git a/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb b/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb
index ff870f5fd1..1c305c5970 100644
--- a/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb
+++ b/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb
@@ -1,25 +1,30 @@
require 'active_support/core_ext/kernel/singleton_class'
require 'active_support/core_ext/module/remove_method'
+require 'active_support/core_ext/module/deprecation'
+
class Class
def superclass_delegating_accessor(name, options = {})
# Create private _name and _name= methods that can still be used if the public
- # methods are overridden. This allows
- _superclass_delegating_accessor("_#{name}")
+ # methods are overridden.
+ _superclass_delegating_accessor("_#{name}", options)
- # Generate the public methods name, name=, and name?
+ # Generate the public methods name, name=, and name?.
# These methods dispatch to the private _name, and _name= methods, making them
- # overridable
+ # overridable.
singleton_class.send(:define_method, name) { send("_#{name}") }
singleton_class.send(:define_method, "#{name}?") { !!send("_#{name}") }
singleton_class.send(:define_method, "#{name}=") { |value| send("_#{name}=", value) }
- # If an instance_reader is needed, generate methods for name and name= on the
- # class itself, so instances will be able to see them
- define_method(name) { send("_#{name}") } if options[:instance_reader] != false
- define_method("#{name}?") { !!send("#{name}") } if options[:instance_reader] != false
+ # If an instance_reader is needed, generate public instance methods name and name?.
+ if options[:instance_reader] != false
+ define_method(name) { send("_#{name}") }
+ define_method("#{name}?") { !!send("#{name}") }
+ end
end
+ deprecate superclass_delegating_accessor: :class_attribute
+
private
# Take the object being set and store it in a method. This gives us automatic
# inheritance behavior, without having to store the object in an instance
diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb
index af048d0c85..c60e833441 100644
--- a/activesupport/lib/active_support/core_ext/date/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date/calculations.rb
@@ -129,7 +129,7 @@ class Date
options.fetch(:day, day)
)
end
-
+
# Allow Date to be compared with Time by converting to DateTime and relying on the <=> from there.
def compare_with_coercion(other)
if other.is_a?(Time)
diff --git a/activesupport/lib/active_support/core_ext/date/conversions.rb b/activesupport/lib/active_support/core_ext/date/conversions.rb
index 6bc8f12176..df419a6e63 100644
--- a/activesupport/lib/active_support/core_ext/date/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/date/conversions.rb
@@ -1,6 +1,7 @@
require 'date'
require 'active_support/inflector/methods'
require 'active_support/core_ext/date/zones'
+require 'active_support/core_ext/module/remove_method'
class Date
DATE_FORMATS = {
@@ -19,8 +20,10 @@ class Date
# 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.
- remove_method :xmlschema
+ # 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.
#
@@ -37,12 +40,12 @@ class Date
# date.to_formatted_s(:rfc822) # => "10 Nov 2007"
# date.to_formatted_s(:iso8601) # => "2007-11-10"
#
- # == Adding your own time formats to to_formatted_s
+ # == Adding your own date formats to to_formatted_s
# You can add your own formats to the Date::DATE_FORMATS hash.
# Use the format name as the hash key and either a strftime string
# or Proc instance that takes a date argument as the value.
#
- # # config/initializers/time_formats.rb
+ # # config/initializers/date_formats.rb
# Date::DATE_FORMATS[:month_and_year] = '%B %Y'
# Date::DATE_FORMATS[:short_ordinal] = ->(date) { date.strftime("%B #{date.day.ordinalize}") }
def to_formatted_s(format = :default)
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 c869a0e210..9525c10112 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
@@ -9,15 +9,26 @@ module DateAndTime
:saturday => 5,
:sunday => 6
}
+ WEEKEND_DAYS = [ 6, 0 ]
# Returns a new date/time representing yesterday.
def yesterday
- advance(:days => -1)
+ advance(days: -1)
+ end
+
+ # Returns a new date/time representing the previous day.
+ def prev_day
+ advance(days: -1)
end
# Returns a new date/time representing tomorrow.
def tomorrow
- advance(:days => 1)
+ advance(days: 1)
+ end
+
+ # Returns a new date/time representing the next day.
+ def next_day
+ advance(days: 1)
end
# Returns true if the date/time is today.
@@ -35,6 +46,11 @@ module DateAndTime
self > self.class.current
end
+ # Returns true if the date/time falls on a Saturday or Sunday.
+ def on_weekend?
+ WEEKEND_DAYS.include?(wday)
+ end
+
# Returns a new date/time the specified number of days ago.
def days_ago(days)
advance(:days => -days)
@@ -111,9 +127,19 @@ module DateAndTime
# Returns a new date/time representing the given day in the next week.
# The +given_day_in_next_week+ defaults to the beginning of the week
# which is determined by +Date.beginning_of_week+ or +config.beginning_of_week+
- # when set. +DateTime+ objects have their time set to 0:00.
- def next_week(given_day_in_next_week = Date.beginning_of_week)
- first_hour(weeks_since(1).beginning_of_week.days_since(days_span(given_day_in_next_week)))
+ # when set. +DateTime+ objects have their time set to 0:00 unless +same_time+ is true.
+ def next_week(given_day_in_next_week = Date.beginning_of_week, same_time: false)
+ result = first_hour(weeks_since(1).beginning_of_week.days_since(days_span(given_day_in_next_week)))
+ same_time ? copy_time_to(result) : result
+ end
+
+ # Returns a new date/time representing the next weekday.
+ def next_weekday
+ if next_day.on_weekend?
+ next_week(:monday, same_time: true)
+ else
+ next_day
+ end
end
# Short-hand for months_since(1).
@@ -134,12 +160,23 @@ module DateAndTime
# Returns a new date/time representing the given day in the previous week.
# Week is assumed to start on +start_day+, default is
# +Date.beginning_of_week+ or +config.beginning_of_week+ when set.
- # DateTime objects have their time set to 0:00.
- def prev_week(start_day = Date.beginning_of_week)
- first_hour(weeks_ago(1).beginning_of_week.days_since(days_span(start_day)))
+ # DateTime objects have their time set to 0:00 unless +same_time+ is true.
+ def prev_week(start_day = Date.beginning_of_week, same_time: false)
+ result = first_hour(weeks_ago(1).beginning_of_week.days_since(days_span(start_day)))
+ same_time ? copy_time_to(result) : result
end
alias_method :last_week, :prev_week
+ # Returns a new date/time representing the previous weekday.
+ def prev_weekday
+ if prev_day.on_weekend?
+ copy_time_to(beginning_of_week(:friday))
+ else
+ prev_day
+ end
+ end
+ alias_method :last_weekday, :prev_weekday
+
# Short-hand for months_ago(1).
def prev_month
months_ago(1)
@@ -213,18 +250,42 @@ module DateAndTime
end
alias :at_end_of_year :end_of_year
- private
+ # Returns a Range representing the whole week of the current date/time.
+ # Week starts on start_day, default is <tt>Date.week_start</tt> or <tt>config.week_start</tt> when set.
+ def all_week(start_day = Date.beginning_of_week)
+ beginning_of_week(start_day)..end_of_week(start_day)
+ end
- def first_hour(date_or_time)
- date_or_time.acts_like?(:time) ? date_or_time.beginning_of_day : date_or_time
+ # Returns a Range representing the whole month of the current date/time.
+ def all_month
+ beginning_of_month..end_of_month
end
- def last_hour(date_or_time)
- date_or_time.acts_like?(:time) ? date_or_time.end_of_day : date_or_time
+ # Returns a Range representing the whole quarter of the current date/time.
+ def all_quarter
+ beginning_of_quarter..end_of_quarter
end
- def days_span(day)
- (DAYS_INTO_WEEK[day] - DAYS_INTO_WEEK[Date.beginning_of_week]) % 7
+ # Returns a Range representing the whole year of the current date/time.
+ def all_year
+ beginning_of_year..end_of_year
end
+
+ private
+ def first_hour(date_or_time)
+ date_or_time.acts_like?(:time) ? date_or_time.beginning_of_day : date_or_time
+ end
+
+ def last_hour(date_or_time)
+ date_or_time.acts_like?(:time) ? date_or_time.end_of_day : date_or_time
+ end
+
+ def days_span(day)
+ (DAYS_INTO_WEEK[day] - DAYS_INTO_WEEK[Date.beginning_of_week]) % 7
+ end
+
+ def copy_time_to(other)
+ other.change(hour: hour, min: min, sec: sec, usec: try(:usec))
+ end
end
end
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 8e5d723074..55ad384f4f 100644
--- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
@@ -10,7 +10,11 @@ class DateTime
end
end
- # Seconds since midnight: DateTime.now.seconds_since_midnight.
+ # Returns the number of seconds since 00:00:00.
+ #
+ # DateTime.new(2012, 8, 29, 0, 0, 0).seconds_since_midnight # => 0
+ # DateTime.new(2012, 8, 29, 12, 34, 56).seconds_since_midnight # => 45296
+ # DateTime.new(2012, 8, 29, 23, 59, 59).seconds_since_midnight # => 86399
def seconds_since_midnight
sec + (min * 60) + (hour * 3600)
end
@@ -53,6 +57,16 @@ class DateTime
# <tt>:months</tt>, <tt>:weeks</tt>, <tt>:days</tt>, <tt>:hours</tt>,
# <tt>:minutes</tt>, <tt>:seconds</tt>.
def advance(options)
+ unless options[:weeks].nil?
+ options[:weeks], partial_weeks = options[:weeks].divmod(1)
+ options[:days] = options.fetch(:days, 0) + 7 * partial_weeks
+ end
+
+ unless options[:days].nil?
+ options[:days], partial_days = options[:days].divmod(1)
+ options[:hours] = options.fetch(:hours, 0) + 24 * partial_days
+ end
+
d = to_date.advance(options)
datetime_advanced_by_date = change(:year => d.year, :month => d.month, :day => d.day)
seconds_to_advance = \
@@ -63,7 +77,7 @@ class DateTime
if seconds_to_advance.zero?
datetime_advanced_by_date
else
- datetime_advanced_by_date.since seconds_to_advance
+ datetime_advanced_by_date.since(seconds_to_advance)
end
end
@@ -151,7 +165,13 @@ class DateTime
# Layers additional behavior on DateTime#<=> so that Time and
# ActiveSupport::TimeWithZone instances can be compared with a DateTime.
def <=>(other)
- super other.to_datetime
+ if other.kind_of?(Infinity)
+ super
+ elsif other.respond_to? :to_datetime
+ super other.to_datetime
+ else
+ nil
+ end
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 6ddfb72a0d..2a9c09fc29 100644
--- a/activesupport/lib/active_support/core_ext/date_time/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/conversions.rb
@@ -71,9 +71,9 @@ class DateTime
civil(year, month, day, hour, min, sec, offset)
end
- # Converts +self+ to a floating-point number of seconds since the Unix epoch.
+ # Converts +self+ to a floating-point number of seconds, including fractional microseconds, since the Unix epoch.
def to_f
- seconds_since_unix_epoch.to_f
+ seconds_since_unix_epoch.to_f + sec_fraction
end
# Converts +self+ to an integer number of seconds since the Unix epoch.
diff --git a/activesupport/lib/active_support/core_ext/digest/uuid.rb b/activesupport/lib/active_support/core_ext/digest/uuid.rb
new file mode 100644
index 0000000000..593c51bba2
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/digest/uuid.rb
@@ -0,0 +1,51 @@
+require 'securerandom'
+
+module Digest
+ module UUID
+ DNS_NAMESPACE = "k\xA7\xB8\x10\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc:
+ URL_NAMESPACE = "k\xA7\xB8\x11\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc:
+ OID_NAMESPACE = "k\xA7\xB8\x12\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc:
+ X500_NAMESPACE = "k\xA7\xB8\x14\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc:
+
+ # Generates a v5 non-random UUID (Universally Unique IDentifier).
+ #
+ # 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
+ def self.uuid_from_hash(hash_class, uuid_namespace, name)
+ if hash_class == Digest::MD5
+ version = 3
+ elsif hash_class == Digest::SHA1
+ version = 5
+ else
+ raise ArgumentError, "Expected Digest::SHA1 or Digest::MD5, got #{hash_class.name}."
+ end
+
+ hash = hash_class.new
+ hash.update(uuid_namespace)
+ hash.update(name)
+
+ ary = hash.digest.unpack('NnnnnN')
+ ary[2] = (ary[2] & 0x0FFF) | (version << 12)
+ ary[3] = (ary[3] & 0x3FFF) | 0x8000
+
+ "%08x-%04x-%04x-%04x-%04x%08x" % ary
+ end
+
+ # Convenience method for uuid_from_hash using Digest::MD5.
+ def self.uuid_v3(uuid_namespace, name)
+ self.uuid_from_hash(Digest::MD5, uuid_namespace, name)
+ end
+
+ # Convenience method for uuid_from_hash using Digest::SHA1.
+ def self.uuid_v5(uuid_namespace, name)
+ self.uuid_from_hash(Digest::SHA1, uuid_namespace, name)
+ end
+
+ # Convenience method for SecureRandom.uuid.
+ def self.uuid_v4
+ SecureRandom.uuid
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb
index 4501b7ff58..1343beb87a 100644
--- a/activesupport/lib/active_support/core_ext/enumerable.rb
+++ b/activesupport/lib/active_support/core_ext/enumerable.rb
@@ -35,7 +35,7 @@ module Enumerable
if block_given?
Hash[map { |elem| [yield(elem), elem] }]
else
- to_enum :index_by
+ to_enum(:index_by) { size if respond_to?(:size) }
end
end
diff --git a/activesupport/lib/active_support/core_ext/file/atomic.rb b/activesupport/lib/active_support/core_ext/file/atomic.rb
index c3e6124a57..fad6fa8d9d 100644
--- a/activesupport/lib/active_support/core_ext/file/atomic.rb
+++ b/activesupport/lib/active_support/core_ext/file/atomic.rb
@@ -20,10 +20,10 @@ class File
temp_file = Tempfile.new(basename(file_name), temp_dir)
temp_file.binmode
- yield temp_file
+ return_val = yield temp_file
temp_file.close
- if File.exists?(file_name)
+ if File.exist?(file_name)
# Get original file permissions
old_stat = stat(file_name)
else
@@ -40,7 +40,10 @@ class File
chown(old_stat.uid, old_stat.gid, file_name)
# This operation will affect filesystem ACL's
chmod(old_stat.mode, file_name)
- rescue Errno::EPERM
+
+ # Make sure we return the result of the yielded block
+ return_val
+ rescue Errno::EPERM, Errno::EACCES
# Changing file ownership failed, moving on.
end
end
diff --git a/activesupport/lib/active_support/core_ext/hash.rb b/activesupport/lib/active_support/core_ext/hash.rb
index 686f12c6da..af4d1da0eb 100644
--- a/activesupport/lib/active_support/core_ext/hash.rb
+++ b/activesupport/lib/active_support/core_ext/hash.rb
@@ -1,3 +1,4 @@
+require 'active_support/core_ext/hash/compact'
require 'active_support/core_ext/hash/conversions'
require 'active_support/core_ext/hash/deep_merge'
require 'active_support/core_ext/hash/except'
@@ -5,3 +6,4 @@ require 'active_support/core_ext/hash/indifferent_access'
require 'active_support/core_ext/hash/keys'
require 'active_support/core_ext/hash/reverse_merge'
require 'active_support/core_ext/hash/slice'
+require 'active_support/core_ext/hash/transform_values'
diff --git a/activesupport/lib/active_support/core_ext/hash/compact.rb b/activesupport/lib/active_support/core_ext/hash/compact.rb
new file mode 100644
index 0000000000..5dc9a05ec7
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/hash/compact.rb
@@ -0,0 +1,20 @@
+class Hash
+ # Returns a hash with non +nil+ values.
+ #
+ # hash = { a: true, b: false, c: nil}
+ # hash.compact # => { a: true, b: false}
+ # hash # => { a: true, b: false, c: nil}
+ # { c: nil }.compact # => {}
+ def compact
+ self.select { |_, value| !value.nil? }
+ end
+
+ # Replaces current hash with non +nil+ values.
+ #
+ # hash = { a: true, b: false, c: nil}
+ # hash.compact! # => { a: true, b: false}
+ # hash # => { a: true, b: false}
+ def compact!
+ self.reject! { |_, value| value.nil? }
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb
index fbf2877117..2149d4439d 100644
--- a/activesupport/lib/active_support/core_ext/hash/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb
@@ -10,7 +10,7 @@ require 'active_support/core_ext/string/inflections'
class Hash
# Returns a string containing an XML representation of its receiver:
#
- # {'foo' => 1, 'bar' => 2}.to_xml
+ # { foo: 1, bar: 2 }.to_xml
# # =>
# # <?xml version="1.0" encoding="UTF-8"?>
# # <hash>
@@ -43,7 +43,10 @@ class Hash
# end
#
# { foo: Foo.new }.to_xml(skip_instruct: true)
- # # => "<hash><bar>fooing!</bar></hash>"
+ # # =>
+ # # <hash>
+ # # <bar>fooing!</bar>
+ # # </hash>
#
# * Otherwise, a node with +key+ as tag is created with a string representation of
# +value+ as text node. If +value+ is +nil+ an attribute "nil" set to "true" is added.
@@ -102,7 +105,7 @@ class Hash
# hash = Hash.from_xml(xml)
# # => {"hash"=>{"foo"=>1, "bar"=>2}}
#
- # DisallowedType is raise if the XML contains attributes with <tt>type="yaml"</tt> or
+ # +DisallowedType+ is raised if the XML contains attributes with <tt>type="yaml"</tt> or
# <tt>type="symbol"</tt>. Use <tt>Hash.from_trusted_xml</tt> to parse this XML.
def from_xml(xml, disallowed_types = nil)
ActiveSupport::XMLConverter.new(xml, disallowed_types).to_h
@@ -201,7 +204,7 @@ module ActiveSupport
end
def become_empty_string?(value)
- # {"string" => true}
+ # { "string" => true }
# No tests fail when the second term is removed.
value['type'] == 'string' && value['nil'] != 'true'
end
@@ -218,7 +221,7 @@ module ActiveSupport
def garbage?(value)
# If the type is the only element which makes it then
# this still makes the value nil, except if type is
- # a XML node(where type['value'] is a Hash)
+ # an XML node(where type['value'] is a Hash)
value['type'] && !value['type'].is_a?(::Hash) && value.size == 1
end
@@ -235,6 +238,6 @@ module ActiveSupport
value.map! { |i| deep_to_h(i) }
value.length > 1 ? value : value.first
end
+
end
end
-
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 e07db50b77..9c9faf67ea 100644
--- a/activesupport/lib/active_support/core_ext/hash/deep_merge.rb
+++ b/activesupport/lib/active_support/core_ext/hash/deep_merge.rb
@@ -1,27 +1,38 @@
class Hash
# Returns a new hash with +self+ and +other_hash+ merged recursively.
#
- # h1 = { x: { y: [4,5,6] }, z: [7,8,9] }
- # h2 = { x: { y: [7,8,9] }, z: 'xyz' }
+ # h1 = { a: true, b: { c: [1, 2, 3] } }
+ # h2 = { a: false, b: { x: [3, 4, 5] } }
#
- # h1.deep_merge(h2) #=> {x: {y: [7, 8, 9]}, z: "xyz"}
- # h2.deep_merge(h1) #=> {x: {y: [4, 5, 6]}, z: [7, 8, 9]}
- # h1.deep_merge(h2) { |key, old, new| Array.wrap(old) + Array.wrap(new) }
- # #=> {:x=>{:y=>[4, 5, 6, 7, 8, 9]}, :z=>[7, 8, 9, "xyz"]}
+ # h1.deep_merge(h2) # => { a: false, b: { c: [1, 2, 3], x: [3, 4, 5] } }
+ #
+ # Like with Hash#merge in the standard library, a block can be provided
+ # to merge values:
+ #
+ # h1 = { a: 100, b: 200, c: { c1: 100 } }
+ # h2 = { b: 250, c: { c1: 200 } }
+ # h1.deep_merge(h2) { |key, this_val, other_val| this_val + other_val }
+ # # => { a: 100, b: 450, c: { c1: 300 } }
def deep_merge(other_hash, &block)
dup.deep_merge!(other_hash, &block)
end
# Same as +deep_merge+, but modifies +self+.
def deep_merge!(other_hash, &block)
- other_hash.each_pair do |k,v|
- tv = self[k]
- if tv.is_a?(Hash) && v.is_a?(Hash)
- self[k] = tv.deep_merge(v, &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)
else
- self[k] = block && tv ? block.call(k, tv, v) : v
+ if block_given? && key?(current_key)
+ block.call(current_key, this_value, other_value)
+ else
+ other_value
+ end
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 d90e996ad4..6e397abf51 100644
--- a/activesupport/lib/active_support/core_ext/hash/except.rb
+++ b/activesupport/lib/active_support/core_ext/hash/except.rb
@@ -1,13 +1,19 @@
class Hash
- # Return a hash that includes everything but the given keys. This is useful for
- # limiting a set of parameters to everything but a few known toggles:
+ # Returns a hash that includes everything but the given keys.
+ # hash = { a: true, b: false, c: nil}
+ # hash.except(:c) # => { a: true, b: false}
+ # hash # => { a: true, b: false, c: nil}
#
+ # This is useful for limiting a set of parameters to everything but a few known toggles:
# @person.update(params[:person].except(:admin))
def except(*keys)
dup.except!(*keys)
end
# Replaces the hash without the given keys.
+ # hash = { a: true, b: false, c: nil}
+ # hash.except!(:c) # => { a: true, b: false}
+ # hash # => { a: true, b: false }
def except!(*keys)
keys.each { |key| delete(key) }
self
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 981e8436bf..28cb3e2a3b 100644
--- a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb
+++ b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb
@@ -18,5 +18,6 @@ class Hash
#
# b = { b: 1 }
# { a: b }.with_indifferent_access['a'] # calls b.nested_under_indifferent_access
+ # # => {"b"=>1}
alias nested_under_indifferent_access with_indifferent_access
end
diff --git a/activesupport/lib/active_support/core_ext/hash/keys.rb b/activesupport/lib/active_support/core_ext/hash/keys.rb
index b4c451ace4..9297a59c46 100644
--- a/activesupport/lib/active_support/core_ext/hash/keys.rb
+++ b/activesupport/lib/active_support/core_ext/hash/keys.rb
@@ -1,12 +1,13 @@
class Hash
- # Return a new hash with all keys converted using the block operation.
+ # Returns a new hash with all keys converted using the block operation.
#
# hash = { name: 'Rob', age: '28' }
#
# hash.transform_keys{ |key| key.to_s.upcase }
- # # => { "NAME" => "Rob", "AGE" => "28" }
+ # # => {"NAME"=>"Rob", "AGE"=>"28"}
def transform_keys
- result = {}
+ return enum_for(:transform_keys) unless block_given?
+ result = self.class.new
each_key do |key|
result[yield(key)] = self[key]
end
@@ -16,35 +17,36 @@ class Hash
# Destructively convert all keys using the block operations.
# Same as transform_keys but modifies +self+.
def transform_keys!
+ return enum_for(:transform_keys!) unless block_given?
keys.each do |key|
self[yield(key)] = delete(key)
end
self
end
- # Return a new hash with all keys converted to strings.
+ # Returns a new hash with all keys converted to strings.
#
# hash = { name: 'Rob', age: '28' }
#
# hash.stringify_keys
- # #=> { "name" => "Rob", "age" => "28" }
+ # # => {"name"=>"Rob", "age"=>"28"}
def stringify_keys
- transform_keys{ |key| key.to_s }
+ transform_keys(&:to_s)
end
# Destructively convert all keys to strings. Same as
# +stringify_keys+, but modifies +self+.
def stringify_keys!
- transform_keys!{ |key| key.to_s }
+ transform_keys!(&:to_s)
end
- # Return a new hash with all keys converted to symbols, as long as
+ # Returns a new hash with all keys converted to symbols, as long as
# they respond to +to_sym+.
#
# hash = { 'name' => 'Rob', 'age' => '28' }
#
# hash.symbolize_keys
- # #=> { name: "Rob", age: "28" }
+ # # => {:name=>"Rob", :age=>"28"}
def symbolize_keys
transform_keys{ |key| key.to_sym rescue key }
end
@@ -57,82 +59,108 @@ class Hash
end
alias_method :to_options!, :symbolize_keys!
- # Validate all keys in a hash match <tt>*valid_keys</tt>, raising ArgumentError
- # on a mismatch. Note that keys are NOT treated indifferently, meaning if you
- # use strings for keys but assert symbols as keys, this will fail.
+ # Validate all keys in a hash match <tt>*valid_keys</tt>, raising
+ # ArgumentError on a mismatch.
#
- # { name: 'Rob', years: '28' }.assert_valid_keys(:name, :age) # => raises "ArgumentError: Unknown key: years"
- # { name: 'Rob', age: '28' }.assert_valid_keys('name', 'age') # => raises "ArgumentError: Unknown key: name"
+ # Note that keys are treated differently than HashWithIndifferentAccess,
+ # meaning that string and symbol keys will not match.
+ #
+ # { name: 'Rob', years: '28' }.assert_valid_keys(:name, :age) # => raises "ArgumentError: Unknown key: :years. Valid keys are: :name, :age"
+ # { name: 'Rob', age: '28' }.assert_valid_keys('name', 'age') # => raises "ArgumentError: Unknown key: :name. Valid keys are: 'name', 'age'"
# { name: 'Rob', age: '28' }.assert_valid_keys(:name, :age) # => passes, raises nothing
def assert_valid_keys(*valid_keys)
valid_keys.flatten!
each_key do |k|
- raise ArgumentError.new("Unknown key: #{k}") unless valid_keys.include?(k)
+ unless valid_keys.include?(k)
+ raise ArgumentError.new("Unknown key: #{k.inspect}. Valid keys are: #{valid_keys.map(&:inspect).join(', ')}")
+ end
end
end
- # Return a new hash with all keys converted by the block operation.
+ # Returns a new hash with all keys converted by the block operation.
# This includes the keys from the root hash and from all
- # nested hashes.
+ # nested hashes and arrays.
#
# hash = { person: { name: 'Rob', age: '28' } }
#
# hash.deep_transform_keys{ |key| key.to_s.upcase }
- # # => { "PERSON" => { "NAME" => "Rob", "AGE" => "28" } }
+ # # => {"PERSON"=>{"NAME"=>"Rob", "AGE"=>"28"}}
def deep_transform_keys(&block)
- result = {}
- each do |key, value|
- result[yield(key)] = value.is_a?(Hash) ? value.deep_transform_keys(&block) : value
- end
- result
+ _deep_transform_keys_in_object(self, &block)
end
# Destructively convert all keys by using the block operation.
# This includes the keys from the root hash and from all
- # nested hashes.
+ # nested hashes and arrays.
def deep_transform_keys!(&block)
- keys.each do |key|
- value = delete(key)
- self[yield(key)] = value.is_a?(Hash) ? value.deep_transform_keys!(&block) : value
- end
- self
+ _deep_transform_keys_in_object!(self, &block)
end
- # Return a new hash with all keys converted to strings.
+ # Returns a new hash with all keys converted to strings.
# This includes the keys from the root hash and from all
- # nested hashes.
+ # nested hashes and arrays.
#
# hash = { person: { name: 'Rob', age: '28' } }
#
# hash.deep_stringify_keys
- # # => { "person" => { "name" => "Rob", "age" => "28" } }
+ # # => {"person"=>{"name"=>"Rob", "age"=>"28"}}
def deep_stringify_keys
- deep_transform_keys{ |key| key.to_s }
+ deep_transform_keys(&:to_s)
end
# Destructively convert all keys to strings.
# This includes the keys from the root hash and from all
- # nested hashes.
+ # nested hashes and arrays.
def deep_stringify_keys!
- deep_transform_keys!{ |key| key.to_s }
+ deep_transform_keys!(&:to_s)
end
- # Return a new hash with all keys converted to symbols, as long as
+ # Returns a new hash with all keys converted to symbols, as long as
# they respond to +to_sym+. This includes the keys from the root hash
- # and from all nested hashes.
+ # and from all nested hashes and arrays.
#
# hash = { 'person' => { 'name' => 'Rob', 'age' => '28' } }
#
# hash.deep_symbolize_keys
- # # => { person: { name: "Rob", age: "28" } }
+ # # => {:person=>{:name=>"Rob", :age=>"28"}}
def deep_symbolize_keys
deep_transform_keys{ |key| key.to_sym rescue key }
end
# Destructively convert all keys to symbols, as long as they respond
# to +to_sym+. This includes the keys from the root hash and from all
- # nested hashes.
+ # nested hashes and arrays.
def deep_symbolize_keys!
deep_transform_keys!{ |key| key.to_sym rescue key }
end
+
+ private
+ # support methods for deep transforming nested hashes and arrays
+ def _deep_transform_keys_in_object(object, &block)
+ case object
+ when Hash
+ object.each_with_object({}) do |(key, value), result|
+ result[yield(key)] = _deep_transform_keys_in_object(value, &block)
+ end
+ when Array
+ object.map {|e| _deep_transform_keys_in_object(e, &block) }
+ else
+ object
+ end
+ end
+
+ def _deep_transform_keys_in_object!(object, &block)
+ case object
+ when Hash
+ object.keys.each do |key|
+ value = object.delete(key)
+ object[yield(key)] = _deep_transform_keys_in_object!(value, &block)
+ end
+ object
+ when Array
+ object.map! {|e| _deep_transform_keys_in_object!(e, &block)}
+ else
+ object
+ end
+ end
end
diff --git a/activesupport/lib/active_support/core_ext/hash/slice.rb b/activesupport/lib/active_support/core_ext/hash/slice.rb
index 9fa9b3dac4..41b2279013 100644
--- a/activesupport/lib/active_support/core_ext/hash/slice.rb
+++ b/activesupport/lib/active_support/core_ext/hash/slice.rb
@@ -1,6 +1,12 @@
class Hash
- # Slice a hash to include only the given keys. This is useful for
- # limiting an options hash to valid keys before passing to a method:
+ # Slice a hash to include only the given keys. Returns a hash containing
+ # the given keys.
+ #
+ # { a: 1, b: 2, c: 3, d: 4 }.slice(:a, :b)
+ # # => {:a=>1, :b=>2}
+ #
+ # This is useful for limiting an options hash to valid keys before
+ # passing to a method:
#
# def search(criteria = {})
# criteria.assert_valid_keys(:mass, :velocity, :time)
@@ -26,6 +32,8 @@ class Hash
keys.map! { |key| convert_key(key) } if respond_to?(:convert_key, true)
omit = slice(*self.keys - keys)
hash = slice(*keys)
+ hash.default = default
+ hash.default_proc = default_proc if default_proc
replace(hash)
omit
end
diff --git a/activesupport/lib/active_support/core_ext/hash/transform_values.rb b/activesupport/lib/active_support/core_ext/hash/transform_values.rb
new file mode 100644
index 0000000000..e9bcce761f
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/hash/transform_values.rb
@@ -0,0 +1,23 @@
+class Hash
+ # Returns a new hash with the results of running +block+ once for every value.
+ # The keys are unchanged.
+ #
+ # { a: 1, b: 2, c: 3 }.transform_values { |x| x * 2 }
+ # # => { a: 2, b: 4, c: 6 }
+ def transform_values
+ return enum_for(:transform_values) unless block_given?
+ result = self.class.new
+ each do |key, value|
+ result[key] = yield(value)
+ end
+ result
+ end
+
+ # Destructive +transform_values+
+ def transform_values!
+ return enum_for(:transform_values!) unless block_given?
+ each do |key, value|
+ self[key] = yield(value)
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/integer/multiple.rb b/activesupport/lib/active_support/core_ext/integer/multiple.rb
index 7c6c2f1ca7..c668c7c2eb 100644
--- a/activesupport/lib/active_support/core_ext/integer/multiple.rb
+++ b/activesupport/lib/active_support/core_ext/integer/multiple.rb
@@ -1,9 +1,9 @@
class Integer
# Check whether the integer is evenly divisible by the argument.
#
- # 0.multiple_of?(0) #=> true
- # 6.multiple_of?(5) #=> false
- # 10.multiple_of?(2) #=> true
+ # 0.multiple_of?(0) # => true
+ # 6.multiple_of?(5) # => false
+ # 10.multiple_of?(2) # => true
def multiple_of?(number)
number != 0 ? self % number == 0 : zero?
end
diff --git a/activesupport/lib/active_support/core_ext/kernel.rb b/activesupport/lib/active_support/core_ext/kernel.rb
index 0275f4c037..364ed9d65f 100644
--- a/activesupport/lib/active_support/core_ext/kernel.rb
+++ b/activesupport/lib/active_support/core_ext/kernel.rb
@@ -1,4 +1,4 @@
-require 'active_support/core_ext/kernel/reporting'
require 'active_support/core_ext/kernel/agnostics'
-require 'active_support/core_ext/kernel/debugger'
+require 'active_support/core_ext/kernel/concern'
+require 'active_support/core_ext/kernel/reporting'
require 'active_support/core_ext/kernel/singleton_class'
diff --git a/activesupport/lib/active_support/core_ext/kernel/concern.rb b/activesupport/lib/active_support/core_ext/kernel/concern.rb
new file mode 100644
index 0000000000..bf72caa058
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/kernel/concern.rb
@@ -0,0 +1,10 @@
+require 'active_support/core_ext/module/concerning'
+
+module Kernel
+ # A shortcut to define a toplevel concern, not within a module.
+ #
+ # See Module::Concerning for more.
+ def concern(topic, &module_definition)
+ Object.concern topic, &module_definition
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/kernel/debugger.rb b/activesupport/lib/active_support/core_ext/kernel/debugger.rb
index 2073cac98d..1fde3db070 100644
--- a/activesupport/lib/active_support/core_ext/kernel/debugger.rb
+++ b/activesupport/lib/active_support/core_ext/kernel/debugger.rb
@@ -1,10 +1,3 @@
-module Kernel
- unless respond_to?(:debugger)
- # Starts a debugging session if the +debugger+ gem has been loaded (call rails server --debugger to do load it).
- def debugger
- message = "\n***** Debugger requested, but was not available (ensure the debugger gem is listed in Gemfile/installed as gem): Start server with --debugger to enable *****\n"
- defined?(Rails) ? Rails.logger.info(message) : $stderr.puts(message)
- end
- alias breakpoint debugger unless respond_to?(:breakpoint)
- end
-end
+require 'active_support/deprecation'
+
+ActiveSupport::Deprecation.warn("This file is deprecated and will be removed in Rails 5.1 with no replacement.")
diff --git a/activesupport/lib/active_support/core_ext/kernel/reporting.rb b/activesupport/lib/active_support/core_ext/kernel/reporting.rb
index 36ab836457..9189e6d977 100644
--- a/activesupport/lib/active_support/core_ext/kernel/reporting.rb
+++ b/activesupport/lib/active_support/core_ext/kernel/reporting.rb
@@ -1,4 +1,3 @@
-require 'rbconfig'
require 'tempfile'
module Kernel
@@ -29,27 +28,6 @@ module Kernel
$VERBOSE = old_verbose
end
- # For compatibility
- def silence_stderr #:nodoc:
- silence_stream(STDERR) { yield }
- end
-
- # Silences any stream for the duration of the block.
- #
- # silence_stream(STDOUT) do
- # puts 'This will never be seen'
- # end
- #
- # puts 'But this will'
- def silence_stream(stream)
- old_stream = stream.dup
- stream.reopen(RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ ? 'NUL:' : '/dev/null')
- stream.sync = true
- yield
- ensure
- stream.reopen(old_stream)
- end
-
# Blocks and ignores any exception passed as argument if raised within the block.
#
# suppress(ZeroDivisionError) do
@@ -60,51 +38,6 @@ module Kernel
# puts 'This code gets executed and nothing related to ZeroDivisionError was seen'
def suppress(*exception_classes)
yield
- rescue Exception => e
- raise unless exception_classes.any? { |cls| e.kind_of?(cls) }
- end
-
- # Captures the given stream and returns it:
- #
- # stream = capture(:stdout) { puts 'notice' }
- # stream # => "notice\n"
- #
- # stream = capture(:stderr) { warn 'error' }
- # stream # => "error\n"
- #
- # even for subprocesses:
- #
- # stream = capture(:stdout) { system('echo notice') }
- # stream # => "notice\n"
- #
- # stream = capture(:stderr) { system('echo error 1>&2') }
- # stream # => "error\n"
- def capture(stream)
- stream = stream.to_s
- captured_stream = Tempfile.new(stream)
- stream_io = eval("$#{stream}")
- origin_stream = stream_io.dup
- stream_io.reopen(captured_stream)
-
- yield
-
- stream_io.rewind
- return captured_stream.read
- ensure
- captured_stream.close
- captured_stream.unlink
- stream_io.reopen(origin_stream)
- end
- alias :silence :capture
-
- # Silences both STDOUT and STDERR, even for subprocesses.
- #
- # quietly { system 'bundle install' }
- def quietly
- silence_stream(STDOUT) do
- silence_stream(STDERR) do
- yield
- end
- end
+ rescue *exception_classes
end
end
diff --git a/activesupport/lib/active_support/core_ext/load_error.rb b/activesupport/lib/active_support/core_ext/load_error.rb
index fe24f3716d..d9fb392752 100644
--- a/activesupport/lib/active_support/core_ext/load_error.rb
+++ b/activesupport/lib/active_support/core_ext/load_error.rb
@@ -1,3 +1,5 @@
+require 'active_support/deprecation/proxy_wrappers'
+
class LoadError
REGEXPS = [
/^no such file to load -- (.+)$/i,
@@ -7,6 +9,7 @@ class LoadError
]
unless method_defined?(:path)
+ # Returns the path which was unable to be loaded.
def path
@path ||= begin
REGEXPS.find do |regex|
@@ -17,9 +20,11 @@ class LoadError
end
end
+ # 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)
location.sub(/\.rb$/, '') == path.sub(/\.rb$/, '')
end
end
-MissingSourceFile = LoadError \ No newline at end of file
+MissingSourceFile = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('MissingSourceFile', 'LoadError')
diff --git a/activesupport/lib/active_support/core_ext/module.rb b/activesupport/lib/active_support/core_ext/module.rb
index f2d4887df6..b4efff8b24 100644
--- a/activesupport/lib/active_support/core_ext/module.rb
+++ b/activesupport/lib/active_support/core_ext/module.rb
@@ -4,6 +4,7 @@ require 'active_support/core_ext/module/anonymous'
require 'active_support/core_ext/module/reachable'
require 'active_support/core_ext/module/attribute_accessors'
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/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 580cb80413..0a6fadf928 100644
--- a/activesupport/lib/active_support/core_ext/module/aliasing.rb
+++ b/activesupport/lib/active_support/core_ext/module/aliasing.rb
@@ -19,9 +19,9 @@ class Module
# alias_method :foo_without_feature?, :foo?
# alias_method :foo?, :foo_with_feature?
#
- # so you can safely chain foo, foo?, and foo! with the same feature.
+ # so you can safely chain foo, foo?, foo! and/or foo= with the same feature.
def alias_method_chain(target, feature)
- # Strip out punctuation on predicates or bang methods since
+ # Strip out punctuation on predicates, bang or writer methods since
# e.g. target?_without_feature is not a valid method name.
aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1
yield(aliased_target, punctuation) if block_given?
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 db07d549b0..93fb598650 100644
--- a/activesupport/lib/active_support/core_ext/module/attr_internal.rb
+++ b/activesupport/lib/active_support/core_ext/module/attr_internal.rb
@@ -27,10 +27,8 @@ class Module
def attr_internal_define(attr_name, type)
internal_name = attr_internal_ivar_name(attr_name).sub(/\A@/, '')
- class_eval do # class_eval is necessary on 1.9 or else the methods a made private
- # use native attr_* methods as they are faster on some Ruby implementations
- send("attr_#{type}", internal_name)
- end
+ # use native attr_* methods as they are faster on some Ruby implementations
+ send("attr_#{type}", internal_name)
attr_name, internal_name = "#{attr_name}=", "#{internal_name}=" if type == :writer
alias_method attr_name, internal_name
remove_method internal_name
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 672cc0256f..d4e6b5a1ac 100644
--- a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
+++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
@@ -1,10 +1,59 @@
require 'active_support/core_ext/array/extract_options'
+# Extends the module object with class/module and instance accessors for
+# class/module attributes, just like the native attr* accessors for instance
+# attributes.
class Module
+ # Defines a class attribute and creates a class and instance reader methods.
+ # The underlying the class variable is set to +nil+, if it is not previously
+ # defined.
+ #
+ # module HairColors
+ # mattr_reader :hair_colors
+ # end
+ #
+ # HairColors.hair_colors # => nil
+ # HairColors.class_variable_set("@@hair_colors", [:brown, :black])
+ # HairColors.hair_colors # => [:brown, :black]
+ #
+ # The attribute name must be a valid method name in Ruby.
+ #
+ # module Foo
+ # mattr_reader :"1_Badname "
+ # end
+ # # => NameError: invalid attribute name
+ #
+ # If you want to opt out the creation on the instance reader method, pass
+ # <tt>instance_reader: false</tt> or <tt>instance_accessor: false</tt>.
+ #
+ # module HairColors
+ # mattr_writer :hair_colors, instance_reader: false
+ # end
+ #
+ # class Person
+ # include HairColors
+ # end
+ #
+ # Person.new.hair_colors # => NoMethodError
+ #
+ #
+ # Also, you can pass a block to set up the attribute with a default value.
+ #
+ # module HairColors
+ # cattr_reader :hair_colors do
+ # [:brown, :black, :blonde, :red]
+ # end
+ # end
+ #
+ # class Person
+ # include HairColors
+ # end
+ #
+ # Person.hair_colors # => [:brown, :black, :blonde, :red]
def mattr_reader(*syms)
options = syms.extract_options!
syms.each do |sym|
- raise NameError.new('invalid attribute name') unless sym =~ /^[_A-Za-z]\w*$/
+ raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
@@#{sym} = nil unless defined? @@#{sym}
@@ -20,14 +69,60 @@ class Module
end
EOS
end
+ class_variable_set("@@#{sym}", yield) if block_given?
end
end
+ alias :cattr_reader :mattr_reader
+ # Defines a class attribute and creates a class and instance writer methods to
+ # allow assignment to the attribute.
+ #
+ # module HairColors
+ # mattr_writer :hair_colors
+ # end
+ #
+ # class Person
+ # include HairColors
+ # end
+ #
+ # HairColors.hair_colors = [:brown, :black]
+ # Person.class_variable_get("@@hair_colors") # => [:brown, :black]
+ # Person.new.hair_colors = [:blonde, :red]
+ # HairColors.class_variable_get("@@hair_colors") # => [:blonde, :red]
+ #
+ # If you want to opt out the instance writer method, pass
+ # <tt>instance_writer: false</tt> or <tt>instance_accessor: false</tt>.
+ #
+ # module HairColors
+ # mattr_writer :hair_colors, instance_writer: false
+ # end
+ #
+ # class Person
+ # include HairColors
+ # end
+ #
+ # Person.new.hair_colors = [:blonde, :red] # => NoMethodError
+ #
+ # Also, you can pass a block to set up the attribute with a default value.
+ #
+ # class HairColors
+ # mattr_writer :hair_colors do
+ # [:brown, :black, :blonde, :red]
+ # end
+ # end
+ #
+ # class Person
+ # include HairColors
+ # end
+ #
+ # Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red]
def mattr_writer(*syms)
options = syms.extract_options!
syms.each do |sym|
- raise NameError.new('invalid attribute name') unless sym =~ /^[_A-Za-z]\w*$/
+ raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
+ @@#{sym} = nil unless defined? @@#{sym}
+
def self.#{sym}=(obj)
@@#{sym} = obj
end
@@ -40,27 +135,78 @@ class Module
end
EOS
end
+ send("#{sym}=", yield) if block_given?
end
end
+ alias :cattr_writer :mattr_writer
- # Extends the module object with module and instance accessors for class attributes,
- # just like the native attr* accessors for instance attributes.
+ # Defines both class and instance accessors for class attributes.
#
- # module AppConfiguration
- # mattr_accessor :google_api_key
+ # module HairColors
+ # mattr_accessor :hair_colors
+ # end
#
- # self.google_api_key = "123456789"
+ # class Person
+ # include HairColors
# end
#
- # AppConfiguration.google_api_key # => "123456789"
- # AppConfiguration.google_api_key = "overriding the api key!"
- # AppConfiguration.google_api_key # => "overriding the api key!"
+ # Person.hair_colors = [:brown, :black, :blonde, :red]
+ # Person.hair_colors # => [:brown, :black, :blonde, :red]
+ # Person.new.hair_colors # => [:brown, :black, :blonde, :red]
+ #
+ # If a subclass changes the value then that would also change the value for
+ # parent class. Similarly if parent class changes the value then that would
+ # change the value of subclasses too.
+ #
+ # class Male < Person
+ # end
+ #
+ # Male.hair_colors << :blue
+ # Person.hair_colors # => [:brown, :black, :blonde, :red, :blue]
#
# To opt out of the instance writer method, pass <tt>instance_writer: false</tt>.
# To opt out of the instance reader method, pass <tt>instance_reader: false</tt>.
- # To opt out of both instance methods, pass <tt>instance_accessor: false</tt>.
- def mattr_accessor(*syms)
- mattr_reader(*syms)
- mattr_writer(*syms)
+ #
+ # module HairColors
+ # mattr_accessor :hair_colors, instance_writer: false, instance_reader: false
+ # end
+ #
+ # class Person
+ # include HairColors
+ # end
+ #
+ # Person.new.hair_colors = [:brown] # => NoMethodError
+ # Person.new.hair_colors # => NoMethodError
+ #
+ # Or pass <tt>instance_accessor: false</tt>, to opt out both instance methods.
+ #
+ # module HairColors
+ # mattr_accessor :hair_colors, instance_accessor: false
+ # end
+ #
+ # class Person
+ # include HairColors
+ # end
+ #
+ # 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.
+ #
+ # module HairColors
+ # mattr_accessor :hair_colors do
+ # [:brown, :black, :blonde, :red]
+ # end
+ # end
+ #
+ # class Person
+ # include HairColors
+ # end
+ #
+ # Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red]
+ def mattr_accessor(*syms, &blk)
+ mattr_reader(*syms, &blk)
+ mattr_writer(*syms, &blk)
end
+ alias :cattr_accessor :mattr_accessor
end
diff --git a/activesupport/lib/active_support/core_ext/module/concerning.rb b/activesupport/lib/active_support/core_ext/module/concerning.rb
new file mode 100644
index 0000000000..07a392404e
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/module/concerning.rb
@@ -0,0 +1,135 @@
+require 'active_support/concern'
+
+class Module
+ # = Bite-sized separation of concerns
+ #
+ # We often find ourselves with a medium-sized chunk of behavior that we'd
+ # like to extract, but only mix in to a single class.
+ #
+ # Extracting a plain old Ruby object to encapsulate it and collaborate or
+ # delegate to the original object is often a good choice, but when there's
+ # no additional state to encapsulate or we're making DSL-style declarations
+ # about the parent class, introducing new collaborators can obfuscate rather
+ # than simplify.
+ #
+ # The typical route is to just dump everything in a monolithic class, perhaps
+ # with a comment, as a least-bad alternative. Using modules in separate files
+ # means tedious sifting to get a big-picture view.
+ #
+ # = Dissatisfying ways to separate small concerns
+ #
+ # == Using comments:
+ #
+ # class Todo
+ # # Other todo implementation
+ # # ...
+ #
+ # ## Event tracking
+ # has_many :events
+ #
+ # before_create :track_creation
+ # after_destroy :track_deletion
+ #
+ # private
+ # def track_creation
+ # # ...
+ # end
+ # end
+ #
+ # == With an inline module:
+ #
+ # Noisy syntax.
+ #
+ # class Todo
+ # # Other todo implementation
+ # # ...
+ #
+ # module EventTracking
+ # extend ActiveSupport::Concern
+ #
+ # included do
+ # has_many :events
+ # before_create :track_creation
+ # after_destroy :track_deletion
+ # end
+ #
+ # private
+ # def track_creation
+ # # ...
+ # end
+ # end
+ # include EventTracking
+ # end
+ #
+ # == Mix-in noise exiled to its own file:
+ #
+ # Once our chunk of behavior starts pushing the scroll-to-understand it's
+ # boundary, we give in and move it to a separate file. At this size, the
+ # overhead feels in good proportion to the size of our extraction, despite
+ # diluting our at-a-glance sense of how things really work.
+ #
+ # class Todo
+ # # Other todo implementation
+ # # ...
+ #
+ # include TodoEventTracking
+ # end
+ #
+ # = Introducing Module#concerning
+ #
+ # By quieting the mix-in noise, we arrive at a natural, low-ceremony way to
+ # separate bite-sized concerns.
+ #
+ # class Todo
+ # # Other todo implementation
+ # # ...
+ #
+ # concerning :EventTracking do
+ # included do
+ # has_many :events
+ # before_create :track_creation
+ # after_destroy :track_deletion
+ # end
+ #
+ # private
+ # def track_creation
+ # # ...
+ # end
+ # end
+ # end
+ #
+ # Todo.ancestors
+ # # => Todo, Todo::EventTracking, Object
+ #
+ # This small step has some wonderful ripple effects. We can
+ # * grok the behavior of our class in one glance,
+ # * clean up monolithic junk-drawer classes by separating their concerns, and
+ # * stop leaning on protected/private for crude "this is internal stuff" modularity.
+ module Concerning
+ # Define a new concern and mix it in.
+ def concerning(topic, &block)
+ include concern(topic, &block)
+ end
+
+ # A low-cruft shortcut to define a concern.
+ #
+ # concern :EventTracking do
+ # ...
+ # end
+ #
+ # is equivalent to
+ #
+ # module EventTracking
+ # extend ActiveSupport::Concern
+ #
+ # ...
+ # end
+ def concern(topic, &module_definition)
+ const_set topic, Module.new {
+ extend ::ActiveSupport::Concern
+ module_eval(&module_definition)
+ }
+ end
+ end
+ include Concerning
+end
diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb
index bca3800344..24df83800b 100644
--- a/activesupport/lib/active_support/core_ext/module/delegation.rb
+++ b/activesupport/lib/active_support/core_ext/module/delegation.rb
@@ -1,15 +1,23 @@
+require 'set'
+
class Module
# Error generated by +delegate+ when a method is called on +nil+ and +allow_nil+
# option is not used.
class DelegationError < NoMethodError; end
+ RUBY_RESERVED_WORDS = Set.new(
+ %w(alias and BEGIN begin break case class def defined? do else elsif END
+ end ensure false for if in module next nil not or redo rescue retry
+ return self super then true undef unless until when while yield)
+ ).freeze
+
# Provides a +delegate+ class method to easily expose contained objects'
# public methods as your own.
#
# ==== Options
# * <tt>:to</tt> - Specifies the target object
# * <tt>:prefix</tt> - Prefixes the new method with the target name or a custom prefix
- # * <tt>:allow_nil</tt> - if set to true, prevents a +NoMethodError+ to be raised
+ # * <tt>:allow_nil</tt> - if set to true, prevents a +NoMethodError+ from being raised
#
# The macro receives one or more method names (specified as symbols or
# strings) and the name of the target object via the <tt>:to</tt> option
@@ -138,6 +146,8 @@ class Module
#
# Foo.new("Bar").name # raises NoMethodError: undefined method `name'
#
+ # The target method must be public, otherwise it will raise +NoMethodError+.
+ #
def delegate(*methods)
options = methods.pop
unless options.is_a?(Hash) && to = options[:to]
@@ -161,46 +171,35 @@ class Module
line = line.to_i
to = to.to_s
- to = 'self.class' if to == 'class'
+ to = "self.#{to}" if RUBY_RESERVED_WORDS.include?(to)
methods.each do |method|
# Attribute writer methods only accept one argument. Makes sure []=
# methods still accept two arguments.
definition = (method =~ /[^\]]=$/) ? 'arg' : '*args, &block'
- # The following generated methods call the target exactly once, storing
+ # The following generated method calls the target exactly once, storing
# the returned value in a dummy variable.
#
# Reason is twofold: On one hand doing less calls is in general better.
# On the other hand it could be that the target has side-effects,
# whereas conceptually, from the user point of view, the delegator should
# be doing one call.
- if allow_nil
- module_eval(<<-EOS, file, line - 3)
- def #{method_prefix}#{method}(#{definition}) # def customer_name(*args, &block)
- _ = #{to} # _ = client
- if !_.nil? || nil.respond_to?(:#{method}) # if !_.nil? || nil.respond_to?(:name)
- _.#{method}(#{definition}) # _.name(*args, &block)
- end # end
- end # end
- EOS
- else
- exception = %(raise DelegationError, "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}")
- module_eval(<<-EOS, file, line - 2)
- def #{method_prefix}#{method}(#{definition}) # def customer_name(*args, &block)
- _ = #{to} # _ = client
- _.#{method}(#{definition}) # _.name(*args, &block)
- rescue NoMethodError => e # rescue NoMethodError => e
- location = "%s:%d:in `%s'" % [__FILE__, __LINE__ - 2, '#{method_prefix}#{method}'] # location = "%s:%d:in `%s'" % [__FILE__, __LINE__ - 2, 'customer_name']
- if _.nil? && e.backtrace.first == location # if _.nil? && e.backtrace.first == location
- #{exception} # # add helpful message to the exception
- else # else
- raise # raise
- end # end
- end # end
- EOS
- end
+ exception = %(raise DelegationError, "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}")
+
+ method_def = [
+ "def #{method_prefix}#{method}(#{definition})",
+ " _ = #{to}",
+ " if !_.nil? || nil.respond_to?(:#{method})",
+ " _.#{method}(#{definition})",
+ " else",
+ " #{exception unless allow_nil}",
+ " end",
+ "end"
+ ].join ';'
+
+ module_eval(method_def, file, line)
end
end
end
diff --git a/activesupport/lib/active_support/core_ext/module/deprecation.rb b/activesupport/lib/active_support/core_ext/module/deprecation.rb
index d873de197f..56d670fbe8 100644
--- a/activesupport/lib/active_support/core_ext/module/deprecation.rb
+++ b/activesupport/lib/active_support/core_ext/module/deprecation.rb
@@ -1,5 +1,3 @@
-require 'active_support/deprecation/method_wrappers'
-
class Module
# deprecate :foo
# deprecate bar: 'message'
diff --git a/activesupport/lib/active_support/core_ext/module/method_transplanting.rb b/activesupport/lib/active_support/core_ext/module/method_transplanting.rb
index b1097cc83b..1fde3db070 100644
--- a/activesupport/lib/active_support/core_ext/module/method_transplanting.rb
+++ b/activesupport/lib/active_support/core_ext/module/method_transplanting.rb
@@ -1,11 +1,3 @@
-class Module
- ###
- # TODO: remove this after 1.9 support is dropped
- def methods_transplantable? # :nodoc:
- x = Module.new { def foo; end }
- Module.new { define_method :bar, x.instance_method(:foo) }
- true
- rescue TypeError
- false
- end
-end
+require 'active_support/deprecation'
+
+ActiveSupport::Deprecation.warn("This file is deprecated and will be removed in Rails 5.1 with no replacement.")
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 719071d1c2..8a2569a7d0 100644
--- a/activesupport/lib/active_support/core_ext/module/remove_method.rb
+++ b/activesupport/lib/active_support/core_ext/module/remove_method.rb
@@ -1,10 +1,13 @@
class Module
+ # Remove the named method, if it exists.
def remove_possible_method(method)
if method_defined?(method) || private_method_defined?(method)
undef_method(method)
end
end
+ # Replace the existing method definition, if there is one, with the contents
+ # of the block.
def redefine_method(method, &block)
remove_possible_method(method)
define_method(method, &block)
diff --git a/activesupport/lib/active_support/core_ext/name_error.rb b/activesupport/lib/active_support/core_ext/name_error.rb
index e1ebd4f91c..b82148e4e5 100644
--- a/activesupport/lib/active_support/core_ext/name_error.rb
+++ b/activesupport/lib/active_support/core_ext/name_error.rb
@@ -1,5 +1,12 @@
class NameError
# Extract the name of the missing constant from the exception message.
+ #
+ # begin
+ # HelloWorld
+ # rescue NameError => e
+ # e.missing_name
+ # end
+ # # => "HelloWorld"
def missing_name
if /undefined local variable or method/ !~ message
$1 if /((::)?([A-Z]\w*)(::[A-Z]\w*)*)$/ =~ message
@@ -7,6 +14,13 @@ class NameError
end
# Was this exception raised because the given name was missing?
+ #
+ # begin
+ # HelloWorld
+ # rescue NameError => e
+ # e.missing_name?("HelloWorld")
+ # end
+ # # => true
def missing_name?(name)
if name.is_a? Symbol
last_name = (missing_name || '').split('::').last
diff --git a/activesupport/lib/active_support/core_ext/numeric/bytes.rb b/activesupport/lib/active_support/core_ext/numeric/bytes.rb
index deea8e9358..dfbca32474 100644
--- a/activesupport/lib/active_support/core_ext/numeric/bytes.rb
+++ b/activesupport/lib/active_support/core_ext/numeric/bytes.rb
@@ -7,36 +7,56 @@ class Numeric
EXABYTE = PETABYTE * 1024
# Enables the use of byte calculations and declarations, like 45.bytes + 2.6.megabytes
+ #
+ # 2.bytes # => 2
def bytes
self
end
alias :byte :bytes
+ # Returns the number of bytes equivalent to the kilobytes provided.
+ #
+ # 2.kilobytes # => 2048
def kilobytes
self * KILOBYTE
end
alias :kilobyte :kilobytes
+ # Returns the number of bytes equivalent to the megabytes provided.
+ #
+ # 2.megabytes # => 2_097_152
def megabytes
self * MEGABYTE
end
alias :megabyte :megabytes
+ # Returns the number of bytes equivalent to the gigabytes provided.
+ #
+ # 2.gigabytes # => 2_147_483_648
def gigabytes
self * GIGABYTE
end
alias :gigabyte :gigabytes
+ # Returns the number of bytes equivalent to the terabytes provided.
+ #
+ # 2.terabytes # => 2_199_023_255_552
def terabytes
self * TERABYTE
end
alias :terabyte :terabytes
+ # Returns the number of bytes equivalent to the petabytes provided.
+ #
+ # 2.petabytes # => 2_251_799_813_685_248
def petabytes
self * PETABYTE
end
alias :petabyte :petabytes
+ # Returns the number of bytes equivalent to the exabytes provided.
+ #
+ # 2.exabytes # => 2_305_843_009_213_693_952
def exabytes
self * EXABYTE
end
diff --git a/activesupport/lib/active_support/core_ext/numeric/conversions.rb b/activesupport/lib/active_support/core_ext/numeric/conversions.rb
index 6d3635c69a..0c8ff79237 100644
--- a/activesupport/lib/active_support/core_ext/numeric/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/numeric/conversions.rb
@@ -118,18 +118,28 @@ class Numeric
end
end
- [Float, Fixnum, Bignum, BigDecimal].each do |klass|
- klass.send(:alias_method, :to_default_s, :to_s)
-
- klass.send(:define_method, :to_s) do |*args|
- if args[0].is_a?(Symbol)
- format = args[0]
- options = args[1] || {}
+ [Fixnum, Bignum].each do |klass|
+ klass.class_eval do
+ alias_method :to_default_s, :to_s
+ def to_s(base_or_format = 10, options = nil)
+ if base_or_format.is_a?(Symbol)
+ to_formatted_s(base_or_format, options || {})
+ else
+ to_default_s(base_or_format)
+ end
+ end
+ end
+ end
- self.to_formatted_s(format, options)
+ Float.class_eval do
+ alias_method :to_default_s, :to_s
+ def to_s(*args)
+ if args.empty?
+ to_default_s
else
- to_default_s(*args)
+ to_formatted_s(*args)
end
end
end
+
end
diff --git a/activesupport/lib/active_support/core_ext/numeric/time.rb b/activesupport/lib/active_support/core_ext/numeric/time.rb
index 87b9a23aef..98716383f4 100644
--- a/activesupport/lib/active_support/core_ext/numeric/time.rb
+++ b/activesupport/lib/active_support/core_ext/numeric/time.rb
@@ -1,6 +1,8 @@
require 'active_support/duration'
require 'active_support/core_ext/time/calculations'
require 'active_support/core_ext/time/acts_like'
+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.
@@ -36,44 +38,52 @@ class Numeric
end
alias :second :seconds
+ # Returns a Duration instance matching the number of minutes provided.
+ #
+ # 2.minutes # => 120 seconds
def minutes
ActiveSupport::Duration.new(self * 60, [[:seconds, self * 60]])
end
alias :minute :minutes
+ # Returns a Duration instance matching the number of hours provided.
+ #
+ # 2.hours # => 7_200 seconds
def hours
ActiveSupport::Duration.new(self * 3600, [[:seconds, self * 3600]])
end
alias :hour :hours
+ # Returns a Duration instance matching the number of days provided.
+ #
+ # 2.days # => 2 days
def days
ActiveSupport::Duration.new(self * 24.hours, [[:days, self]])
end
alias :day :days
+ # Returns a Duration instance matching the number of weeks provided.
+ #
+ # 2.weeks # => 14 days
def weeks
ActiveSupport::Duration.new(self * 7.days, [[:days, self * 7]])
end
alias :week :weeks
+ # Returns a Duration instance matching the number of fortnights provided.
+ #
+ # 2.fortnights # => 28 days
def fortnights
ActiveSupport::Duration.new(self * 2.weeks, [[:days, self * 14]])
end
alias :fortnight :fortnights
- # Reads best without arguments: 10.minutes.ago
- def ago(time = ::Time.current)
- time - self
- end
-
- # Reads best with argument: 10.minutes.until(time)
- alias :until :ago
-
- # Reads best with argument: 10.minutes.since(time)
- def since(time = ::Time.current)
- time + self
+ # 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().
+ #
+ # 2.in_milliseconds # => 2_000
+ def in_milliseconds
+ self * 1000
end
-
- # Reads best without arguments: 10.minutes.from_now
- alias :from_now :since
end
diff --git a/activesupport/lib/active_support/core_ext/object.rb b/activesupport/lib/active_support/core_ext/object.rb
index ec2157221f..f4f9152d6a 100644
--- a/activesupport/lib/active_support/core_ext/object.rb
+++ b/activesupport/lib/active_support/core_ext/object.rb
@@ -8,7 +8,7 @@ require 'active_support/core_ext/object/inclusion'
require 'active_support/core_ext/object/conversions'
require 'active_support/core_ext/object/instance_variables'
-require 'active_support/core_ext/object/to_json'
+require 'active_support/core_ext/object/json'
require 'active_support/core_ext/object/to_param'
require 'active_support/core_ext/object/to_query'
require 'active_support/core_ext/object/with_options'
diff --git a/activesupport/lib/active_support/core_ext/object/blank.rb b/activesupport/lib/active_support/core_ext/object/blank.rb
index 8a5eb4bc93..38e43478df 100644
--- a/activesupport/lib/active_support/core_ext/object/blank.rb
+++ b/activesupport/lib/active_support/core_ext/object/blank.rb
@@ -4,36 +4,42 @@ class Object
# An object is blank if it's false, empty, or a whitespace string.
# For example, '', ' ', +nil+, [], and {} are all blank.
#
- # This simplifies:
+ # This simplifies
#
- # if address.nil? || address.empty?
+ # address.nil? || address.empty?
#
- # ...to:
+ # to
#
- # if address.blank?
+ # address.blank?
+ #
+ # @return [true, false]
def blank?
- respond_to?(:empty?) ? empty? : !self
+ respond_to?(:empty?) ? !!empty? : !self
end
- # An object is present if it's not <tt>blank?</tt>.
+ # An object is present if it's not blank.
+ #
+ # @return [true, false]
def present?
!blank?
end
- # Returns object if it's <tt>present?</tt> otherwise returns +nil+.
- # <tt>object.presence</tt> is equivalent to <tt>object.present? ? object : nil</tt>.
+ # Returns the receiver if it's present otherwise returns +nil+.
+ # <tt>object.presence</tt> is equivalent to
#
- # This is handy for any representation of objects where blank is the same
- # as not present at all. For example, this simplifies a common check for
- # HTTP POST/query parameters:
+ # object.present? ? object : nil
+ #
+ # For example, something like
#
# state = params[:state] if params[:state].present?
# country = params[:country] if params[:country].present?
# region = state || country || 'US'
#
- # ...becomes:
+ # becomes
#
# region = params[:state].presence || params[:country].presence || 'US'
+ #
+ # @return [Object]
def presence
self if present?
end
@@ -43,6 +49,8 @@ class NilClass
# +nil+ is blank:
#
# nil.blank? # => true
+ #
+ # @return [true]
def blank?
true
end
@@ -52,6 +60,8 @@ class FalseClass
# +false+ is blank:
#
# false.blank? # => true
+ #
+ # @return [true]
def blank?
true
end
@@ -61,6 +71,8 @@ class TrueClass
# +true+ is not blank:
#
# true.blank? # => false
+ #
+ # @return [false]
def blank?
false
end
@@ -71,6 +83,8 @@ class Array
#
# [].blank? # => true
# [1,2,3].blank? # => false
+ #
+ # @return [true, false]
alias_method :blank?, :empty?
end
@@ -79,18 +93,28 @@ class Hash
#
# {}.blank? # => true
# { key: 'value' }.blank? # => false
+ #
+ # @return [true, false]
alias_method :blank?, :empty?
end
class String
+ BLANK_RE = /\A[[:space:]]*\z/
+
# A string is blank if it's empty or contains whitespaces only:
#
- # ''.blank? # => true
- # ' '.blank? # => true
- # ' '.blank? # => true
- # ' something here '.blank? # => false
+ # ''.blank? # => true
+ # ' '.blank? # => true
+ # "\t\n\r".blank? # => true
+ # ' blah '.blank? # => false
+ #
+ # Unicode whitespace is supported:
+ #
+ # "\u00a0".blank? # => true
+ #
+ # @return [true, false]
def blank?
- self !~ /[^[:space:]]/
+ BLANK_RE === self
end
end
@@ -99,6 +123,8 @@ class Numeric #:nodoc:
#
# 1.blank? # => false
# 0.blank? # => false
+ #
+ # @return [false]
def blank?
false
end
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 1d639f3af6..0191d2e973 100644
--- a/activesupport/lib/active_support/core_ext/object/deep_dup.rb
+++ b/activesupport/lib/active_support/core_ext/object/deep_dup.rb
@@ -8,8 +8,8 @@ class Object
# dup = object.deep_dup
# dup.instance_variable_set(:@a, 1)
#
- # object.instance_variable_defined?(:@a) #=> false
- # dup.instance_variable_defined?(:@a) #=> true
+ # object.instance_variable_defined?(:@a) # => false
+ # dup.instance_variable_defined?(:@a) # => true
def deep_dup
duplicable? ? dup : self
end
@@ -22,10 +22,10 @@ class Array
# dup = array.deep_dup
# dup[1][2] = 4
#
- # array[1][2] #=> nil
- # dup[1][2] #=> 4
+ # array[1][2] # => nil
+ # dup[1][2] # => 4
def deep_dup
- map { |it| it.deep_dup }
+ map(&:deep_dup)
end
end
@@ -36,8 +36,8 @@ class Hash
# dup = hash.deep_dup
# dup[:a][:c] = 'c'
#
- # hash[:a][:c] #=> nil
- # dup[:a][:c] #=> "c"
+ # hash[:a][:c] # => nil
+ # dup[:a][:c] # => "c"
def deep_dup
each_with_object(dup) do |(key, value), hash|
hash[key.deep_dup] = value.deep_dup
diff --git a/activesupport/lib/active_support/core_ext/object/duplicable.rb b/activesupport/lib/active_support/core_ext/object/duplicable.rb
index 9cd7485e2e..620f7b6561 100644
--- a/activesupport/lib/active_support/core_ext/object/duplicable.rb
+++ b/activesupport/lib/active_support/core_ext/object/duplicable.rb
@@ -19,7 +19,7 @@
class Object
# Can you safely dup this object?
#
- # False for +nil+, +false+, +true+, symbol, and number objects;
+ # False for +nil+, +false+, +true+, symbol, number objects;
# true otherwise.
def duplicable?
true
@@ -78,13 +78,17 @@ end
require 'bigdecimal'
class BigDecimal
- begin
- BigDecimal.new('4.56').dup
+ def duplicable?
+ true
+ end
+end
- def duplicable?
- true
- end
- rescue TypeError
- # can't dup, so use superclass implementation
+class Method
+ # Methods are not duplicable:
+ #
+ # method(:puts).duplicable? # => false
+ # method(:puts).dup # => TypeError: allocator undefined for Method
+ def duplicable?
+ false
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 b5671f66d0..55f281b213 100644
--- a/activesupport/lib/active_support/core_ext/object/inclusion.rb
+++ b/activesupport/lib/active_support/core_ext/object/inclusion.rb
@@ -12,4 +12,16 @@ class Object
rescue NoMethodError
raise ArgumentError.new("The parameter passed to #in? must respond to #include?")
end
+
+ # Returns the receiver if it's included in the argument otherwise returns +nil+.
+ # Argument must be any object which responds to +#include?+. Usage:
+ #
+ # params[:bucket_type].presence_in %w( project calendar )
+ #
+ # This will throw an ArgumentError if the argument doesn't respond to +#include?+.
+ #
+ # @return [Object]
+ def presence_in(another_object)
+ self.in?(another_object) ? self : nil
+ end
end
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 755e1c6b16..593a7a4940 100644
--- a/activesupport/lib/active_support/core_ext/object/instance_variables.rb
+++ b/activesupport/lib/active_support/core_ext/object/instance_variables.rb
@@ -23,6 +23,6 @@ class Object
#
# C.new(0, 1).instance_variable_names # => ["@y", "@x"]
def instance_variable_names
- instance_variables.map { |var| var.to_s }
+ instance_variables.map(&:to_s)
end
end
diff --git a/activesupport/lib/active_support/core_ext/object/json.rb b/activesupport/lib/active_support/core_ext/object/json.rb
new file mode 100644
index 0000000000..698b2d1920
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/object/json.rb
@@ -0,0 +1,197 @@
+# Hack to load json gem first so we can overwrite its to_json.
+require 'json'
+require 'bigdecimal'
+require 'active_support/core_ext/big_decimal/conversions' # for #to_s
+require 'active_support/core_ext/hash/except'
+require 'active_support/core_ext/hash/slice'
+require 'active_support/core_ext/object/instance_variables'
+require 'time'
+require 'active_support/core_ext/time/conversions'
+require 'active_support/core_ext/date_time/conversions'
+require 'active_support/core_ext/date/conversions'
+require 'active_support/core_ext/module/aliasing'
+
+# The JSON gem adds a few modules to Ruby core classes containing :to_json definition, overwriting
+# their default behavior. That said, we need to define the basic to_json method in all of them,
+# otherwise they will always use to_json gem implementation, which is backwards incompatible in
+# several cases (for instance, the JSON implementation for Hash does not work) with inheritance
+# and consequently classes as ActiveSupport::OrderedHash cannot be serialized to json.
+#
+# On the other hand, we should avoid conflict with ::JSON.{generate,dump}(obj). Unfortunately, the
+# JSON gem's encoder relies on its own to_json implementation to encode objects. Since it always
+# passes a ::JSON::State object as the only argument to to_json, we can detect that and forward the
+# calls to the original to_json method.
+#
+# It should be noted that when using ::JSON.{generate,dump} directly, ActiveSupport's encoder is
+# bypassed completely. This means that as_json won't be invoked and the JSON gem will simply
+# ignore any options it does not natively understand. This also means that ::JSON.{generate,dump}
+# should give exactly the same results with or without active support.
+[Object, Array, FalseClass, Float, Hash, Integer, NilClass, String, TrueClass, Enumerable].each do |klass|
+ klass.class_eval do
+ def to_json_with_active_support_encoder(options = nil)
+ if options.is_a?(::JSON::State)
+ # Called from JSON.{generate,dump}, forward it to JSON gem's to_json
+ self.to_json_without_active_support_encoder(options)
+ else
+ # to_json is being invoked directly, use ActiveSupport's encoder
+ ActiveSupport::JSON.encode(self, options)
+ end
+ end
+
+ alias_method_chain :to_json, :active_support_encoder
+ end
+end
+
+class Object
+ def as_json(options = nil) #:nodoc:
+ if respond_to?(:to_hash)
+ to_hash.as_json(options)
+ else
+ instance_values.as_json(options)
+ end
+ end
+end
+
+class Struct #:nodoc:
+ def as_json(options = nil)
+ Hash[members.zip(values)].as_json(options)
+ end
+end
+
+class TrueClass
+ def as_json(options = nil) #:nodoc:
+ self
+ end
+end
+
+class FalseClass
+ def as_json(options = nil) #:nodoc:
+ self
+ end
+end
+
+class NilClass
+ def as_json(options = nil) #:nodoc:
+ self
+ end
+end
+
+class String
+ def as_json(options = nil) #:nodoc:
+ self
+ end
+end
+
+class Symbol
+ def as_json(options = nil) #:nodoc:
+ to_s
+ end
+end
+
+class Numeric
+ def as_json(options = nil) #:nodoc:
+ self
+ end
+end
+
+class Float
+ # Encoding Infinity or NaN to JSON should return "null". The default returns
+ # "Infinity" or "NaN" which are not valid JSON.
+ def as_json(options = nil) #:nodoc:
+ finite? ? self : nil
+ end
+end
+
+class BigDecimal
+ # A BigDecimal would be naturally represented as a JSON number. Most libraries,
+ # however, parse non-integer JSON numbers directly as floats. Clients using
+ # those libraries would get in general a wrong number and no way to recover
+ # other than manually inspecting the string with the JSON code itself.
+ #
+ # That's why a JSON string is returned. The JSON literal is not numeric, but
+ # if the other end knows by contract that the data is supposed to be a
+ # BigDecimal, it still has the chance to post-process the string and get the
+ # real value.
+ def as_json(options = nil) #:nodoc:
+ finite? ? to_s : nil
+ end
+end
+
+class Regexp
+ def as_json(options = nil) #:nodoc:
+ to_s
+ end
+end
+
+module Enumerable
+ def as_json(options = nil) #:nodoc:
+ to_a.as_json(options)
+ end
+end
+
+class Range
+ def as_json(options = nil) #:nodoc:
+ to_s
+ end
+end
+
+class Array
+ def as_json(options = nil) #:nodoc:
+ map { |v| options ? v.as_json(options.dup) : v.as_json }
+ end
+end
+
+class Hash
+ def as_json(options = nil) #:nodoc:
+ # create a subset of the hash by applying :only or :except
+ subset = if options
+ if attrs = options[:only]
+ slice(*Array(attrs))
+ elsif attrs = options[:except]
+ except(*Array(attrs))
+ else
+ self
+ end
+ else
+ self
+ end
+
+ Hash[subset.map { |k, v| [k.to_s, options ? v.as_json(options.dup) : v.as_json] }]
+ end
+end
+
+class Time
+ def as_json(options = nil) #:nodoc:
+ if ActiveSupport::JSON::Encoding.use_standard_json_time_format
+ xmlschema(ActiveSupport::JSON::Encoding.time_precision)
+ else
+ %(#{strftime("%Y/%m/%d %H:%M:%S")} #{formatted_offset(false)})
+ end
+ end
+end
+
+class Date
+ def as_json(options = nil) #:nodoc:
+ if ActiveSupport::JSON::Encoding.use_standard_json_time_format
+ strftime("%Y-%m-%d")
+ else
+ strftime("%Y/%m/%d")
+ end
+ end
+end
+
+class DateTime
+ def as_json(options = nil) #:nodoc:
+ if ActiveSupport::JSON::Encoding.use_standard_json_time_format
+ xmlschema(ActiveSupport::JSON::Encoding.time_precision)
+ else
+ strftime('%Y/%m/%d %H:%M:%S %z')
+ end
+ end
+end
+
+class Process::Status #:nodoc:
+ def as_json(options = nil)
+ { :exitstatus => exitstatus, :pid => pid }
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/object/to_json.rb b/activesupport/lib/active_support/core_ext/object/to_json.rb
deleted file mode 100644
index 83cc8066e7..0000000000
--- a/activesupport/lib/active_support/core_ext/object/to_json.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-# Hack to load json gem first so we can overwrite its to_json.
-begin
- require 'json'
-rescue LoadError
-end
-
-# The JSON gem adds a few modules to Ruby core classes containing :to_json definition, overwriting
-# their default behavior. That said, we need to define the basic to_json method in all of them,
-# otherwise they will always use to_json gem implementation, which is backwards incompatible in
-# several cases (for instance, the JSON implementation for Hash does not work) with inheritance
-# and consequently classes as ActiveSupport::OrderedHash cannot be serialized to json.
-[Object, Array, FalseClass, Float, Hash, Integer, NilClass, String, TrueClass].each do |klass|
- klass.class_eval do
- # Dumps object in JSON (JavaScript Object Notation). See www.json.org for more info.
- def to_json(options = nil)
- ActiveSupport::JSON.encode(self, options)
- end
- end
-end
-
-module Process
- class Status
- def as_json(options = nil)
- { :exitstatus => exitstatus, :pid => pid }
- end
- end
-end
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 3b137ce6ae..684d4ef57e 100644
--- a/activesupport/lib/active_support/core_ext/object/to_param.rb
+++ b/activesupport/lib/active_support/core_ext/object/to_param.rb
@@ -1,58 +1 @@
-class Object
- # Alias of <tt>to_s</tt>.
- def to_param
- to_s
- end
-end
-
-class NilClass
- # Returns +self+.
- def to_param
- self
- end
-end
-
-class TrueClass
- # Returns +self+.
- def to_param
- self
- end
-end
-
-class FalseClass
- # Returns +self+.
- def to_param
- self
- end
-end
-
-class Array
- # Calls <tt>to_param</tt> on all its elements and joins the result with
- # slashes. This is used by <tt>url_for</tt> in Action Pack.
- def to_param
- collect { |e| e.to_param }.join '/'
- end
-end
-
-class Hash
- # Returns a string representation of the receiver suitable for use as a URL
- # query string:
- #
- # {name: 'David', nationality: 'Danish'}.to_param
- # # => "name=David&nationality=Danish"
- #
- # An optional namespace can be passed to enclose the param names:
- #
- # {name: 'David', nationality: 'Danish'}.to_param('user')
- # # => "user[name]=David&user[nationality]=Danish"
- #
- # The string pairs "key=value" that conform the query string
- # are sorted lexicographically in ascending order.
- #
- # This method is also aliased as +to_query+.
- def to_param(namespace = nil)
- collect do |key, value|
- value.to_query(namespace ? "#{namespace}[#{key}]" : key)
- end.sort! * '&'
- end
-end
+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 5d5fcf00e0..ec5ace4e16 100644
--- a/activesupport/lib/active_support/core_ext/object/to_query.rb
+++ b/activesupport/lib/active_support/core_ext/object/to_query.rb
@@ -1,27 +1,84 @@
-require 'active_support/core_ext/object/to_param'
+require 'cgi'
class Object
- # Converts an object into a string suitable for use as a URL query string, using the given <tt>key</tt> as the
- # param name.
- #
- # Note: This method is defined as a default implementation for all Objects for Hash#to_query to work.
+ # Alias of <tt>to_s</tt>.
+ def to_param
+ to_s
+ end
+
+ # Converts an object into a string suitable for use as a URL query string,
+ # using the given <tt>key</tt> as the param name.
def to_query(key)
- require 'cgi' unless defined?(CGI) && defined?(CGI::escape)
"#{CGI.escape(key.to_param)}=#{CGI.escape(to_param.to_s)}"
end
end
+class NilClass
+ # Returns +self+.
+ def to_param
+ self
+ end
+end
+
+class TrueClass
+ # Returns +self+.
+ def to_param
+ self
+ end
+end
+
+class FalseClass
+ # Returns +self+.
+ def to_param
+ self
+ end
+end
+
class Array
+ # Calls <tt>to_param</tt> on all its elements and joins the result with
+ # slashes. This is used by <tt>url_for</tt> in Action Pack.
+ def to_param
+ collect(&:to_param).join '/'
+ end
+
# Converts an array into a string suitable for use as a URL query string,
# using the given +key+ as the param name.
#
# ['Rails', 'coding'].to_query('hobbies') # => "hobbies%5B%5D=Rails&hobbies%5B%5D=coding"
def to_query(key)
prefix = "#{key}[]"
- collect { |value| value.to_query(prefix) }.join '&'
+
+ if empty?
+ nil.to_query(prefix)
+ else
+ collect { |value| value.to_query(prefix) }.join '&'
+ end
end
end
class Hash
- alias_method :to_query, :to_param
+ # Returns a string representation of the receiver suitable for use as a URL
+ # query string:
+ #
+ # {name: 'David', nationality: 'Danish'}.to_query
+ # # => "name=David&nationality=Danish"
+ #
+ # An optional namespace can be passed to enclose key names:
+ #
+ # {name: 'David', nationality: 'Danish'}.to_query('user')
+ # # => "user%5Bname%5D=David&user%5Bnationality%5D=Danish"
+ #
+ # The string pairs "key=value" that conform the query string
+ # are sorted lexicographically in ascending order.
+ #
+ # This method is also aliased as +to_param+.
+ def to_query(namespace = nil)
+ collect do |key, value|
+ unless (value.is_a?(Hash) || value.is_a?(Array)) && value.empty?
+ value.to_query(namespace ? "#{namespace}[#{key}]" : key)
+ end
+ end.compact.sort! * '&'
+ end
+
+ alias_method :to_param, :to_query
end
diff --git a/activesupport/lib/active_support/core_ext/object/try.rb b/activesupport/lib/active_support/core_ext/object/try.rb
index 48190e1e66..e0f70b9caa 100644
--- a/activesupport/lib/active_support/core_ext/object/try.rb
+++ b/activesupport/lib/active_support/core_ext/object/try.rb
@@ -9,7 +9,23 @@ class Object
#
# instead of
#
- # @person ? @person.name : nil
+ # @person.name if @person
+ #
+ # +try+ calls can be chained:
+ #
+ # @person.try(:spouse).try(:name)
+ #
+ # instead of
+ #
+ # @person.spouse.name if @person && @person.spouse
+ #
+ # +try+ will also return +nil+ if the receiver does not respond to the method:
+ #
+ # @person.try(:non_existing_method) # => nil
+ #
+ # instead of
+ #
+ # @person.non_existing_method if @person.respond_to?(:non_existing_method) # => nil
#
# +try+ returns +nil+ when called on +nil+ regardless of whether it responds
# to the method:
@@ -24,7 +40,7 @@ class Object
#
# The number of arguments in the signature must match. If the object responds
# to the method the call is attempted and +ArgumentError+ is still raised
- # otherwise.
+ # in case of argument mismatch.
#
# If +try+ is called without arguments it yields the receiver to a given
# block unless it is +nil+:
@@ -33,24 +49,33 @@ class Object
# ...
# end
#
- # Please also note that +try+ is defined on +Object+, therefore it won't work
+ # You can also call try with a block without accepting an argument, and the block
+ # will be instance_eval'ed instead:
+ #
+ # @person.try { upcase.truncate(50) }
+ #
+ # Please also note that +try+ is defined on +Object+. Therefore, it won't work
# with instances of classes that do not have +Object+ among their ancestors,
# like direct subclasses of +BasicObject+. For example, using +try+ with
# +SimpleDelegator+ will delegate +try+ to the target instead of calling it on
- # delegator itself.
+ # the delegator itself.
def try(*a, &b)
- if a.empty? && block_given?
- yield self
- else
- public_send(*a, &b) if respond_to?(a.first)
- end
+ try!(*a, &b) if a.empty? || respond_to?(a.first)
end
- # Same as #try, but will raise a NoMethodError exception if the receiving is not nil and
- # does not implement the tried method.
+ # Same as #try, but raises a NoMethodError exception if the receiver is
+ # not +nil+ and does not implement the tried method.
+ #
+ # "a".try!(:upcase) # => "A"
+ # nil.try!(:upcase) # => nil
+ # 123.try!(:upcase) # => NoMethodError: undefined method `upcase' for 123:Fixnum
def try!(*a, &b)
if a.empty? && block_given?
- yield self
+ if b.arity.zero?
+ instance_eval(&b)
+ else
+ yield self
+ end
else
public_send(*a, &b)
end
@@ -59,12 +84,12 @@ end
class NilClass
# Calling +try+ on +nil+ always returns +nil+.
- # It becomes specially helpful when navigating through associations that may return +nil+.
+ # It becomes especially helpful when navigating through associations that may return +nil+.
#
# nil.try(:name) # => nil
#
# Without +try+
- # @person && !@person.children.blank? && @person.children.first.name
+ # @person && @person.children.any? && @person.children.first.name
#
# With +try+
# @person.try(:children).try(:first).try(:name)
@@ -72,6 +97,9 @@ class NilClass
nil
end
+ # Calling +try!+ on +nil+ always returns +nil+.
+ #
+ # nil.try!(:name) # => nil
def try!(*args)
nil
end
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 42e388b065..7d38e1d134 100644
--- a/activesupport/lib/active_support/core_ext/object/with_options.rb
+++ b/activesupport/lib/active_support/core_ext/object/with_options.rb
@@ -34,9 +34,36 @@ class Object
# body i18n.t :body, user_name: user.name
# end
#
+ # When you don't pass an explicit receiver, it executes the whole block
+ # in merging options context:
+ #
+ # class Account < ActiveRecord::Base
+ # with_options dependent: :destroy do
+ # has_many :customers
+ # has_many :products
+ # has_many :invoices
+ # has_many :expenses
+ # end
+ # end
+ #
# <tt>with_options</tt> can also be nested since the call is forwarded to its receiver.
- # Each nesting level will merge inherited defaults in addition to their own.
- def with_options(options)
- yield ActiveSupport::OptionMerger.new(self, options)
+ #
+ # NOTE: Each nesting level will merge inherited defaults in addition to their own.
+ #
+ # class Post < ActiveRecord::Base
+ # with_options if: :persisted?, length: { minimum: 50 } do
+ # validates :content, if: -> { content.present? }
+ # end
+ # end
+ #
+ # The code is equivalent to:
+ #
+ # validates :content, length: { minimum: 50 }, if: -> { content.present? }
+ #
+ # Hence the inherited default for `if` key is ignored.
+ #
+ def with_options(options, &block)
+ option_merger = ActiveSupport::OptionMerger.new(self, options)
+ block.arity.zero? ? option_merger.instance_eval(&block) : block.call(option_merger)
end
end
diff --git a/activesupport/lib/active_support/core_ext/range.rb b/activesupport/lib/active_support/core_ext/range.rb
index 1d8b1ede5a..9368e81235 100644
--- a/activesupport/lib/active_support/core_ext/range.rb
+++ b/activesupport/lib/active_support/core_ext/range.rb
@@ -1,3 +1,4 @@
require 'active_support/core_ext/range/conversions'
require 'active_support/core_ext/range/include_range'
require 'active_support/core_ext/range/overlaps'
+require 'active_support/core_ext/range/each'
diff --git a/activesupport/lib/active_support/core_ext/range/each.rb b/activesupport/lib/active_support/core_ext/range/each.rb
new file mode 100644
index 0000000000..ecef78f55f
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/range/each.rb
@@ -0,0 +1,23 @@
+require 'active_support/core_ext/module/aliasing'
+
+class Range #:nodoc:
+
+ def each_with_time_with_zone(&block)
+ ensure_iteration_allowed
+ each_without_time_with_zone(&block)
+ end
+ alias_method_chain :each, :time_with_zone
+
+ def step_with_time_with_zone(n = 1, &block)
+ ensure_iteration_allowed
+ step_without_time_with_zone(n, &block)
+ end
+ alias_method_chain :step, :time_with_zone
+
+ private
+ def ensure_iteration_allowed
+ if first.is_a?(Time)
+ raise TypeError, "can't iterate from #{first.class}"
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/securerandom.rb b/activesupport/lib/active_support/core_ext/securerandom.rb
new file mode 100644
index 0000000000..6cdbea1f37
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/securerandom.rb
@@ -0,0 +1,23 @@
+require 'securerandom'
+
+module SecureRandom
+ BASE58_ALPHABET = ('0'..'9').to_a + ('A'..'Z').to_a + ('a'..'z').to_a - ['0', 'O', 'I', 'l']
+ # SecureRandom.base58 generates a random base58 string.
+ #
+ # The argument _n_ specifies the length, of the random string to be generated.
+ #
+ # If _n_ is not specified or is nil, 16 is assumed. It may be larger in the future.
+ #
+ # The result may contain alphanumeric characters except 0, O, I and l
+ #
+ # p SecureRandom.base58 #=> "4kUgL2pdQMSCQtjE"
+ # p SecureRandom.base58(24) #=> "77TMHrHJFvFDwodq8w7Ev2m7"
+ #
+ def self.base58(n = 16)
+ SecureRandom.random_bytes(n).unpack("C*").map do |byte|
+ idx = byte % 64
+ idx = SecureRandom.random_number(58) if idx >= 58
+ BASE58_ALPHABET[idx]
+ end.join
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/string/access.rb b/activesupport/lib/active_support/core_ext/string/access.rb
index ee3b6d2b3f..ebd0dd3fc7 100644
--- a/activesupport/lib/active_support/core_ext/string/access.rb
+++ b/activesupport/lib/active_support/core_ext/string/access.rb
@@ -8,22 +8,22 @@ class String
# the beginning of the range is greater than the end of the string.
#
# str = "hello"
- # str.at(0) #=> "h"
- # str.at(1..3) #=> "ell"
- # str.at(-2) #=> "l"
- # str.at(-2..-1) #=> "lo"
- # str.at(5) #=> nil
- # str.at(5..-1) #=> ""
+ # str.at(0) # => "h"
+ # str.at(1..3) # => "ell"
+ # str.at(-2) # => "l"
+ # str.at(-2..-1) # => "lo"
+ # str.at(5) # => nil
+ # str.at(5..-1) # => ""
#
# If a Regexp is given, the matching portion of the string is returned.
# If a String is given, that given string is returned if it occurs in
# the string. In both cases, nil is returned if there is no match.
#
# str = "hello"
- # str.at(/lo/) #=> "lo"
- # str.at(/ol/) #=> nil
- # str.at("lo") #=> "lo"
- # str.at("ol") #=> nil
+ # str.at(/lo/) # => "lo"
+ # str.at(/ol/) # => nil
+ # str.at("lo") # => "lo"
+ # str.at("ol") # => nil
def at(position)
self[position]
end
@@ -32,15 +32,15 @@ class String
# If the position is negative, it is counted from the end of the string.
#
# str = "hello"
- # str.from(0) #=> "hello"
- # str.from(3) #=> "lo"
- # str.from(-2) #=> "lo"
+ # str.from(0) # => "hello"
+ # str.from(3) # => "lo"
+ # str.from(-2) # => "lo"
#
# You can mix it with +to+ method and do fun things like:
#
# str = "hello"
- # str.from(0).to(-1) #=> "hello"
- # str.from(1).to(-2) #=> "ell"
+ # str.from(0).to(-1) # => "hello"
+ # str.from(1).to(-2) # => "ell"
def from(position)
self[position..-1]
end
@@ -49,34 +49,34 @@ class String
# If the position is negative, it is counted from the end of the string.
#
# str = "hello"
- # str.to(0) #=> "h"
- # str.to(3) #=> "hell"
- # str.to(-2) #=> "hell"
+ # str.to(0) # => "h"
+ # str.to(3) # => "hell"
+ # str.to(-2) # => "hell"
#
# You can mix it with +from+ method and do fun things like:
#
# str = "hello"
- # str.from(0).to(-1) #=> "hello"
- # str.from(1).to(-2) #=> "ell"
+ # str.from(0).to(-1) # => "hello"
+ # str.from(1).to(-2) # => "ell"
def to(position)
- self[0, position + 1]
+ self[0..position]
end
# Returns the first character. If a limit is supplied, returns a substring
# from the beginning of the string until it reaches the limit value. If the
- # given limit is greater than or equal to the string length, returns self.
+ # given limit is greater than or equal to the string length, returns a copy of self.
#
# str = "hello"
- # str.first #=> "h"
- # str.first(1) #=> "h"
- # str.first(2) #=> "he"
- # str.first(0) #=> ""
- # str.first(6) #=> "hello"
+ # str.first # => "h"
+ # str.first(1) # => "h"
+ # str.first(2) # => "he"
+ # str.first(0) # => ""
+ # str.first(6) # => "hello"
def first(limit = 1)
if limit == 0
''
elsif limit >= size
- self
+ self.dup
else
to(limit - 1)
end
@@ -84,19 +84,19 @@ class String
# Returns the last character of the string. If a limit is supplied, returns a substring
# from the end of the string until it reaches the limit value (counting backwards). If
- # the given limit is greater than or equal to the string length, returns self.
+ # the given limit is greater than or equal to the string length, returns a copy of self.
#
# str = "hello"
- # str.last #=> "o"
- # str.last(1) #=> "o"
- # str.last(2) #=> "lo"
- # str.last(0) #=> ""
- # str.last(6) #=> "hello"
+ # str.last # => "o"
+ # str.last(1) # => "o"
+ # str.last(2) # => "lo"
+ # str.last(0) # => ""
+ # str.last(6) # => "hello"
def last(limit = 1)
if limit == 0
''
elsif limit >= size
- self
+ self.dup
else
from(-limit)
end
diff --git a/activesupport/lib/active_support/core_ext/string/conversions.rb b/activesupport/lib/active_support/core_ext/string/conversions.rb
index 6691fc0995..3e0cb8a7ac 100644
--- a/activesupport/lib/active_support/core_ext/string/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/string/conversions.rb
@@ -36,20 +36,20 @@ class String
# Converts a string to a Date value.
#
- # "1-1-2012".to_date #=> Sun, 01 Jan 2012
- # "01/01/2012".to_date #=> Sun, 01 Jan 2012
- # "2012-12-13".to_date #=> Thu, 13 Dec 2012
- # "12/13/2012".to_date #=> ArgumentError: invalid date
+ # "1-1-2012".to_date # => Sun, 01 Jan 2012
+ # "01/01/2012".to_date # => Sun, 01 Jan 2012
+ # "2012-12-13".to_date # => Thu, 13 Dec 2012
+ # "12/13/2012".to_date # => ArgumentError: invalid date
def to_date
::Date.parse(self, false) unless blank?
end
# Converts a string to a DateTime value.
#
- # "1-1-2012".to_datetime #=> Sun, 01 Jan 2012 00:00:00 +0000
- # "01/01/2012 23:59:59".to_datetime #=> Sun, 01 Jan 2012 23:59:59 +0000
- # "2012-12-13 12:50".to_datetime #=> Thu, 13 Dec 2012 12:50:00 +0000
- # "12/13/2012".to_datetime #=> ArgumentError: invalid date
+ # "1-1-2012".to_datetime # => Sun, 01 Jan 2012 00:00:00 +0000
+ # "01/01/2012 23:59:59".to_datetime # => Sun, 01 Jan 2012 23:59:59 +0000
+ # "2012-12-13 12:50".to_datetime # => Thu, 13 Dec 2012 12:50:00 +0000
+ # "12/13/2012".to_datetime # => ArgumentError: invalid date
def to_datetime
::DateTime.parse(self, false) unless blank?
end
diff --git a/activesupport/lib/active_support/core_ext/string/exclude.rb b/activesupport/lib/active_support/core_ext/string/exclude.rb
index 114bcb87f0..0ac684f6ee 100644
--- a/activesupport/lib/active_support/core_ext/string/exclude.rb
+++ b/activesupport/lib/active_support/core_ext/string/exclude.rb
@@ -2,9 +2,9 @@ class String
# The inverse of <tt>String#include?</tt>. Returns true if the string
# does not include the other string.
#
- # "hello".exclude? "lo" #=> false
- # "hello".exclude? "ol" #=> true
- # "hello".exclude? ?h #=> false
+ # "hello".exclude? "lo" # => false
+ # "hello".exclude? "ol" # => true
+ # "hello".exclude? ?h # => false
def exclude?(string)
!include?(string)
end
diff --git a/activesupport/lib/active_support/core_ext/string/filters.rb b/activesupport/lib/active_support/core_ext/string/filters.rb
index 2e2dd86a8b..096292dc58 100644
--- a/activesupport/lib/active_support/core_ext/string/filters.rb
+++ b/activesupport/lib/active_support/core_ext/string/filters.rb
@@ -3,7 +3,7 @@ class String
# the string, and then changing remaining consecutive whitespace
# groups into one space each.
#
- # Note that it handles both ASCII and Unicode whitespace like mongolian vowel separator (U+180E).
+ # Note that it handles both ASCII and Unicode whitespace.
#
# %{ Multi-line
# string }.squish # => "Multi-line string"
@@ -13,6 +13,9 @@ class String
end
# Performs a destructive squish. See String#squish.
+ # str = " foo bar \n \t boo"
+ # str.squish! # => "foo bar boo"
+ # str # => "foo bar boo"
def squish!
gsub!(/\A[[:space:]]+/, '')
gsub!(/[[:space:]]+\z/, '')
@@ -20,14 +23,24 @@ class String
self
end
- # Returns a new string with all occurances of the pattern removed. Short-hand for String#gsub(pattern, '').
- def remove(pattern)
- gsub pattern, ''
+ # Returns a new string with all occurrences of the patterns removed.
+ # str = "foo bar test"
+ # str.remove(" test") # => "foo bar"
+ # str # => "foo bar test"
+ def remove(*patterns)
+ dup.remove!(*patterns)
end
- # Alters the string by removing all occurances of the pattern. Short-hand for String#gsub!(pattern, '').
- def remove!(pattern)
- gsub! pattern, ''
+ # Alters the string by removing all occurrences of the patterns.
+ # str = "foo bar test"
+ # str.remove!(" test") # => "foo bar"
+ # str # => "foo bar"
+ def remove!(*patterns)
+ patterns.each do |pattern|
+ gsub! pattern, ""
+ end
+
+ self
end
# Truncates a given +text+ after a given <tt>length</tt> if +text+ is longer than <tt>length</tt>:
@@ -62,4 +75,28 @@ class String
"#{self[0, stop]}#{omission}"
end
+
+ # Truncates a given +text+ after a given number of words (<tt>words_count</tt>):
+ #
+ # 'Once upon a time in a world far far away'.truncate_words(4)
+ # # => "Once upon a time..."
+ #
+ # Pass a string or regexp <tt>:separator</tt> to specify a different separator of words:
+ #
+ # 'Once<br>upon<br>a<br>time<br>in<br>a<br>world'.truncate_words(5, separator: '<br>')
+ # # => "Once<br>upon<br>a<br>time<br>in..."
+ #
+ # The last characters will be replaced with the <tt>:omission</tt> string (defaults to "..."):
+ #
+ # 'And they found that many people were sleeping better.'.truncate_words(5, omission: '... (continued)')
+ # # => "And they found that many... (continued)"
+ def truncate_words(words_count, options = {})
+ sep = options[:separator] || /\s+/
+ sep = Regexp.escape(sep.to_s) unless Regexp === sep
+ if self =~ /\A((?:.+?#{sep}){#{words_count - 1}}.+?)#{sep}.*/m
+ $1 + (options[:omission] || '...')
+ else
+ dup
+ end
+ end
end
diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb
index 56e8a5f98d..38d567c014 100644
--- a/activesupport/lib/active_support/core_ext/string/inflections.rb
+++ b/activesupport/lib/active_support/core_ext/string/inflections.rb
@@ -31,7 +31,7 @@ class String
def pluralize(count = nil, locale = :en)
locale = count if count.is_a?(Symbol)
if count == 1
- self
+ self.dup
else
ActiveSupport::Inflector.pluralize(self, locale)
end
@@ -130,6 +130,8 @@ class String
#
# 'ActiveRecord::CoreExtensions::String::Inflections'.demodulize # => "Inflections"
# 'Inflections'.demodulize # => "Inflections"
+ # '::Inflections'.demodulize # => "Inflections"
+ # ''.demodulize # => ''
#
# See also +deconstantize+.
def demodulize
@@ -182,21 +184,24 @@ class String
#
# 'egg_and_hams'.classify # => "EggAndHam"
# 'posts'.classify # => "Post"
- #
- # Singular names are not handled correctly.
- #
- # 'business'.classify # => "Business"
def classify
ActiveSupport::Inflector.classify(self)
end
- # Capitalizes the first word, turns underscores into spaces, and strips '_id'.
+ # Capitalizes the first word, turns underscores into spaces, and strips a
+ # trailing '_id' if present.
# Like +titleize+, this is meant for creating pretty output.
#
- # 'employee_salary'.humanize # => "Employee salary"
- # 'author_id'.humanize # => "Author"
- def humanize
- ActiveSupport::Inflector.humanize(self)
+ # The capitalization of the first word can be turned off by setting the
+ # 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)
end
# Creates a foreign key name from a class name.
diff --git a/activesupport/lib/active_support/core_ext/string/multibyte.rb b/activesupport/lib/active_support/core_ext/string/multibyte.rb
index a124202936..2eedd4fdb1 100644
--- a/activesupport/lib/active_support/core_ext/string/multibyte.rb
+++ b/activesupport/lib/active_support/core_ext/string/multibyte.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'active_support/multibyte'
class String
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 dc033ed11b..ff5712ed5d 100644
--- a/activesupport/lib/active_support/core_ext/string/output_safety.rb
+++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb
@@ -4,9 +4,10 @@ require 'active_support/core_ext/kernel/singleton_class'
class ERB
module Util
HTML_ESCAPE = { '&' => '&amp;', '>' => '&gt;', '<' => '&lt;', '"' => '&quot;', "'" => '&#39;' }
- JSON_ESCAPE = { '&' => '\u0026', '>' => '\u003E', '<' => '\u003C' }
- HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+));)/
- JSON_ESCAPE_REGEXP = /[&"><]/
+ JSON_ESCAPE = { '&' => '\u0026', '>' => '\u003e', '<' => '\u003c', "\u2028" => '\u2028', "\u2029" => '\u2029' }
+ HTML_ESCAPE_REGEXP = /[&"'><]/
+ HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+)|(#[xX][\dA-Fa-f]+));)/
+ JSON_ESCAPE_REGEXP = /[\u2028\u2029&><]/u
# A utility method for escaping HTML tag characters.
# This method is also aliased as <tt>h</tt>.
@@ -17,12 +18,7 @@ class ERB
# puts html_escape('is a > 0 & a < 10?')
# # => is a &gt; 0 &amp; a &lt; 10?
def html_escape(s)
- s = s.to_s
- if s.html_safe?
- s
- else
- s.gsub(/[&"'><]/, HTML_ESCAPE).html_safe
- end
+ unwrapped_html_escape(s).html_safe
end
# Aliasing twice issues a warning "discarding old...". Remove first to avoid it.
@@ -34,6 +30,18 @@ class ERB
singleton_class.send(:remove_method, :html_escape)
module_function :html_escape
+ # HTML escapes strings but doesn't wrap them with an ActiveSupport::SafeBuffer.
+ # This method is not for public consumption! Seriously!
+ def unwrapped_html_escape(s) # :nodoc:
+ s = s.to_s
+ if s.html_safe?
+ s
+ else
+ s.gsub(HTML_ESCAPE_REGEXP, HTML_ESCAPE)
+ end
+ end
+ module_function :unwrapped_html_escape
+
# A utility method for escaping HTML without affecting existing escaped entities.
#
# html_escape_once('1 < 2 &amp; 3')
@@ -48,17 +56,61 @@ class ERB
module_function :html_escape_once
- # A utility method for escaping HTML entities in JSON strings
- # using \uXXXX JavaScript escape sequences for string literals:
+ # A utility method for escaping HTML entities in JSON strings. Specifically, the
+ # &, > and < characters are replaced with their equivalent unicode escaped form -
+ # \u0026, \u003e, and \u003c. The Unicode sequences \u2028 and \u2029 are also
+ # escaped as they are treated as newline characters in some JavaScript engines.
+ # These sequences have identical meaning as the original characters inside the
+ # context of a JSON string, so assuming the input is a valid and well-formed
+ # JSON value, the output will have equivalent meaning when parsed:
+ #
+ # json = JSON.generate({ name: "</script><script>alert('PWNED!!!')</script>"})
+ # # => "{\"name\":\"</script><script>alert('PWNED!!!')</script>\"}"
+ #
+ # json_escape(json)
+ # # => "{\"name\":\"\\u003C/script\\u003E\\u003Cscript\\u003Ealert('PWNED!!!')\\u003C/script\\u003E\"}"
+ #
+ # JSON.parse(json) == JSON.parse(json_escape(json))
+ # # => true
+ #
+ # The intended use case for this method is to escape JSON strings before including
+ # them inside a script tag to avoid XSS vulnerability:
+ #
+ # <script>
+ # var currentUser = <%= raw json_escape(current_user.to_json) %>;
+ # </script>
#
- # json_escape('is a > 0 & a < 10?')
- # # => is a \u003E 0 \u0026 a \u003C 10?
+ # It is necessary to +raw+ the result of +json_escape+, so that quotation marks
+ # don't get converted to <tt>&quot;</tt> entities. +json_escape+ doesn't
+ # automatically flag the result as HTML safe, since the raw value is unsafe to
+ # use inside HTML attributes.
#
- # Note that after this operation is performed the output is not
- # valid JSON. In particular double quotes are removed:
+ # If your JSON is being used downstream for insertion into the DOM, be aware of
+ # whether or not it is being inserted via +html()+. Most JQuery plugins do this.
+ # If that is the case, be sure to +html_escape+ or +sanitize+ any user-generated
+ # content returned by your JSON.
#
- # json_escape('{"name":"john","created_at":"2010-04-28T01:39:31Z","id":1}')
- # # => {name:john,created_at:2010-04-28T01:39:31Z,id:1}
+ # If you need to output JSON elsewhere in your HTML, you can just do something
+ # like this, as any unsafe characters (including quotation marks) will be
+ # automatically escaped for you:
+ #
+ # <div data-user-info="<%= current_user.to_json %>">...</div>
+ #
+ # WARNING: this helper only works with valid JSON. Using this on non-JSON values
+ # will open up serious XSS vulnerabilities. For example, if you replace the
+ # +current_user.to_json+ in the example above with user input instead, the browser
+ # will happily eval() that string as JavaScript.
+ #
+ # The escaping performed in this method is identical to those performed in the
+ # Active Support JSON encoder when +ActiveSupport.escape_html_entities_in_json+ is
+ # set to true. Because this transformation is idempotent, this helper can be
+ # applied even if +ActiveSupport.escape_html_entities_in_json+ is already true.
+ #
+ # Therefore, when you are unsure if +ActiveSupport.escape_html_entities_in_json+
+ # is enabled, or if you are unsure where your JSON string originated from, it
+ # is recommended that you always apply this helper (other libraries, such as the
+ # JSON gem, do not provide this kind of protection by default; also some gems
+ # might override +to_json+ to bypass Active Support's encoder).
def json_escape(s)
result = s.to_s.gsub(JSON_ESCAPE_REGEXP, JSON_ESCAPE)
s.html_safe? ? result.html_safe : result
@@ -84,7 +136,7 @@ module ActiveSupport #:nodoc:
class SafeBuffer < String
UNSAFE_STRING_METHODS = %w(
capitalize chomp chop delete downcase gsub lstrip next reverse rstrip
- slice squeeze strip sub succ swapcase tr tr_s upcase prepend
+ slice squeeze strip sub succ swapcase tr tr_s upcase
)
alias_method :original_concat, :concat
@@ -102,7 +154,11 @@ module ActiveSupport #:nodoc:
else
if html_safe?
new_safe_buffer = super
- new_safe_buffer.instance_eval { @html_safe = true }
+
+ if new_safe_buffer
+ new_safe_buffer.instance_variable_set :@html_safe, true
+ end
+
new_safe_buffer
else
to_str[*args]
@@ -130,28 +186,27 @@ module ActiveSupport #:nodoc:
end
def concat(value)
- if !html_safe? || value.html_safe?
- super(value)
- else
- super(ERB::Util.h(value))
- end
+ super(html_escape_interpolated_argument(value))
end
alias << concat
+ def prepend(value)
+ super(html_escape_interpolated_argument(value))
+ end
+
def +(other)
dup.concat(other)
end
def %(args)
- args = Array(args).map do |arg|
- if !html_safe? || arg.html_safe?
- arg
- else
- ERB::Util.h(arg)
- end
+ case args
+ when Hash
+ escaped_args = Hash[args.map { |k,arg| [k, html_escape_interpolated_argument(arg)] }]
+ else
+ escaped_args = Array(args).map { |arg| html_escape_interpolated_argument(arg) }
end
- self.class.new(super(args))
+ self.class.new(super(escaped_args))
end
def html_safe?
@@ -171,7 +226,7 @@ module ActiveSupport #:nodoc:
end
UNSAFE_STRING_METHODS.each do |unsafe_method|
- if 'String'.respond_to?(unsafe_method)
+ if unsafe_method.respond_to?(unsafe_method)
class_eval <<-EOT, __FILE__, __LINE__ + 1
def #{unsafe_method}(*args, &block) # def capitalize(*args, &block)
to_str.#{unsafe_method}(*args, &block) # to_str.capitalize(*args, &block)
@@ -184,10 +239,22 @@ module ActiveSupport #:nodoc:
EOT
end
end
+
+ private
+
+ def html_escape_interpolated_argument(arg)
+ (!html_safe? || arg.html_safe?) ? arg :
+ arg.to_s.gsub(ERB::Util::HTML_ESCAPE_REGEXP, ERB::Util::HTML_ESCAPE)
+ end
end
end
class String
+ # Marks a string as trusted safe. It will be inserted into HTML with no
+ # additional escaping performed. It is your responsibilty 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
+ # this method. It should never be called on user input.
def html_safe
ActiveSupport::SafeBuffer.new(self)
end
diff --git a/activesupport/lib/active_support/core_ext/struct.rb b/activesupport/lib/active_support/core_ext/struct.rb
index c2c30044f2..1fde3db070 100644
--- a/activesupport/lib/active_support/core_ext/struct.rb
+++ b/activesupport/lib/active_support/core_ext/struct.rb
@@ -1,6 +1,3 @@
-# Backport of Struct#to_h from Ruby 2.0
-class Struct # :nodoc:
- def to_h
- Hash[members.zip(values)]
- end
-end unless Struct.instance_methods.include?(:to_h)
+require 'active_support/deprecation'
+
+ActiveSupport::Deprecation.warn("This file is deprecated and will be removed in Rails 5.1 with no replacement.")
diff --git a/activesupport/lib/active_support/core_ext/thread.rb b/activesupport/lib/active_support/core_ext/thread.rb
deleted file mode 100644
index 5481766f10..0000000000
--- a/activesupport/lib/active_support/core_ext/thread.rb
+++ /dev/null
@@ -1,74 +0,0 @@
-class Thread
- LOCK = Mutex.new # :nodoc:
-
- # Returns the value of a thread local variable that has been set. Note that
- # these are different than fiber local values.
- #
- # Thread local values are carried along with threads, and do not respect
- # fibers. For example:
- #
- # Thread.new {
- # Thread.current.thread_variable_set("foo", "bar") # set a thread local
- # Thread.current["foo"] = "bar" # set a fiber local
- #
- # Fiber.new {
- # Fiber.yield [
- # Thread.current.thread_variable_get("foo"), # get the thread local
- # Thread.current["foo"], # get the fiber local
- # ]
- # }.resume
- # }.join.value # => ['bar', nil]
- #
- # The value <tt>"bar"</tt> is returned for the thread local, where +nil+ is returned
- # for the fiber local. The fiber is executed in the same thread, so the
- # thread local values are available.
- def thread_variable_get(key)
- locals[key.to_sym]
- end
-
- # Sets a thread local with +key+ to +value+. Note that these are local to
- # threads, and not to fibers. Please see Thread#thread_variable_get for
- # more information.
- def thread_variable_set(key, value)
- locals[key.to_sym] = value
- end
-
- # Returns an an array of the names of the thread-local variables (as Symbols).
- #
- # thr = Thread.new do
- # Thread.current.thread_variable_set(:cat, 'meow')
- # Thread.current.thread_variable_set("dog", 'woof')
- # end
- # thr.join #=> #<Thread:0x401b3f10 dead>
- # thr.thread_variables #=> [:dog, :cat]
- #
- # Note that these are not fiber local variables. Please see Thread#thread_variable_get
- # for more details.
- def thread_variables
- locals.keys
- end
-
- # Returns <tt>true</tt> if the given string (or symbol) exists as a
- # thread-local variable.
- #
- # me = Thread.current
- # me.thread_variable_set(:oliver, "a")
- # me.thread_variable?(:oliver) #=> true
- # me.thread_variable?(:stanley) #=> false
- #
- # Note that these are not fiber local variables. Please see Thread#thread_variable_get
- # for more details.
- def thread_variable?(key)
- locals.has_key?(key.to_sym)
- end
-
- private
-
- def locals
- if defined?(@locals)
- @locals
- else
- LOCK.synchronize { @locals ||= {} }
- end
- end
-end unless Thread.instance_methods.include?(:thread_variable_set)
diff --git a/activesupport/lib/active_support/core_ext/time.rb b/activesupport/lib/active_support/core_ext/time.rb
index 32cffe237d..72c3234630 100644
--- a/activesupport/lib/active_support/core_ext/time.rb
+++ b/activesupport/lib/active_support/core_ext/time.rb
@@ -1,5 +1,4 @@
require 'active_support/core_ext/time/acts_like'
require 'active_support/core_ext/time/calculations'
require 'active_support/core_ext/time/conversions'
-require 'active_support/core_ext/time/marshal'
require 'active_support/core_ext/time/zones'
diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb
index 6e0af0db4d..13610ba19f 100644
--- a/activesupport/lib/active_support/core_ext/time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/time/calculations.rb
@@ -64,11 +64,12 @@ class Time
# Returns a new Time 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>) reset cascadingly, so if only the hour is passed,
- # then minute, sec, and usec is set to 0. If the hour and minute is passed, then
- # sec and usec 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>: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.
#
# 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)
@@ -80,21 +81,29 @@ class Time
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_usec = options.fetch(:usec, (options[:hour] || options[:min] || options[:sec]) ? 0 : Rational(nsec, 1000))
+
+ if new_nsec = options[:nsec]
+ raise ArgumentError, "Can't change both :nsec and :usec at the same time: #{options.inspect}" if options[:usec]
+ new_usec = Rational(new_nsec, 1000)
+ else
+ 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)
elsif zone
::Time.local(new_year, new_month, new_day, new_hour, new_min, new_sec, new_usec)
else
+ raise ArgumentError, 'argument out of range' if new_usec > 999999
::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec + (new_usec.to_r / 1000000), utc_offset)
end
end
- # Uses Date to provide precise Time calculations for years, months, and days.
- # The +options+ parameter takes a hash with any of these keys: <tt>:years</tt>,
- # <tt>:months</tt>, <tt>:weeks</tt>, <tt>:days</tt>, <tt>:hours</tt>,
- # <tt>:minutes</tt>, <tt>:seconds</tt>.
+ # Uses Date to provide precise Time calculations for years, months, and days
+ # according to the proleptic Gregorian calendar. The +options+ parameter
+ # takes a hash with any of these keys: <tt>:years</tt>, <tt>:months</tt>,
+ # <tt>:weeks</tt>, <tt>:days</tt>, <tt>:hours</tt>, <tt>:minutes</tt>,
+ # <tt>:seconds</tt>.
def advance(options)
unless options[:weeks].nil?
options[:weeks], partial_weeks = options[:weeks].divmod(1)
@@ -107,6 +116,7 @@ class Time
end
d = to_date.advance(options)
+ d = d.gregorian if d.julian?
time_advanced_by_date = change(:year => d.year, :month => d.month, :day => d.day)
seconds_to_advance = \
options.fetch(:seconds, 0) +
@@ -152,7 +162,7 @@ class Time
alias :at_noon :middle_of_day
alias :at_middle_of_day :middle_of_day
- # Returns a new Time representing the end of the day, 23:59:59.999999 (.999999999 in ruby1.9)
+ # Returns a new Time representing the end of the day, 23:59:59.999999
def end_of_day
change(
:hour => 23,
@@ -169,7 +179,7 @@ class Time
end
alias :at_beginning_of_hour :beginning_of_hour
- # Returns a new Time representing the end of the hour, x:59:59.999999 (.999999999 in ruby1.9)
+ # Returns a new Time representing the end of the hour, x:59:59.999999
def end_of_hour
change(
:min => 59,
@@ -185,7 +195,7 @@ class Time
end
alias :at_beginning_of_minute :beginning_of_minute
- # Returns a new Time representing the end of the minute, x:xx:59.999999 (.999999999 in ruby1.9)
+ # Returns a new Time representing the end of the minute, x:xx:59.999999
def end_of_minute
change(
:sec => 59,
@@ -199,27 +209,6 @@ class Time
beginning_of_day..end_of_day
end
- # Returns a Range representing the whole week of the current time.
- # Week starts on start_day, default is <tt>Date.week_start</tt> or <tt>config.week_start</tt> when set.
- def all_week(start_day = Date.beginning_of_week)
- beginning_of_week(start_day)..end_of_week(start_day)
- end
-
- # Returns a Range representing the whole month of the current time.
- def all_month
- beginning_of_month..end_of_month
- end
-
- # Returns a Range representing the whole quarter of the current time.
- def all_quarter
- beginning_of_quarter..end_of_quarter
- end
-
- # Returns a Range representing the whole year of the current time.
- def all_year
- beginning_of_year..end_of_year
- end
-
def plus_with_duration(other) #:nodoc:
if ActiveSupport::Duration === other
other.since(self)
diff --git a/activesupport/lib/active_support/core_ext/time/conversions.rb b/activesupport/lib/active_support/core_ext/time/conversions.rb
index 9fd26156c7..dbf1f2f373 100644
--- a/activesupport/lib/active_support/core_ext/time/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/time/conversions.rb
@@ -20,7 +20,7 @@ class Time
:iso8601 => lambda { |time| time.iso8601 }
}
- # Converts to a formatted string. See DATE_FORMATS for builtin formats.
+ # Converts to a formatted string. See DATE_FORMATS for built-in formats.
#
# This method is aliased to <tt>to_s</tt>.
#
diff --git a/activesupport/lib/active_support/core_ext/time/marshal.rb b/activesupport/lib/active_support/core_ext/time/marshal.rb
index 497c4c3fb8..467bad1726 100644
--- a/activesupport/lib/active_support/core_ext/time/marshal.rb
+++ b/activesupport/lib/active_support/core_ext/time/marshal.rb
@@ -1,30 +1,3 @@
-# Ruby 1.9.2 adds utc_offset and zone to Time, but marshaling only
-# preserves utc_offset. Preserve zone also, even though it may not
-# work in some edge cases.
-if Time.local(2010).zone != Marshal.load(Marshal.dump(Time.local(2010))).zone
- class Time
- class << self
- alias_method :_load_without_zone, :_load
- def _load(marshaled_time)
- time = _load_without_zone(marshaled_time)
- time.instance_eval do
- if zone = defined?(@_zone) && remove_instance_variable('@_zone')
- ary = to_a
- ary[0] += subsec if ary[0] == sec
- ary[-1] = zone
- utc? ? Time.utc(*ary) : Time.local(*ary)
- else
- self
- end
- end
- end
- end
+require 'active_support/deprecation'
- alias_method :_dump_without_zone, :_dump
- def _dump(*args)
- obj = dup
- obj.instance_variable_set('@_zone', zone)
- obj.send :_dump_without_zone, *args
- end
- end
-end
+ActiveSupport::Deprecation.warn("This is deprecated and will be removed in Rails 5.1 with no replacement.")
diff --git a/activesupport/lib/active_support/core_ext/time/zones.rb b/activesupport/lib/active_support/core_ext/time/zones.rb
index bbda04d60c..0668eadb1e 100644
--- a/activesupport/lib/active_support/core_ext/time/zones.rb
+++ b/activesupport/lib/active_support/core_ext/time/zones.rb
@@ -1,4 +1,5 @@
require 'active_support/time_with_zone'
+require 'active_support/core_ext/time/acts_like'
require 'active_support/core_ext/date_and_time/zones'
class Time
@@ -50,7 +51,16 @@ class Time
end
end
- # Returns a TimeZone instance or nil, or raises an ArgumentError for invalid timezones.
+ # Returns a TimeZone instance matching the time zone provided.
+ # Accepts the time zone in any format supported by <tt>Time.zone=</tt>.
+ # Raises an ArgumentError for invalid time zones.
+ #
+ # Time.find_zone! "America/New_York" # => #<ActiveSupport::TimeZone @name="America/New_York" ...>
+ # Time.find_zone! "EST" # => #<ActiveSupport::TimeZone @name="EST" ...>
+ # Time.find_zone! -5.hours # => #<ActiveSupport::TimeZone @name="Bogota" ...>
+ # Time.find_zone! nil # => nil
+ # Time.find_zone! false # => false
+ # Time.find_zone! "NOT-A-TIMEZONE" # => ArgumentError: Invalid Timezone: NOT-A-TIMEZONE
def find_zone!(time_zone)
if !time_zone || time_zone.is_a?(ActiveSupport::TimeZone)
time_zone
@@ -71,6 +81,12 @@ class Time
raise ArgumentError, "Invalid Timezone: #{time_zone}"
end
+ # Returns a TimeZone instance matching the time zone provided.
+ # Accepts the time zone in any format supported by <tt>Time.zone=</tt>.
+ # Returns +nil+ for invalid time zones.
+ #
+ # Time.find_zone "America/New_York" # => #<ActiveSupport::TimeZone @name="America/New_York" ...>
+ # Time.find_zone "NOT-A-TIMEZONE" # => nil
def find_zone(time_zone)
find_zone!(time_zone) rescue nil
end
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index 73559bfe0e..664cc15a29 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -8,6 +8,7 @@ require 'active_support/core_ext/module/introspection'
require 'active_support/core_ext/module/anonymous'
require 'active_support/core_ext/module/qualified_const'
require 'active_support/core_ext/object/blank'
+require 'active_support/core_ext/kernel/reporting'
require 'active_support/core_ext/load_error'
require 'active_support/core_ext/name_error'
require 'active_support/core_ext/string/starts_ends_with'
@@ -29,6 +30,10 @@ module ActiveSupport #:nodoc:
mattr_accessor :loaded
self.loaded = Set.new
+ # Stack of files being loaded.
+ mattr_accessor :loading
+ self.loading = []
+
# Should we load files or require them?
mattr_accessor :mechanism
self.mechanism = ENV['NO_RELOAD'] ? :require : :load
@@ -175,14 +180,23 @@ module ActiveSupport #:nodoc:
end
def const_missing(const_name)
- # The interpreter does not pass nesting information, and in the
- # case of anonymous modules we cannot even make the trade-off of
- # assuming their name reflects the nesting. Resort to Object as
- # the only meaningful guess we can make.
- from_mod = anonymous? ? ::Object : self
+ from_mod = anonymous? ? guess_for_anonymous(const_name) : self
Dependencies.load_missing_constant(from_mod, const_name)
end
+ # We assume that the name of the module reflects the nesting
+ # (unless it can be proven that is not the case) and the path to the file
+ # that defines the constant. Anonymous modules cannot follow these
+ # conventions and therefore we assume that the user wants to refer to a
+ # top-level constant.
+ def guess_for_anonymous(const_name)
+ if Object.const_defined?(const_name)
+ raise NameError.new "#{const_name} cannot be autoloaded from an anonymous class or module", const_name
+ else
+ Object
+ end
+ end
+
def unloadable(const_desc = self)
super(const_desc)
end
@@ -191,16 +205,29 @@ module ActiveSupport #:nodoc:
# Object includes this module.
module Loadable #:nodoc:
def self.exclude_from(base)
- base.class_eval { define_method(:load, Kernel.instance_method(:load)) }
+ base.class_eval do
+ define_method(:load, Kernel.instance_method(:load))
+ private :load
+ end
end
def require_or_load(file_name)
Dependencies.require_or_load(file_name)
end
+ # Interprets a file using <tt>mechanism</tt> and marks its defined
+ # constants as autoloaded. <tt>file_name</tt> can be either a string or
+ # respond to <tt>to_path</tt>.
+ #
+ # Use this method in code that absolutely needs a certain constant to be
+ # defined at that point. A typical use case is to make constant name
+ # resolution deterministic for constants with the same relative name in
+ # different namespaces whose evaluation would depend on load order
+ # otherwise.
def require_dependency(file_name, message = "No such file to load -- %s")
+ file_name = file_name.to_path if file_name.respond_to?(:to_path)
unless file_name.is_a?(String)
- raise ArgumentError, "the file name must be a String -- you passed #{file_name.inspect}"
+ raise ArgumentError, "the file name must either be a String or implement #to_path -- you passed #{file_name.inspect}"
end
Dependencies.depend_on(file_name, message)
@@ -217,18 +244,6 @@ module ActiveSupport #:nodoc:
raise
end
- def load(file, wrap = false)
- result = false
- load_dependency(file) { result = super }
- result
- end
-
- def require(file)
- result = false
- load_dependency(file) { result = super }
- result
- end
-
# Mark the given constant as unloadable. Unloadable constants are removed
# each time dependencies are cleared.
#
@@ -245,6 +260,20 @@ module ActiveSupport #:nodoc:
def unloadable(const_desc)
Dependencies.mark_for_unload const_desc
end
+
+ private
+
+ def load(file, wrap = false)
+ result = false
+ load_dependency(file) { result = super }
+ result
+ end
+
+ def require(file)
+ result = false
+ load_dependency(file) { result = super }
+ result
+ end
end
# Exception file-blaming.
@@ -297,6 +326,7 @@ module ActiveSupport #:nodoc:
def clear
log_call
loaded.clear
+ loading.clear
remove_unloadable_constants!
end
@@ -309,6 +339,7 @@ module ActiveSupport #:nodoc:
# Record that we've seen this file *before* loading it to avoid an
# infinite loop with mutual dependencies.
loaded << expanded
+ loading << expanded
begin
if load?
@@ -331,6 +362,8 @@ module ActiveSupport #:nodoc:
rescue Exception
loaded.delete expanded
raise
+ ensure
+ loading.pop
end
# Record history *after* loading so first load gets warnings.
@@ -340,7 +373,7 @@ module ActiveSupport #:nodoc:
# Is the provided constant path defined?
def qualified_const_defined?(path)
- Object.qualified_const_defined?(path.sub(/^::/, ''), false)
+ Object.const_defined?(path, false)
end
# Given +path+, a filesystem path to a ruby file, return an array of
@@ -388,7 +421,8 @@ module ActiveSupport #:nodoc:
end
def load_once_path?(path)
- # to_s works around a ruby1.9 issue where #starts_with?(Pathname) will always return false
+ # to_s works around a ruby issue where String#starts_with?(Pathname)
+ # will raise a TypeError: no implicit conversion of Pathname into String
autoload_once_paths.any? { |base| path.starts_with? base.to_s }
end
@@ -445,8 +479,6 @@ module ActiveSupport #:nodoc:
raise ArgumentError, "A copy of #{from_mod} has been removed from the module tree but is still active!"
end
- raise NameError, "#{from_mod} is not missing constant #{const_name}!" if from_mod.const_defined?(const_name, false)
-
qualified_name = qualified_name_for from_mod, const_name
path_suffix = qualified_name.underscore
@@ -456,10 +488,10 @@ module ActiveSupport #:nodoc:
expanded = File.expand_path(file_path)
expanded.sub!(/\.rb\z/, '')
- if loaded.include?(expanded)
+ if loading.include?(expanded)
raise "Circular dependency detected while autoloading constant #{qualified_name}"
else
- require_or_load(expanded)
+ require_or_load(expanded, qualified_name)
raise LoadError, "Unable to autoload constant #{qualified_name}, expected #{file_path} to define it" unless from_mod.const_defined?(const_name, false)
return from_mod.const_get(const_name)
end
@@ -497,9 +529,9 @@ module ActiveSupport #:nodoc:
end
end
- raise NameError,
- "uninitialized constant #{qualified_name}",
- caller.reject { |l| l.starts_with? __FILE__ }
+ name_error = NameError.new("uninitialized constant #{qualified_name}", const_name)
+ name_error.set_backtrace(caller.reject {|l| l.starts_with? __FILE__ })
+ raise name_error
end
# Remove the constants that have been autoloaded, and those that have been
@@ -575,7 +607,7 @@ module ActiveSupport #:nodoc:
def autoloaded?(desc)
return false if desc.is_a?(Module) && desc.anonymous?
name = to_constant_name desc
- return false unless qualified_const_defined? name
+ return false unless qualified_const_defined?(name)
return autoloaded_constants.include?(name)
end
@@ -648,6 +680,14 @@ module ActiveSupport #:nodoc:
constants = normalized.split('::')
to_remove = constants.pop
+ # Remove the file path from the loaded list.
+ file_path = search_for_file(const.underscore)
+ if file_path
+ expanded = File.expand_path(file_path)
+ expanded.sub!(/\.rb\z/, '')
+ self.loaded.delete(expanded)
+ end
+
if constants.empty?
parent = Object
else
@@ -702,7 +742,7 @@ module ActiveSupport #:nodoc:
protected
def log_call(*args)
if log_activity?
- arg_str = args.collect { |arg| arg.inspect } * ', '
+ arg_str = args.collect(&:inspect) * ', '
/in `([a-z_\?\!]+)'/ =~ caller(1).first
selector = $1 || '<unknown>'
log "called #{selector}(#{arg_str})"
diff --git a/activesupport/lib/active_support/dependencies/autoload.rb b/activesupport/lib/active_support/dependencies/autoload.rb
index c0dba5f7fd..13036d521d 100644
--- a/activesupport/lib/active_support/dependencies/autoload.rb
+++ b/activesupport/lib/active_support/dependencies/autoload.rb
@@ -67,7 +67,7 @@ module ActiveSupport
end
def eager_load!
- @_autoloads.values.each { |file| require file }
+ @_autoloads.each_value { |file| require file }
end
def autoloads
diff --git a/activesupport/lib/active_support/deprecation.rb b/activesupport/lib/active_support/deprecation.rb
index 0281c9222e..46e9996d59 100644
--- a/activesupport/lib/active_support/deprecation.rb
+++ b/activesupport/lib/active_support/deprecation.rb
@@ -32,7 +32,7 @@ module ActiveSupport
# and the second is a library name
#
# ActiveSupport::Deprecation.new('2.0', 'MyLibrary')
- def initialize(deprecation_horizon = '4.1', gem_name = 'Rails')
+ def initialize(deprecation_horizon = '5.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 328b8c320a..9f9dca8453 100644
--- a/activesupport/lib/active_support/deprecation/behaviors.rb
+++ b/activesupport/lib/active_support/deprecation/behaviors.rb
@@ -20,7 +20,7 @@ module ActiveSupport
log: ->(message, callstack) {
logger =
- if defined?(Rails) && Rails.logger
+ if defined?(Rails.logger) && Rails.logger
Rails.logger
else
require 'active_support/logger'
diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb
index 00727ef73c..5a64fc52cc 100644
--- a/activesupport/lib/active_support/duration.rb
+++ b/activesupport/lib/active_support/duration.rb
@@ -1,4 +1,3 @@
-require 'active_support/proxy_object'
require 'active_support/core_ext/array/conversions'
require 'active_support/core_ext/object/acts_like'
@@ -7,7 +6,7 @@ module ActiveSupport
# Time#advance, respectively. It mainly supports the methods on Numeric.
#
# 1.month.ago # equivalent to Time.now.advance(months: -1)
- class Duration < ProxyObject
+ class Duration
attr_accessor :value, :parts
def initialize(value, parts) #:nodoc:
@@ -39,6 +38,10 @@ module ActiveSupport
end
alias :kind_of? :is_a?
+ def instance_of?(klass) # :nodoc:
+ Duration == klass || value.instance_of?(klass)
+ end
+
# Returns +true+ if +other+ is also a Duration instance with the
# same +value+, or if <tt>other == value</tt>.
def ==(other)
@@ -49,6 +52,20 @@ module ActiveSupport
end
end
+ def to_s
+ @value.to_s
+ end
+
+ # Returns +true+ if +other+ is also a Duration instance, which has the
+ # same parts as this one.
+ def eql?(other)
+ Duration === other && other.value.eql?(value)
+ end
+
+ def hash
+ @value.hash
+ end
+
def self.===(other) #:nodoc:
other.is_a?(Duration)
rescue ::NoMethodError
@@ -70,19 +87,23 @@ module ActiveSupport
alias :until :ago
def inspect #:nodoc:
- val_for = parts.inject(::Hash.new(0)) { |h,(l,r)| h[l] += r; h }
- [:years, :months, :days, :minutes, :seconds].
- select {|unit| val_for[unit].nonzero?}.
- tap {|units| units << :seconds if units.empty?}.
- map {|unit| [unit, val_for[unit]]}.
- map {|unit, val| "#{val} #{val == 1 ? unit.to_s.chop : unit.to_s}"}.
- to_sentence(:locale => :en)
+ parts.
+ reduce(::Hash.new(0)) { |h,(l,r)| h[l] += r; h }.
+ sort_by {|unit, _ | [:years, :months, :days, :minutes, :seconds].index(unit)}.
+ map {|unit, val| "#{val} #{val == 1 ? unit.to_s.chop : unit.to_s}"}.
+ to_sentence(locale: ::I18n.default_locale)
end
def as_json(options = nil) #:nodoc:
to_i
end
+ def respond_to_missing?(method, include_private=false) #:nodoc:
+ @value.respond_to?(method, include_private)
+ end
+
+ delegate :<=>, to: :value
+
protected
def sum(sign, time = ::Time.current) #:nodoc:
diff --git a/activesupport/lib/active_support/file_update_checker.rb b/activesupport/lib/active_support/file_update_checker.rb
index d6918bede2..78b627c286 100644
--- a/activesupport/lib/active_support/file_update_checker.rb
+++ b/activesupport/lib/active_support/file_update_checker.rb
@@ -92,7 +92,7 @@ module ActiveSupport
def watched
@watched || begin
- all = @files.select { |f| File.exists?(f) }
+ all = @files.select { |f| File.exist?(f) }
all.concat(Dir[@glob]) if @glob
all
end
diff --git a/activesupport/lib/active_support/file_watcher.rb b/activesupport/lib/active_support/file_watcher.rb
deleted file mode 100644
index 81e63e76a7..0000000000
--- a/activesupport/lib/active_support/file_watcher.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-module ActiveSupport
- class FileWatcher
- class Backend
- def initialize(path, watcher)
- @watcher = watcher
- @path = path
- end
-
- def trigger(files)
- @watcher.trigger(files)
- end
- end
-
- def initialize
- @regex_matchers = {}
- end
-
- def watch(pattern, &block)
- @regex_matchers[pattern] = block
- end
-
- def trigger(files)
- trigger_files = Hash.new { |h,k| h[k] = Hash.new { |h2,k2| h2[k2] = [] } }
-
- files.each do |file, state|
- @regex_matchers.each do |pattern, block|
- trigger_files[block][state] << file if pattern === file
- end
- end
-
- trigger_files.each do |block, payload|
- block.call payload
- end
- end
- end
-end
diff --git a/activesupport/lib/active_support/gem_version.rb b/activesupport/lib/active_support/gem_version.rb
new file mode 100644
index 0000000000..7068f09d87
--- /dev/null
+++ b/activesupport/lib/active_support/gem_version.rb
@@ -0,0 +1,15 @@
+module ActiveSupport
+ # Returns the version of the currently loaded Active Support as a <tt>Gem::Version</tt>
+ def self.gem_version
+ Gem::Version.new VERSION::STRING
+ end
+
+ module VERSION
+ MAJOR = 5
+ MINOR = 0
+ TINY = 0
+ PRE = "alpha"
+
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
+ end
+end
diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb
index 3da99872c0..c7477b9ff4 100644
--- a/activesupport/lib/active_support/hash_with_indifferent_access.rb
+++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb
@@ -1,4 +1,5 @@
require 'active_support/core_ext/hash/keys'
+require 'active_support/core_ext/hash/reverse_merge'
module ActiveSupport
# Implements a hash where keys <tt>:foo</tt> and <tt>"foo"</tt> are considered
@@ -55,7 +56,7 @@ module ActiveSupport
end
def initialize(constructor = {})
- if constructor.is_a?(Hash)
+ if constructor.respond_to?(:to_hash)
super()
update(constructor)
else
@@ -72,8 +73,10 @@ module ActiveSupport
end
def self.new_from_hash_copying_default(hash)
+ hash = hash.to_hash
new(hash).tap do |new_hash|
new_hash.default = hash.default
+ new_hash.default_proc = hash.default_proc if hash.default_proc
end
end
@@ -125,7 +128,7 @@ module ActiveSupport
if other_hash.is_a? HashWithIndifferentAccess
super(other_hash)
else
- other_hash.each_pair do |key, value|
+ other_hash.to_hash.each_pair do |key, value|
if block_given? && key?(key)
value = yield(convert_key(key), self[key], value)
end
@@ -159,7 +162,7 @@ module ActiveSupport
#
# counters.fetch('foo') # => 1
# counters.fetch(:bar, 0) # => 0
- # counters.fetch(:bar) {|key| 0} # => 0
+ # counters.fetch(:bar) { |key| 0 } # => 0
# counters.fetch(:zoo) # => KeyError: key not found: "zoo"
def fetch(key, *extras)
super(convert_key(key), *extras)
@@ -172,10 +175,17 @@ module ActiveSupport
# hash[:b] = 'y'
# hash.values_at('a', 'b') # => ["x", "y"]
def values_at(*indices)
- indices.collect {|key| self[convert_key(key)]}
+ indices.collect { |key| self[convert_key(key)] }
end
- # Returns an exact copy of the hash.
+ # 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
+ # dup[:a][:c] # => "c"
def dup
self.class.new(self).tap do |new_hash|
new_hash.default = default
@@ -207,7 +217,7 @@ module ActiveSupport
# Replaces the contents of this hash with other_hash.
#
# h = { "a" => 100, "b" => 200 }
- # h.replace({ "c" => 300, "d" => 400 }) #=> {"c"=>300, "d"=>400}
+ # h.replace({ "c" => 300, "d" => 400 }) # => {"c"=>300, "d"=>400}
def replace(other_hash)
super(self.class.new_from_hash_copying_default(other_hash))
end
@@ -227,17 +237,21 @@ module ActiveSupport
def deep_symbolize_keys; to_hash.deep_symbolize_keys! end
def to_options!; self end
- def select(*args, &block)
- dup.tap {|hash| hash.select!(*args, &block)}
+ def select(*)
+ super.with_indifferent_access
+ end
+
+ def reject(*)
+ super.with_indifferent_access
end
# Convert to a regular hash with string keys.
def to_hash
- _new_hash= {}
+ _new_hash = Hash.new(default)
each do |key, value|
- _new_hash[convert_key(key)] = convert_value(value, for: :to_hash)
+ _new_hash[key] = convert_value(value, for: :to_hash)
end
- Hash.new(default).merge!(_new_hash)
+ _new_hash
end
protected
@@ -253,7 +267,7 @@ module ActiveSupport
value.nested_under_indifferent_access
end
elsif value.is_a?(Array)
- unless options[:for] == :assignment
+ if options[:for] != :assignment || value.frozen?
value = value.dup
end
value.map! { |e| convert_value(e, options) }
diff --git a/activesupport/lib/active_support/i18n_railtie.rb b/activesupport/lib/active_support/i18n_railtie.rb
index 890dd9380b..95f3f6255a 100644
--- a/activesupport/lib/active_support/i18n_railtie.rb
+++ b/activesupport/lib/active_support/i18n_railtie.rb
@@ -31,6 +31,12 @@ module I18n
fallbacks = app.config.i18n.delete(:fallbacks)
+ # Avoid issues with setting the default_locale by disabling available locales
+ # check while configuring.
+ enforce_available_locales = app.config.i18n.delete(:enforce_available_locales)
+ enforce_available_locales = I18n.enforce_available_locales if enforce_available_locales.nil?
+ I18n.enforce_available_locales = false
+
app.config.i18n.each do |setting, value|
case setting
when :railties_load_path
@@ -44,16 +50,25 @@ module I18n
init_fallbacks(fallbacks) if fallbacks && validate_fallbacks(fallbacks)
+ # Restore available locales check so it will take place from now on.
+ I18n.enforce_available_locales = enforce_available_locales
+
reloader = ActiveSupport::FileUpdateChecker.new(I18n.load_path.dup){ I18n.reload! }
app.reloaders << reloader
- ActionDispatch::Reloader.to_prepare { reloader.execute_if_updated }
+ ActionDispatch::Reloader.to_prepare do
+ reloader.execute_if_updated
+ # 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
@i18n_inited = true
end
def self.include_fallbacks_module
- I18n.backend.class.send(:include, I18n::Backend::Fallbacks)
+ I18n.backend.class.include(I18n::Backend::Fallbacks)
end
def self.init_fallbacks(fallbacks)
diff --git a/activesupport/lib/active_support/inflections.rb b/activesupport/lib/active_support/inflections.rb
index 4ea6abfa12..2ca1124e76 100644
--- a/activesupport/lib/active_support/inflections.rb
+++ b/activesupport/lib/active_support/inflections.rb
@@ -1,5 +1,11 @@
require 'active_support/inflector/inflections'
+#--
+# Defines the standard inflection rules. These are the starting point for
+# new projects and are not considered complete. The current set of inflection
+# rules is frozen. This means, we do not change them to become more complete.
+# This is a safety measure to keep existing applications from breaking.
+#++
module ActiveSupport
Inflector.inflections(:en) do |inflect|
inflect.plural(/$/, 's')
diff --git a/activesupport/lib/active_support/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb
index c96debb93f..486838bd15 100644
--- a/activesupport/lib/active_support/inflector/inflections.rb
+++ b/activesupport/lib/active_support/inflector/inflections.rb
@@ -52,21 +52,21 @@ module ActiveSupport
# into a non-delimited single lowercase word when passed to +underscore+.
#
# acronym 'HTML'
- # titleize 'html' #=> 'HTML'
- # camelize 'html' #=> 'HTML'
- # underscore 'MyHTML' #=> 'my_html'
+ # titleize 'html' # => 'HTML'
+ # camelize 'html' # => 'HTML'
+ # underscore 'MyHTML' # => 'my_html'
#
# The acronym, however, must occur as a delimited unit and not be part of
# another word for conversions to recognize it:
#
# acronym 'HTTP'
- # camelize 'my_http_delimited' #=> 'MyHTTPDelimited'
- # camelize 'https' #=> 'Https', not 'HTTPs'
- # underscore 'HTTPS' #=> 'http_s', not 'https'
+ # camelize 'my_http_delimited' # => 'MyHTTPDelimited'
+ # camelize 'https' # => 'Https', not 'HTTPs'
+ # underscore 'HTTPS' # => 'http_s', not 'https'
#
# acronym 'HTTPS'
- # camelize 'https' #=> 'HTTPS'
- # underscore 'HTTPS' #=> 'https'
+ # camelize 'https' # => 'HTTPS'
+ # underscore 'HTTPS' # => 'https'
#
# Note: Acronyms that are passed to +pluralize+ will no longer be
# recognized, since the acronym will not occur as a delimited unit in the
@@ -74,25 +74,25 @@ module ActiveSupport
# form as an acronym as well:
#
# acronym 'API'
- # camelize(pluralize('api')) #=> 'Apis'
+ # camelize(pluralize('api')) # => 'Apis'
#
# acronym 'APIs'
- # camelize(pluralize('api')) #=> 'APIs'
+ # camelize(pluralize('api')) # => 'APIs'
#
# +acronym+ may be used to specify any word that contains an acronym or
# otherwise needs to maintain a non-standard capitalization. The only
# restriction is that the word must begin with a capital letter.
#
# acronym 'RESTful'
- # underscore 'RESTful' #=> 'restful'
- # underscore 'RESTfulController' #=> 'restful_controller'
- # titleize 'RESTfulController' #=> 'RESTful Controller'
- # camelize 'restful' #=> 'RESTful'
- # camelize 'restful_controller' #=> 'RESTfulController'
+ # underscore 'RESTful' # => 'restful'
+ # underscore 'RESTfulController' # => 'restful_controller'
+ # titleize 'RESTfulController' # => 'RESTful Controller'
+ # camelize 'restful' # => 'RESTful'
+ # camelize 'restful_controller' # => 'RESTfulController'
#
# acronym 'McDonald'
- # underscore 'McDonald' #=> 'mcdonald'
- # camelize 'mcdonald' #=> 'McDonald'
+ # underscore 'McDonald' # => 'mcdonald'
+ # camelize 'mcdonald' # => 'McDonald'
def acronym(word)
@acronyms[word.downcase] = word
@acronym_regex = /#{@acronyms.values.join("|")}/
@@ -154,13 +154,13 @@ module ActiveSupport
end
end
- # Add uncountable words that shouldn't be attempted inflected.
+ # Specifies words that are uncountable and should not be inflected.
#
# uncountable 'money'
# uncountable 'money', 'information'
# uncountable %w( money information rice )
def uncountable(*words)
- (@uncountables << words).flatten!
+ @uncountables += words.flatten.map(&:downcase)
end
# Specifies a humanized form of a string by a regular expression rule or
diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
index d063fcb49c..fe8a2ac9ba 100644
--- a/activesupport/lib/active_support/inflector/methods.rb
+++ b/activesupport/lib/active_support/inflector/methods.rb
@@ -22,49 +22,49 @@ module ActiveSupport
# pluralized using rules defined for that language. By default,
# this parameter is set to <tt>:en</tt>.
#
- # 'post'.pluralize # => "posts"
- # 'octopus'.pluralize # => "octopi"
- # 'sheep'.pluralize # => "sheep"
- # 'words'.pluralize # => "words"
- # 'CamelOctopus'.pluralize # => "CamelOctopi"
- # 'ley'.pluralize(:es) # => "leyes"
+ # pluralize('post') # => "posts"
+ # pluralize('octopus') # => "octopi"
+ # pluralize('sheep') # => "sheep"
+ # pluralize('words') # => "words"
+ # pluralize('CamelOctopus') # => "CamelOctopi"
+ # pluralize('ley', :es) # => "leyes"
def pluralize(word, locale = :en)
apply_inflections(word, inflections(locale).plurals)
end
- # The reverse of +pluralize+, returns the singular form of a word in a
+ # The reverse of #pluralize, returns the singular form of a word in a
# string.
#
# If passed an optional +locale+ parameter, the word will be
- # pluralized using rules defined for that language. By default,
+ # singularized using rules defined for that language. By default,
# this parameter is set to <tt>:en</tt>.
#
- # 'posts'.singularize # => "post"
- # 'octopi'.singularize # => "octopus"
- # 'sheep'.singularize # => "sheep"
- # 'word'.singularize # => "word"
- # 'CamelOctopi'.singularize # => "CamelOctopus"
- # 'leyes'.singularize(:es) # => "ley"
+ # singularize('posts') # => "post"
+ # singularize('octopi') # => "octopus"
+ # singularize('sheep') # => "sheep"
+ # singularize('word') # => "word"
+ # singularize('CamelOctopi') # => "CamelOctopus"
+ # singularize('leyes', :es) # => "ley"
def singularize(word, locale = :en)
apply_inflections(word, inflections(locale).singulars)
end
- # By default, +camelize+ converts strings to UpperCamelCase. If the argument
- # to +camelize+ is set to <tt>:lower</tt> then +camelize+ produces
+ # Converts strings to UpperCamelCase.
+ # If the +uppercase_first_letter+ parameter is set to false, then produces
# lowerCamelCase.
#
- # +camelize+ will also convert '/' to '::' which is useful for converting
+ # Also converts '/' to '::' which is useful for converting
# paths to namespaces.
#
- # 'active_model'.camelize # => "ActiveModel"
- # 'active_model'.camelize(:lower) # => "activeModel"
- # 'active_model/errors'.camelize # => "ActiveModel::Errors"
- # 'active_model/errors'.camelize(:lower) # => "activeModel::Errors"
+ # camelize('active_model') # => "ActiveModel"
+ # camelize('active_model', false) # => "activeModel"
+ # camelize('active_model/errors') # => "ActiveModel::Errors"
+ # camelize('active_model/errors', false) # => "activeModel::Errors"
#
# As a rule of thumb you can think of +camelize+ as the inverse of
- # +underscore+, though there are cases where that does not hold:
+ # #underscore, though there are cases where that does not hold:
#
- # 'SSLError'.underscore.camelize # => "SslError"
+ # camelize(underscore('SSLError')) # => "SslError"
def camelize(term, uppercase_first_letter = true)
string = term.to_s
if uppercase_first_letter
@@ -73,7 +73,7 @@ module ActiveSupport
string = string.sub(/^(?:#{inflections.acronym_regex}(?=\b|[A-Z_])|\w)/) { $&.downcase }
end
string.gsub!(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{inflections.acronyms[$2] || $2.capitalize}" }
- string.gsub!('/', '::')
+ string.gsub!(/\//, '::')
string
end
@@ -81,16 +81,17 @@ module ActiveSupport
#
# Changes '::' to '/' to convert namespaces to paths.
#
- # 'ActiveModel'.underscore # => "active_model"
- # 'ActiveModel::Errors'.underscore # => "active_model/errors"
+ # underscore('ActiveModel') # => "active_model"
+ # underscore('ActiveModel::Errors') # => "active_model/errors"
#
# As a rule of thumb you can think of +underscore+ as the inverse of
- # +camelize+, though there are cases where that does not hold:
+ # #camelize, though there are cases where that does not hold:
#
- # 'SSLError'.underscore.camelize # => "SslError"
+ # camelize(underscore('SSLError')) # => "SslError"
def underscore(camel_cased_word)
- word = camel_cased_word.to_s.gsub('::', '/')
- word.gsub!(/(?:([A-Za-z\d])|^)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1}#{$1 && '_'}#{$2.downcase}" }
+ return camel_cased_word unless camel_cased_word =~ /[A-Z-]|::/
+ word = camel_cased_word.to_s.gsub(/::/, '/')
+ word.gsub!(/(?:(?<=([A-Za-z\d]))|\b)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1 && '_'}#{$2.downcase}" }
word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
word.tr!("-", "_")
@@ -98,20 +99,47 @@ module ActiveSupport
word
end
- # Capitalizes the first word and turns underscores into spaces and strips a
- # trailing "_id", if any. Like +titleize+, this is meant for creating pretty
- # output.
+ # Tweaks an attribute name for display to end users.
+ #
+ # Specifically, performs these transformations:
+ #
+ # * Applies human inflection rules to the argument.
+ # * Deletes leading underscores, if any.
+ # * Removes a "_id" suffix if present.
+ # * 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"
+ #
+ # If "SSL" was defined to be an acronym:
+ #
+ # humanize('ssl_error') # => "SSL error"
#
- # 'employee_salary'.humanize # => "Employee salary"
- # 'author_id'.humanize # => "Author"
- def humanize(lower_case_and_underscored_word)
+ def humanize(lower_case_and_underscored_word, options = {})
result = lower_case_and_underscored_word.to_s.dup
+
inflections.humans.each { |(rule, replacement)| break if result.sub!(rule, replacement) }
- result.gsub!(/_id$/, "")
+
+ result.sub!(/\A_+/, '')
+ result.sub!(/_id\z/, '')
result.tr!('_', ' ')
- result.gsub(/([a-z\d]*)/i) { |match|
+
+ result.gsub!(/([a-z\d]*)/i) do |match|
"#{inflections.acronyms[match] || match.downcase}"
- }.gsub(/^\w/) { $&.upcase }
+ end
+
+ if options.fetch(:capitalize, true)
+ result.sub!(/\A\w/) { |match| match.upcase }
+ end
+
+ result
end
# Capitalizes all the words and replaces some characters in the string to
@@ -120,34 +148,34 @@ module ActiveSupport
#
# +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"
- # 'TheManWithoutAPast'.titleize # => "The Man Without A Past"
- # 'raiders_of_the_lost_ark'.titleize # => "Raiders Of The Lost Ark"
+ # 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]/) { $&.capitalize }
end
- # Create the name of a table like Rails does for models to table names. This
- # method uses the +pluralize+ method on the last word in the string.
+ # Creates the name of a table like Rails does for models to table names.
+ # This method uses the #pluralize method on the last word in the string.
#
- # 'RawScaledScorer'.tableize # => "raw_scaled_scorers"
- # 'egg_and_ham'.tableize # => "egg_and_hams"
- # 'fancyCategory'.tableize # => "fancy_categories"
+ # tableize('RawScaledScorer') # => "raw_scaled_scorers"
+ # tableize('egg_and_ham') # => "egg_and_hams"
+ # tableize('fancyCategory') # => "fancy_categories"
def tableize(class_name)
pluralize(underscore(class_name))
end
- # Create a class name from a plural table name like Rails does for table
+ # Creates a class name from a plural table name like Rails does for table
# names to models. Note that this returns a string and not a Class (To
- # convert to an actual class follow +classify+ with +constantize+).
+ # convert to an actual class follow +classify+ with #constantize).
#
- # 'egg_and_hams'.classify # => "EggAndHam"
- # 'posts'.classify # => "Post"
+ # classify('egg_and_hams') # => "EggAndHam"
+ # classify('posts') # => "Post"
#
# Singular names are not handled correctly:
#
- # 'business'.classify # => "Busines"
+ # classify('calculus') # => "Calculu"
def classify(table_name)
# strip out any leading schema name
camelize(singularize(table_name.to_s.sub(/.*\./, '')))
@@ -155,17 +183,19 @@ module ActiveSupport
# Replaces underscores with dashes in the string.
#
- # 'puni_puni'.dasherize # => "puni-puni"
+ # dasherize('puni_puni') # => "puni-puni"
def dasherize(underscored_word)
underscored_word.tr('_', '-')
end
# Removes the module part from the expression in the string.
#
- # 'ActiveRecord::CoreExtensions::String::Inflections'.demodulize # => "Inflections"
- # 'Inflections'.demodulize # => "Inflections"
+ # demodulize('ActiveRecord::CoreExtensions::String::Inflections') # => "Inflections"
+ # demodulize('Inflections') # => "Inflections"
+ # demodulize('::Inflections') # => "Inflections"
+ # demodulize('') # => ""
#
- # See also +deconstantize+.
+ # See also #deconstantize.
def demodulize(path)
path = path.to_s
if i = path.rindex('::')
@@ -177,13 +207,13 @@ module ActiveSupport
# Removes the rightmost segment from the constant expression in the string.
#
- # 'Net::HTTP'.deconstantize # => "Net"
- # '::Net::HTTP'.deconstantize # => "::Net"
- # 'String'.deconstantize # => ""
- # '::String'.deconstantize # => ""
- # ''.deconstantize # => ""
+ # deconstantize('Net::HTTP') # => "Net"
+ # deconstantize('::Net::HTTP') # => "::Net"
+ # deconstantize('String') # => ""
+ # deconstantize('::String') # => ""
+ # deconstantize('') # => ""
#
- # See also +demodulize+.
+ # See also #demodulize.
def deconstantize(path)
path.to_s[0, path.rindex('::') || 0] # implementation based on the one in facets' Module#spacename
end
@@ -192,9 +222,9 @@ module ActiveSupport
# +separate_class_name_and_id_with_underscore+ sets whether
# the method should put '_' between the name and 'id'.
#
- # 'Message'.foreign_key # => "message_id"
- # 'Message'.foreign_key(false) # => "messageid"
- # 'Admin::Post'.foreign_key # => "post_id"
+ # foreign_key('Message') # => "message_id"
+ # foreign_key('Message', false) # => "messageid"
+ # foreign_key('Admin::Post') # => "post_id"
def foreign_key(class_name, separate_class_name_and_id_with_underscore = true)
underscore(demodulize(class_name)) + (separate_class_name_and_id_with_underscore ? "_id" : "id")
end
@@ -220,7 +250,7 @@ module ActiveSupport
def constantize(camel_cased_word)
names = camel_cased_word.split('::')
- # Trigger a builtin NameError exception including the ill-formed constant in the message.
+ # Trigger a built-in NameError exception including the ill-formed constant in the message.
Object.const_get(camel_cased_word) if names.empty?
# Remove the first blank element in case of '::ClassName' notation.
@@ -234,8 +264,8 @@ module ActiveSupport
next candidate if constant.const_defined?(name, false)
next candidate unless Object.const_defined?(name)
- # Go down the ancestors to check it it's owned
- # directly before we reach Object or the end of ancestors.
+ # Go down the ancestors to check if it is owned directly. The check
+ # stops when we reach Object or the end of ancestors tree.
constant = constant.ancestors.inject do |const, ancestor|
break const if ancestor == Object
break ancestor if ancestor.const_defined?(name, false)
@@ -250,8 +280,8 @@ module ActiveSupport
# Tries to find a constant with the name specified in the argument string.
#
- # 'Module'.safe_constantize # => Module
- # 'Test::Unit'.safe_constantize # => Test::Unit
+ # safe_constantize('Module') # => Module
+ # safe_constantize('Test::Unit') # => Test::Unit
#
# The name is assumed to be the one of a top-level constant, no matter
# whether it starts with "::" or not. No lexical context is taken into
@@ -260,21 +290,21 @@ module ActiveSupport
# C = 'outside'
# module M
# C = 'inside'
- # C # => 'inside'
- # 'C'.safe_constantize # => 'outside', same as ::C
+ # C # => 'inside'
+ # safe_constantize('C') # => 'outside', same as ::C
# end
#
# +nil+ is returned when the name is not in CamelCase or the constant (or
# part of it) is unknown.
#
- # 'blargle'.safe_constantize # => nil
- # 'UnknownModule'.safe_constantize # => nil
- # 'UnknownModule::Foo::Bar'.safe_constantize # => nil
+ # safe_constantize('blargle') # => nil
+ # safe_constantize('UnknownModule') # => nil
+ # safe_constantize('UnknownModule::Foo::Bar') # => nil
def safe_constantize(camel_cased_word)
constantize(camel_cased_word)
rescue NameError => e
- raise unless e.message =~ /(uninitialized constant|wrong constant name) #{const_regexp(camel_cased_word)}$/ ||
- e.name.to_s == camel_cased_word.to_s
+ raise if e.name && !(camel_cased_word.to_s.split("::").include?(e.name.to_s) ||
+ e.name.to_s == camel_cased_word.to_s)
rescue ArgumentError => e
raise unless e.message =~ /not missing constant #{const_regexp(camel_cased_word)}\!$/
end
@@ -318,10 +348,11 @@ module ActiveSupport
private
- # Mount a regular expression that will match part by part of the constant.
+ # Mounts a regular expression, returned as a string to ease interpolation,
+ # that will match part by part the given constant.
#
- # const_regexp("Foo::Bar::Baz") # => /Foo(::Bar(::Baz)?)?/
- # const_regexp("::") # => /::/
+ # const_regexp("Foo::Bar::Baz") # => "Foo(::Bar(::Baz)?)?"
+ # const_regexp("::") # => "::"
def const_regexp(camel_cased_word) #:nodoc:
parts = camel_cased_word.split("::")
diff --git a/activesupport/lib/active_support/inflector/transliterate.rb b/activesupport/lib/active_support/inflector/transliterate.rb
index 1cde417fc5..edea142e82 100644
--- a/activesupport/lib/active_support/inflector/transliterate.rb
+++ b/activesupport/lib/active_support/inflector/transliterate.rb
@@ -67,17 +67,8 @@ module ActiveSupport
# Replaces special characters in a string so that it may be used as part of
# a 'pretty' URL.
#
- # class Person
- # def to_param
- # "#{id}-#{name.parameterize}"
- # end
- # end
- #
- # @person = Person.find(1)
- # # => #<Person id: 1, name: "Donald E. Knuth">
- #
- # <%= link_to(@person.name, person_path(@person)) %>
- # # => <a href="/person/1-donald-e-knuth">Donald E. Knuth</a>
+ # parameterize("Donald E. Knuth") # => "donald-e-knuth"
+ # parameterize("^trés|Jolie-- ") # => "tres-jolie"
def parameterize(string, sep = '-')
# replace accented chars with their ascii equivalents
parameterized_string = transliterate(string)
@@ -92,6 +83,5 @@ module ActiveSupport
end
parameterized_string.downcase
end
-
end
end
diff --git a/activesupport/lib/active_support/json/decoding.rb b/activesupport/lib/active_support/json/decoding.rb
index 30833a4cb1..35548f3f56 100644
--- a/activesupport/lib/active_support/json/decoding.rb
+++ b/activesupport/lib/active_support/json/decoding.rb
@@ -7,14 +7,24 @@ module ActiveSupport
mattr_accessor :parse_json_times
module JSON
+ # matches YAML-formatted dates
+ DATE_REGEX = /^(?:\d{4}-\d{2}-\d{2}|\d{4}-\d{1,2}-\d{1,2}[T \t]+\d{1,2}:\d{2}:\d{2}(\.[0-9]*)?(([ \t]*)Z|[-+]\d{2}?(:\d{2})?))$/
+
class << self
# Parses a JSON string (JavaScript Object Notation) into a hash.
- # See www.json.org for more info.
+ # See http://www.json.org for more info.
#
# ActiveSupport::JSON.decode("{\"team\":\"rails\",\"players\":\"36\"}")
# => {"team" => "rails", "players" => "36"}
- def decode(json, proc = nil, options = {})
- data = ::JSON.load(json, proc, options)
+ def decode(json, options = {})
+ if options.present?
+ raise ArgumentError, "In Rails 4.1, ActiveSupport::JSON.decode no longer " \
+ "accepts an options hash for MultiJSON. MultiJSON reached its end of life " \
+ "and has been removed."
+ end
+
+ data = ::JSON.parse(json, quirks_mode: true)
+
if ActiveSupport.parse_json_times
convert_dates_from(data)
else
diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb
index 77b5d8d227..48f4967892 100644
--- a/activesupport/lib/active_support/json/encoding.rb
+++ b/activesupport/lib/active_support/json/encoding.rb
@@ -1,344 +1,123 @@
-#encoding: us-ascii
-
-require 'active_support/core_ext/object/to_json'
+require 'active_support/core_ext/object/json'
require 'active_support/core_ext/module/delegation'
-require 'bigdecimal'
-require 'active_support/core_ext/big_decimal/conversions' # for #to_s
-require 'active_support/core_ext/hash/except'
-require 'active_support/core_ext/hash/slice'
-require 'active_support/core_ext/object/instance_variables'
-require 'time'
-require 'active_support/core_ext/time/conversions'
-require 'active_support/core_ext/date_time/conversions'
-require 'active_support/core_ext/date/conversions'
-require 'set'
-
module ActiveSupport
class << self
delegate :use_standard_json_time_format, :use_standard_json_time_format=,
+ :time_precision, :time_precision=,
:escape_html_entities_in_json, :escape_html_entities_in_json=,
- :encode_big_decimal_as_string, :encode_big_decimal_as_string=,
+ :json_encoder, :json_encoder=,
:to => :'ActiveSupport::JSON::Encoding'
end
module JSON
- # matches YAML-formatted dates
- DATE_REGEX = /^(?:\d{4}-\d{2}-\d{2}|\d{4}-\d{1,2}-\d{1,2}[T \t]+\d{1,2}:\d{2}:\d{2}(\.[0-9]*)?(([ \t]*)Z|[-+]\d{2}?(:\d{2})?))$/
-
# Dumps objects in JSON (JavaScript Object Notation).
- # See www.json.org for more info.
+ # See http://www.json.org for more info.
#
# ActiveSupport::JSON.encode({ team: 'rails', players: '36' })
# # => "{\"team\":\"rails\",\"players\":\"36\"}"
def self.encode(value, options = nil)
- Encoding::Encoder.new(options).encode(value)
+ Encoding.json_encoder.new(options).encode(value)
end
module Encoding #:nodoc:
- class CircularReferenceError < StandardError; end
-
- class Encoder
+ class JSONGemEncoder #:nodoc:
attr_reader :options
def initialize(options = nil)
@options = options || {}
- @seen = Set.new
end
- def encode(value, use_options = true)
- check_for_circular_references(value) do
- jsonified = use_options ? value.as_json(options_for(value)) : value.as_json
- jsonified.encode_json(self)
- end
+ # Encode the given object into a JSON string
+ def encode(value)
+ stringify jsonify value.as_json(options.dup)
end
- # like encode, but only calls as_json, without encoding to string.
- def as_json(value, use_options = true)
- check_for_circular_references(value) do
- use_options ? value.as_json(options_for(value)) : value.as_json
+ private
+ # Rails does more escaping than the JSON gem natively does (we
+ # escape \u2028 and \u2029 and optionally >, <, & to work around
+ # certain browser problems).
+ ESCAPED_CHARS = {
+ "\u2028" => '\u2028',
+ "\u2029" => '\u2029',
+ '>' => '\u003e',
+ '<' => '\u003c',
+ '&' => '\u0026',
+ }
+
+ ESCAPE_REGEX_WITH_HTML_ENTITIES = /[\u2028\u2029><&]/u
+ ESCAPE_REGEX_WITHOUT_HTML_ENTITIES = /[\u2028\u2029]/u
+
+ # This class wraps all the strings we see and does the extra escaping
+ class EscapedString < String #:nodoc:
+ def to_json(*)
+ if Encoding.escape_html_entities_in_json
+ super.gsub ESCAPE_REGEX_WITH_HTML_ENTITIES, ESCAPED_CHARS
+ else
+ super.gsub ESCAPE_REGEX_WITHOUT_HTML_ENTITIES, ESCAPED_CHARS
+ end
+ end
end
- end
- def options_for(value)
- if value.is_a?(Array) || value.is_a?(Hash)
- # hashes and arrays need to get encoder in the options, so that
- # they can detect circular references.
- options.merge(:encoder => self)
- else
- options.dup
+ # Mark these as private so we don't leak encoding-specific constructs
+ private_constant :ESCAPED_CHARS, :ESCAPE_REGEX_WITH_HTML_ENTITIES,
+ :ESCAPE_REGEX_WITHOUT_HTML_ENTITIES, :EscapedString
+
+ # Convert an object into a "JSON-ready" representation composed of
+ # primitives like Hash, Array, String, Numeric, and true/false/nil.
+ # Recursively calls #as_json to the object to recursively build a
+ # fully JSON-ready object.
+ #
+ # This allows developers to implement #as_json without having to
+ # worry about what base types of objects they are allowed to return
+ # or having to remember to call #as_json recursively.
+ #
+ # Note: the +options+ hash passed to +object.to_json+ is only passed
+ # to +object.as_json+, not any of this method's recursive +#as_json+
+ # calls.
+ def jsonify(value)
+ case value
+ when String
+ EscapedString.new(value)
+ when Numeric, NilClass, TrueClass, FalseClass
+ value
+ when Hash
+ Hash[value.map { |k, v| [jsonify(k), jsonify(v)] }]
+ when Array
+ value.map { |v| jsonify(v) }
+ else
+ jsonify value.as_json
+ end
end
- end
-
- def escape(string)
- Encoding.escape(string)
- end
- private
- def check_for_circular_references(value)
- unless @seen.add?(value.__id__)
- raise CircularReferenceError, 'object references itself'
- end
- yield
- ensure
- @seen.delete(value.__id__)
+ # Encode a "jsonified" Ruby data structure using the JSON gem
+ def stringify(jsonified)
+ ::JSON.generate(jsonified, quirks_mode: true, max_nesting: false)
end
end
-
- ESCAPED_CHARS = {
- "\x00" => '\u0000', "\x01" => '\u0001', "\x02" => '\u0002',
- "\x03" => '\u0003', "\x04" => '\u0004', "\x05" => '\u0005',
- "\x06" => '\u0006', "\x07" => '\u0007', "\x0B" => '\u000B',
- "\x0E" => '\u000E', "\x0F" => '\u000F', "\x10" => '\u0010',
- "\x11" => '\u0011', "\x12" => '\u0012', "\x13" => '\u0013',
- "\x14" => '\u0014', "\x15" => '\u0015', "\x16" => '\u0016',
- "\x17" => '\u0017', "\x18" => '\u0018', "\x19" => '\u0019',
- "\x1A" => '\u001A', "\x1B" => '\u001B', "\x1C" => '\u001C',
- "\x1D" => '\u001D', "\x1E" => '\u001E', "\x1F" => '\u001F',
- "\010" => '\b',
- "\f" => '\f',
- "\n" => '\n',
- "\xe2\x80\xa8" => '\u2028',
- "\xe2\x80\xa9" => '\u2029',
- "\r" => '\r',
- "\t" => '\t',
- '"' => '\"',
- '\\' => '\\\\',
- '>' => '\u003E',
- '<' => '\u003C',
- '&' => '\u0026',
- "#{0xe2.chr}#{0x80.chr}#{0xa8.chr}" => '\u2028',
- "#{0xe2.chr}#{0x80.chr}#{0xa9.chr}" => '\u2029',
- }
-
class << self
# If true, use ISO 8601 format for dates and times. Otherwise, fall back
# to the Active Support legacy format.
attr_accessor :use_standard_json_time_format
- # If false, serializes BigDecimal objects as numeric instead of wrapping
- # them in a string.
- attr_accessor :encode_big_decimal_as_string
-
- attr_accessor :escape_regex
- attr_reader :escape_html_entities_in_json
+ # If true, encode >, <, & as escaped unicode sequences (e.g. > as \u003e)
+ # as a safety measure.
+ attr_accessor :escape_html_entities_in_json
- def escape_html_entities_in_json=(value)
- self.escape_regex = \
- if @escape_html_entities_in_json = value
- /\xe2\x80\xa8|\xe2\x80\xa9|[\x00-\x1F"\\><&]/
- else
- /\xe2\x80\xa8|\xe2\x80\xa9|[\x00-\x1F"\\]/
- end
- end
+ # Sets the precision of encoded time values.
+ # Defaults to 3 (equivalent to millisecond precision)
+ attr_accessor :time_precision
- def escape(string)
- string = string.encode(::Encoding::UTF_8, :undef => :replace).force_encoding(::Encoding::BINARY)
- json = string.gsub(escape_regex) { |s| ESCAPED_CHARS[s] }
- json = %("#{json}")
- json.force_encoding(::Encoding::UTF_8)
- json
- end
+ # Sets the encoder used by Rails to encode Ruby objects into JSON strings
+ # in +Object#to_json+ and +ActiveSupport::JSON.encode+.
+ attr_accessor :json_encoder
end
self.use_standard_json_time_format = true
self.escape_html_entities_in_json = true
- self.encode_big_decimal_as_string = true
- end
- end
-end
-
-class Object
- def as_json(options = nil) #:nodoc:
- if respond_to?(:to_hash)
- to_hash
- else
- instance_values
- end
- end
-end
-
-class Struct #:nodoc:
- def as_json(options = nil)
- Hash[members.zip(values)]
- end
-end
-
-class TrueClass
- def as_json(options = nil) #:nodoc:
- self
- end
-
- def encode_json(encoder) #:nodoc:
- to_s
- end
-end
-
-class FalseClass
- def as_json(options = nil) #:nodoc:
- self
- end
-
- def encode_json(encoder) #:nodoc:
- to_s
- end
-end
-
-class NilClass
- def as_json(options = nil) #:nodoc:
- self
- end
-
- def encode_json(encoder) #:nodoc:
- 'null'
- end
-end
-
-class String
- def as_json(options = nil) #:nodoc:
- self
- end
-
- def encode_json(encoder) #:nodoc:
- encoder.escape(self)
- end
-end
-
-class Symbol
- def as_json(options = nil) #:nodoc:
- to_s
- end
-end
-
-class Numeric
- def as_json(options = nil) #:nodoc:
- self
- end
-
- def encode_json(encoder) #:nodoc:
- to_s
- end
-end
-
-class Float
- # Encoding Infinity or NaN to JSON should return "null". The default returns
- # "Infinity" or "NaN" which breaks parsing the JSON. E.g. JSON.parse('[NaN]').
- def as_json(options = nil) #:nodoc:
- finite? ? self : nil
- end
-end
-
-class BigDecimal
- # A BigDecimal would be naturally represented as a JSON number. Most libraries,
- # however, parse non-integer JSON numbers directly as floats. Clients using
- # those libraries would get in general a wrong number and no way to recover
- # other than manually inspecting the string with the JSON code itself.
- #
- # That's why a JSON string is returned. The JSON literal is not numeric, but
- # if the other end knows by contract that the data is supposed to be a
- # BigDecimal, it still has the chance to post-process the string and get the
- # real value.
- #
- # Use <tt>ActiveSupport.use_standard_json_big_decimal_format = true</tt> to
- # override this behavior.
- def as_json(options = nil) #:nodoc:
- if finite?
- ActiveSupport.encode_big_decimal_as_string ? to_s : self
- else
- nil
- end
- end
-end
-
-class Regexp
- def as_json(options = nil) #:nodoc:
- to_s
- end
-end
-
-module Enumerable
- def as_json(options = nil) #:nodoc:
- to_a.as_json(options)
- end
-end
-
-class Range
- def as_json(options = nil) #:nodoc:
- to_s
- end
-end
-
-class Array
- def as_json(options = nil) #:nodoc:
- # use encoder as a proxy to call as_json on all elements, to protect from circular references
- encoder = options && options[:encoder] || ActiveSupport::JSON::Encoding::Encoder.new(options)
- map { |v| encoder.as_json(v, options) }
- end
-
- def encode_json(encoder) #:nodoc:
- # we assume here that the encoder has already run as_json on self and the elements, so we run encode_json directly
- "[#{map { |v| v.encode_json(encoder) } * ','}]"
- end
-end
-
-class Hash
- def as_json(options = nil) #:nodoc:
- # create a subset of the hash by applying :only or :except
- subset = if options
- if attrs = options[:only]
- slice(*Array(attrs))
- elsif attrs = options[:except]
- except(*Array(attrs))
- else
- self
- end
- else
- self
- end
-
- # use encoder as a proxy to call as_json on all values in the subset, to protect from circular references
- encoder = options && options[:encoder] || ActiveSupport::JSON::Encoding::Encoder.new(options)
- Hash[subset.map { |k, v| [k.to_s, encoder.as_json(v, options)] }]
- end
-
- def encode_json(encoder) #:nodoc:
- # values are encoded with use_options = false, because we don't want hash representations from ActiveModel to be
- # processed once again with as_json with options, as this could cause unexpected results (i.e. missing fields);
-
- # on the other hand, we need to run as_json on the elements, because the model representation may contain fields
- # like Time/Date in their original (not jsonified) form, etc.
-
- "{#{map { |k,v| "#{encoder.encode(k.to_s)}:#{encoder.encode(v, false)}" } * ','}}"
- end
-end
-
-class Time
- def as_json(options = nil) #:nodoc:
- if ActiveSupport.use_standard_json_time_format
- xmlschema
- else
- %(#{strftime("%Y/%m/%d %H:%M:%S")} #{formatted_offset(false)})
- end
- end
-end
-
-class Date
- def as_json(options = nil) #:nodoc:
- if ActiveSupport.use_standard_json_time_format
- strftime("%Y-%m-%d")
- else
- strftime("%Y/%m/%d")
- end
- end
-end
-
-class DateTime
- def as_json(options = nil) #:nodoc:
- if ActiveSupport.use_standard_json_time_format
- xmlschema
- else
- strftime('%Y/%m/%d %H:%M:%S %z')
+ self.json_encoder = JSONGemEncoder
+ self.time_precision = 3
end
end
end
diff --git a/activesupport/lib/active_support/key_generator.rb b/activesupport/lib/active_support/key_generator.rb
index 598c46bce5..51d2da3a79 100644
--- a/activesupport/lib/active_support/key_generator.rb
+++ b/activesupport/lib/active_support/key_generator.rb
@@ -57,18 +57,16 @@ module ActiveSupport
# secret they've provided is at least 30 characters in length.
def ensure_secret_secure(secret)
if secret.blank?
- raise ArgumentError, "A secret is required to generate an " +
- "integrity hash for cookie session data. Use " +
- "config.secret_key_base = \"some secret phrase of at " +
- "least #{SECRET_MIN_LENGTH} characters\"" +
- "in config/initializers/secret_token.rb"
+ 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."
end
if secret.length < SECRET_MIN_LENGTH
- raise ArgumentError, "Secret should be something secure, " +
- "like \"#{SecureRandom.hex(16)}\". The value you " +
- "provided, \"#{secret}\", is shorter than the minimum length " +
- "of #{SECRET_MIN_LENGTH} characters"
+ raise ArgumentError, "Secret should be something secure, " \
+ "like \"#{SecureRandom.hex(16)}\". The value you " \
+ "provided, \"#{secret}\", is shorter than the minimum length " \
+ "of #{SECRET_MIN_LENGTH} characters."
end
end
end
diff --git a/activesupport/lib/active_support/logger.rb b/activesupport/lib/active_support/logger.rb
index 4a55bbb350..33fccdcf95 100644
--- a/activesupport/lib/active_support/logger.rb
+++ b/activesupport/lib/active_support/logger.rb
@@ -1,4 +1,4 @@
-require 'active_support/core_ext/class/attribute_accessors'
+require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/logger_silence'
require 'logger'
diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb
index bffdfc6201..92ab6fe648 100644
--- a/activesupport/lib/active_support/message_encryptor.rb
+++ b/activesupport/lib/active_support/message_encryptor.rb
@@ -12,7 +12,7 @@ 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)
+ # salt = SecureRandom.random_bytes(64)
# key = ActiveSupport::KeyGenerator.new('password').generate_key(salt) # => "\x89\xE0\x156\xAC..."
# crypt = ActiveSupport::MessageEncryptor.new(key) # => #<ActiveSupport::MessageEncryptor ...>
# encrypted_data = crypt.encrypt_and_sign('my secret data') # => "NlFBTTMwOUV5UlA1QlNEN2xkY2d6eThYWWh..."
@@ -40,6 +40,7 @@ module ActiveSupport
# Options:
# * <tt>:cipher</tt> - Cipher to use. Can be any cipher returned by
# <tt>OpenSSL::Cipher.ciphers</tt>. Default is 'aes-256-cbc'.
+ # * <tt>:digest</tt> - String of digest to use for signing. Default is +SHA1+.
# * <tt>:serializer</tt> - Object serializer to use. Default is +Marshal+.
def initialize(secret, *signature_key_or_options)
options = signature_key_or_options.extract_options!
@@ -47,7 +48,7 @@ module ActiveSupport
@secret = secret
@sign_secret = sign_secret
@cipher = options[:cipher] || 'aes-256-cbc'
- @verifier = MessageVerifier.new(@sign_secret || @secret, :serializer => NullSerializer)
+ @verifier = MessageVerifier.new(@sign_secret || @secret, digest: options[:digest] || 'SHA1', serializer: NullSerializer)
@serializer = options[:serializer] || Marshal
end
@@ -76,12 +77,12 @@ module ActiveSupport
encrypted_data = cipher.update(@serializer.dump(value))
encrypted_data << cipher.final
- [encrypted_data, iv].map {|v| ::Base64.strict_encode64(v)}.join("--")
+ "#{::Base64.strict_encode64 encrypted_data}--#{::Base64.strict_encode64 iv}"
end
def _decrypt(encrypted_message)
cipher = new_cipher
- encrypted_data, iv = encrypted_message.split("--").map {|v| ::Base64.decode64(v)}
+ encrypted_data, iv = encrypted_message.split("--").map {|v| ::Base64.strict_decode64(v)}
cipher.decrypt
cipher.key = @secret
@@ -91,7 +92,7 @@ module ActiveSupport
decrypted_data << cipher.final
@serializer.load(decrypted_data)
- rescue OpenSSLCipherError, TypeError
+ 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 e0cd92ae3c..eee9bbaead 100644
--- a/activesupport/lib/active_support/message_verifier.rb
+++ b/activesupport/lib/active_support/message_verifier.rb
@@ -1,5 +1,6 @@
require 'base64'
require 'active_support/core_ext/object/blank'
+require 'active_support/security_utils'
module ActiveSupport
# +MessageVerifier+ makes it easy to generate and verify messages which are
@@ -27,37 +28,96 @@ module ActiveSupport
class InvalidSignature < StandardError; end
def initialize(secret, options = {})
+ raise ArgumentError, 'Secret should not be nil.' unless secret
@secret = secret
@digest = options[:digest] || 'SHA1'
@serializer = options[:serializer] || Marshal
end
- def verify(signed_message)
- raise InvalidSignature if signed_message.blank?
+ # Checks if a signed message could have been generated by signing an object
+ # with the +MessageVerifier+'s secret.
+ #
+ # verifier = ActiveSupport::MessageVerifier.new 's3Krit'
+ # signed_message = verifier.generate 'a private message'
+ # verifier.valid_message?(signed_message) # => true
+ #
+ # tampered_message = signed_message.chop # editing the message invalidates the signature
+ # verifier.valid_message?(tampered_message) # => false
+ def valid_message?(signed_message)
+ return if signed_message.blank?
data, digest = signed_message.split("--")
- if data.present? && digest.present? && secure_compare(digest, generate_digest(data))
- @serializer.load(::Base64.decode64(data))
- else
- raise InvalidSignature
+ data.present? && digest.present? && ActiveSupport::SecurityUtils.secure_compare(digest, generate_digest(data))
+ end
+
+ # Decodes the signed message using the +MessageVerifier+'s secret.
+ #
+ # verifier = ActiveSupport::MessageVerifier.new 's3Krit'
+ #
+ # signed_message = verifier.generate 'a private message'
+ # verifier.verified(signed_message) # => 'a private message'
+ #
+ # Returns +nil+ if the message was not signed with the same secret.
+ #
+ # other_verifier = ActiveSupport::MessageVerifier.new 'd1ff3r3nt-s3Krit'
+ # other_verifier.verified(signed_message) # => nil
+ #
+ # Returns +nil+ if the message is not Base64-encoded.
+ #
+ # invalid_message = "f--46a0120593880c733a53b6dad75b42ddc1c8996d"
+ # verifier.verified(invalid_message) # => nil
+ #
+ # Raises any error raised while decoding the signed message.
+ #
+ # incompatible_message = "test--dad7b06c94abba8d46a15fafaef56c327665d5ff"
+ # verifier.verified(incompatible_message) # => TypeError: incompatible marshal file format
+ def verified(signed_message)
+ if valid_message?(signed_message)
+ begin
+ data = signed_message.split("--")[0]
+ @serializer.load(decode(data))
+ rescue ArgumentError => argument_error
+ return if argument_error.message =~ %r{invalid base64}
+ raise
+ end
end
end
+ # Decodes the signed message using the +MessageVerifier+'s secret.
+ #
+ # verifier = ActiveSupport::MessageVerifier.new 's3Krit'
+ # signed_message = verifier.generate 'a private message'
+ #
+ # verifier.verify(signed_message) # => 'a private message'
+ #
+ # Raises +InvalidSignature+ if the message was not signed with the same
+ # secret or was not Base64-encoded.
+ #
+ # other_verifier = ActiveSupport::MessageVerifier.new 'd1ff3r3nt-s3Krit'
+ # other_verifier.verify(signed_message) # => ActiveSupport::MessageVerifier::InvalidSignature
+ def verify(signed_message)
+ verified(signed_message) || raise(InvalidSignature)
+ end
+
+ # Generates a signed message for the provided value.
+ #
+ # The message is signed with the +MessageVerifier+'s secret. Without knowing
+ # the secret, the original value cannot be extracted from the message.
+ #
+ # verifier = ActiveSupport::MessageVerifier.new 's3Krit'
+ # verifier.generate 'a private message' # => "BAhJIhRwcml2YXRlLW1lc3NhZ2UGOgZFVA==--e2d724331ebdee96a10fb99b089508d1c72bd772"
def generate(value)
- data = ::Base64.strict_encode64(@serializer.dump(value))
+ data = encode(@serializer.dump(value))
"#{data}--#{generate_digest(data)}"
end
private
- # constant-time comparison algorithm to prevent timing attacks
- def secure_compare(a, b)
- return false unless a.bytesize == b.bytesize
-
- l = a.unpack "C#{a.bytesize}"
+ def encode(data)
+ ::Base64.strict_encode64(data)
+ end
- res = 0
- b.each_byte { |byte| res |= byte ^ l.shift }
- res == 0
+ def decode(data)
+ ::Base64.strict_decode64(data)
end
def generate_digest(data)
diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb
index a42e7f6542..3c0cf9f137 100644
--- a/activesupport/lib/active_support/multibyte/chars.rb
+++ b/activesupport/lib/active_support/multibyte/chars.rb
@@ -56,11 +56,10 @@ module ActiveSupport #:nodoc:
# Forward all undefined methods to the wrapped string.
def method_missing(method, *args, &block)
+ result = @wrapped_string.__send__(method, *args, &block)
if method.to_s =~ /!$/
- result = @wrapped_string.__send__(method, *args, &block)
self if result
else
- result = @wrapped_string.__send__(method, *args, &block)
result.kind_of?(String) ? chars(result) : result
end
end
diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb
index 04e6b71580..35efebc65f 100644
--- a/activesupport/lib/active_support/multibyte/unicode.rb
+++ b/activesupport/lib/active_support/multibyte/unicode.rb
@@ -11,7 +11,7 @@ module ActiveSupport
NORMALIZATION_FORMS = [:c, :kc, :d, :kd]
# The Unicode version that is supported by the implementation
- UNICODE_VERSION = '6.2.0'
+ UNICODE_VERSION = '7.0.0'
# The default normalization used for operations that require
# normalization. It can be set to any of the normalizations
@@ -42,7 +42,6 @@ module ActiveSupport
0x0085, # White_Space # Cc <control-0085>
0x00A0, # White_Space # Zs NO-BREAK SPACE
0x1680, # White_Space # Zs OGHAM SPACE MARK
- 0x180E, # White_Space # Zs MONGOLIAN VOWEL SEPARATOR
(0x2000..0x200A).to_a, # White_Space # Zs [11] EN QUAD..HAIR SPACE
0x2028, # White_Space # Zl LINE SEPARATOR
0x2029, # White_Space # Zp PARAGRAPH SEPARATOR
@@ -212,37 +211,43 @@ module ActiveSupport
codepoints
end
- # Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent
- # resulting in a valid UTF-8 string.
- #
- # Passing +true+ will forcibly tidy all bytes, assuming that the string's
- # encoding is entirely CP1252 or ISO-8859-1.
- def tidy_bytes(string, force = false)
- return string if string.empty?
-
- if force
- return string.encode(Encoding::UTF_8, Encoding::Windows_1252, invalid: :replace, undef: :replace)
+ # Rubinius' String#scrub, however, doesn't support ASCII-incompatible chars.
+ if !defined?(Rubinius)
+ # Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent
+ # resulting in a valid UTF-8 string.
+ #
+ # Passing +true+ will forcibly tidy all bytes, assuming that the string's
+ # encoding is entirely CP1252 or ISO-8859-1.
+ def tidy_bytes(string, force = false)
+ return string if string.empty?
+ return recode_windows1252_chars(string) if force
+ string.scrub { |bad| recode_windows1252_chars(bad) }
end
+ else
+ def tidy_bytes(string, force = false)
+ return string if string.empty?
+ return recode_windows1252_chars(string) if force
+
+ # We can't transcode to the same format, so we choose a nearly-identical encoding.
+ # We're going to 'transcode' bytes from UTF-8 when possible, then fall back to
+ # CP1252 when we get errors. The final string will be 'converted' back to UTF-8
+ # before returning.
+ reader = Encoding::Converter.new(Encoding::UTF_8, Encoding::UTF_16LE)
+
+ source = string.dup
+ out = ''.force_encoding(Encoding::UTF_16LE)
+
+ loop do
+ reader.primitive_convert(source, out)
+ _, _, _, error_bytes, _ = reader.primitive_errinfo
+ break if error_bytes.nil?
+ out << error_bytes.encode(Encoding::UTF_16LE, Encoding::Windows_1252, invalid: :replace, undef: :replace)
+ end
- # We can't transcode to the same format, so we choose a nearly-identical encoding.
- # We're going to 'transcode' bytes from UTF-8 when possible, then fall back to
- # CP1252 when we get errors. The final string will be 'converted' back to UTF-8
- # before returning.
- reader = Encoding::Converter.new(Encoding::UTF_8, Encoding::UTF_8_MAC)
-
- source = string.dup
- out = ''.force_encoding(Encoding::UTF_8_MAC)
+ reader.finish
- loop do
- reader.primitive_convert(source, out)
- _, _, _, error_bytes, _ = reader.primitive_errinfo
- break if error_bytes.nil?
- out << error_bytes.encode(Encoding::UTF_8_MAC, Encoding::Windows_1252, invalid: :replace, undef: :replace)
+ out.encode!(Encoding::UTF_8)
end
-
- reader.finish
-
- out.encode!(Encoding::UTF_8)
end
# Returns the KC normalization of the string by default. NFKC is
@@ -287,6 +292,13 @@ module ActiveSupport
class Codepoint
attr_accessor :code, :combining_class, :decomp_type, :decomp_mapping, :uppercase_mapping, :lowercase_mapping
+ # Initializing Codepoint object with default values
+ def initialize
+ @combining_class = 0
+ @uppercase_mapping = 0
+ @lowercase_mapping = 0
+ end
+
def swapcase_mapping
uppercase_mapping > 0 ? uppercase_mapping : lowercase_mapping
end
@@ -322,7 +334,7 @@ module ActiveSupport
begin
@codepoints, @composition_exclusion, @composition_map, @boundary, @cp1252 = File.open(self.class.filename, 'rb') { |f| Marshal.load f.read }
rescue => e
- raise IOError.new("Couldn't load the Unicode tables for UTF8Handler (#{e.message}), ActiveSupport::Multibyte is unusable")
+ raise IOError.new("Couldn't load the Unicode tables for UTF8Handler (#{e.message}), ActiveSupport::Multibyte is unusable")
end
# Redefine the === method so we can write shorter rules for grapheme cluster breaks
@@ -354,6 +366,7 @@ module ActiveSupport
private
def apply_mapping(string, mapping) #:nodoc:
+ database.codepoints
string.each_codepoint.map do |codepoint|
cp = database.codepoints[codepoint]
if cp and (ncp = cp.send(mapping)) and ncp > 0
@@ -364,20 +377,13 @@ module ActiveSupport
end.pack('U*')
end
- def tidy_byte(byte)
- if byte < 160
- [database.cp1252[byte] || byte].pack("U").unpack("C*")
- elsif byte < 192
- [194, byte]
- else
- [195, byte - 64]
- end
+ def recode_windows1252_chars(string)
+ string.encode(Encoding::UTF_8, Encoding::Windows_1252, invalid: :replace, undef: :replace)
end
def database
@database ||= UnicodeDatabase.new
end
-
end
end
end
diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb
index b32aa75e59..b9f8e1ab2c 100644
--- a/activesupport/lib/active_support/notifications.rb
+++ b/activesupport/lib/active_support/notifications.rb
@@ -16,7 +16,7 @@ module ActiveSupport
# render text: 'Foo'
# end
#
- # That executes the block first and notifies all subscribers once done.
+ # That first executes the block and then notifies all subscribers once done.
#
# In the example above +render+ is the name of the event, and the rest is called
# the _payload_. The payload is a mechanism that allows instrumenters to pass
@@ -141,6 +141,11 @@ module ActiveSupport
#
# ActiveSupport::Notifications.unsubscribe(subscriber)
#
+ # You can also unsubscribe by passing the name of the subscriber object. Note
+ # that this will unsubscribe all subscriptions with the given name:
+ #
+ # ActiveSupport::Notifications.unsubscribe("render")
+ #
# == Default Queue
#
# Notifications ships with a queue implementation that consumes and publishes events
@@ -173,12 +178,12 @@ module ActiveSupport
unsubscribe(subscriber)
end
- def unsubscribe(args)
- notifier.unsubscribe(args)
+ def unsubscribe(subscriber_or_name)
+ notifier.unsubscribe(subscriber_or_name)
end
def instrumenter
- InstrumentationRegistry.instrumenter_for(notifier)
+ InstrumentationRegistry.instance.instrumenter_for(notifier)
end
end
diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb
index 99fe03e6d0..6bf8c7d5de 100644
--- a/activesupport/lib/active_support/notifications/fanout.rb
+++ b/activesupport/lib/active_support/notifications/fanout.rb
@@ -25,9 +25,15 @@ module ActiveSupport
subscriber
end
- def unsubscribe(subscriber)
+ def unsubscribe(subscriber_or_name)
synchronize do
- @subscribers.reject! { |s| s.matches?(subscriber) }
+ case subscriber_or_name
+ when String
+ @subscribers.reject! { |s| s.matches?(subscriber_or_name) }
+ else
+ @subscribers.delete(subscriber_or_name)
+ end
+
@listeners_for.clear
end
end
@@ -97,31 +103,27 @@ module ActiveSupport
end
def subscribed_to?(name)
- @pattern === name.to_s
+ @pattern === name
end
- def matches?(subscriber_or_name)
- self === subscriber_or_name ||
- @pattern && @pattern === subscriber_or_name
+ def matches?(name)
+ @pattern && @pattern === name
end
end
class Timed < Evented
- def initialize(pattern, delegate)
- @timestack = []
- super
- end
-
def publish(name, *args)
@delegate.call name, *args
end
def start(name, id, payload)
- @timestack.push Time.now
+ timestack = Thread.current[:_timestack] ||= []
+ timestack.push Time.now
end
def finish(name, id, payload)
- started = @timestack.pop
+ timestack = Thread.current[:_timestack]
+ started = timestack.pop
@delegate.call(name, started, Time.now, id, payload)
end
end
diff --git a/activesupport/lib/active_support/notifications/instrumenter.rb b/activesupport/lib/active_support/notifications/instrumenter.rb
index 0c9a729ce5..075ddc2382 100644
--- a/activesupport/lib/active_support/notifications/instrumenter.rb
+++ b/activesupport/lib/active_support/notifications/instrumenter.rb
@@ -54,10 +54,23 @@ module ActiveSupport
@transaction_id = transaction_id
@end = ending
@children = []
+ @duration = nil
end
+ # Returns the difference in milliseconds between when the execution of the
+ # event started and when it ended.
+ #
+ # ActiveSupport::Notifications.subscribe('wait') do |*args|
+ # @event = ActiveSupport::Notifications::Event.new(*args)
+ # end
+ #
+ # ActiveSupport::Notifications.instrument('wait') do
+ # sleep 1
+ # end
+ #
+ # @event.duration # => 1000.138
def duration
- 1000.0 * (self.end - time)
+ @duration ||= 1000.0 * (self.end - time)
end
def <<(event)
diff --git a/activesupport/lib/active_support/number_helper.rb b/activesupport/lib/active_support/number_helper.rb
index 237ef7087f..cfca42bc69 100644
--- a/activesupport/lib/active_support/number_helper.rb
+++ b/activesupport/lib/active_support/number_helper.rb
@@ -1,115 +1,19 @@
-require 'active_support/core_ext/big_decimal/conversions'
-require 'active_support/core_ext/object/blank'
-require 'active_support/core_ext/hash/keys'
-require 'active_support/i18n'
-
module ActiveSupport
module NumberHelper
- extend self
-
- DEFAULTS = {
- # Used in number_to_delimited
- # These are also the defaults for 'currency', 'percentage', 'precision', and 'human'
- format: {
- # Sets the separator between the units, for more precision (e.g. 1.0 / 2.0 == 0.5)
- separator: ".",
- # Delimits thousands (e.g. 1,000,000 is a million) (always in groups of three)
- delimiter: ",",
- # Number of decimals, behind the separator (the number 1 with a precision of 2 gives: 1.00)
- precision: 3,
- # If set to true, precision will mean the number of significant digits instead
- # of the number of decimal digits (1234 with precision 2 becomes 1200, 1.23543 becomes 1.2)
- significant: false,
- # If set, the zeros after the decimal separator will always be stripped (eg.: 1.200 will be 1.2)
- strip_insignificant_zeros: false
- },
-
- # Used in number_to_currency
- currency: {
- format: {
- format: "%u%n",
- negative_format: "-%u%n",
- unit: "$",
- # These five are to override number.format and are optional
- separator: ".",
- delimiter: ",",
- precision: 2,
- significant: false,
- strip_insignificant_zeros: false
- }
- },
-
- # Used in number_to_percentage
- percentage: {
- format: {
- delimiter: "",
- format: "%n%"
- }
- },
-
- # Used in number_to_rounded
- precision: {
- format: {
- delimiter: ""
- }
- },
-
- # Used in number_to_human_size and number_to_human
- human: {
- format: {
- # These five are to override number.format and are optional
- delimiter: "",
- precision: 3,
- significant: true,
- strip_insignificant_zeros: true
- },
- # Used in number_to_human_size
- storage_units: {
- # Storage units output formatting.
- # %u is the storage unit, %n is the number (default: 2 MB)
- format: "%n %u",
- units: {
- byte: "Bytes",
- kb: "KB",
- mb: "MB",
- gb: "GB",
- tb: "TB"
- }
- },
- # Used in number_to_human
- decimal_units: {
- format: "%n %u",
- # Decimal units output formatting
- # By default we will only quantify some of the exponents
- # but the commented ones might be defined or overridden
- # by the user.
- units: {
- # femto: Quadrillionth
- # pico: Trillionth
- # nano: Billionth
- # micro: Millionth
- # mili: Thousandth
- # centi: Hundredth
- # deci: Tenth
- unit: "",
- # ten:
- # one: Ten
- # other: Tens
- # hundred: Hundred
- thousand: "Thousand",
- million: "Million",
- billion: "Billion",
- trillion: "Trillion",
- quadrillion: "Quadrillion"
- }
- }
- }
- }
-
- DECIMAL_UNITS = { 0 => :unit, 1 => :ten, 2 => :hundred, 3 => :thousand, 6 => :million, 9 => :billion, 12 => :trillion, 15 => :quadrillion,
- -1 => :deci, -2 => :centi, -3 => :mili, -6 => :micro, -9 => :nano, -12 => :pico, -15 => :femto }
+ extend ActiveSupport::Autoload
+
+ eager_autoload do
+ autoload :NumberConverter
+ autoload :NumberToRoundedConverter
+ autoload :NumberToDelimitedConverter
+ autoload :NumberToHumanConverter
+ autoload :NumberToHumanSizeConverter
+ autoload :NumberToPhoneConverter
+ autoload :NumberToCurrencyConverter
+ autoload :NumberToPercentageConverter
+ end
- STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb]
+ extend self
# Formats a +number+ into a US phone number (e.g., (555)
# 123-9876). You can customize the format in the +options+ hash.
@@ -137,27 +41,7 @@ module ActiveSupport
# number_to_phone(1235551234, country_code: 1, extension: 1343, delimiter: '.')
# # => +1.123.555.1234 x 1343
def number_to_phone(number, options = {})
- return unless number
- options = options.symbolize_keys
-
- number = number.to_s.strip
- area_code = options[:area_code]
- delimiter = options[:delimiter] || "-"
- extension = options[:extension]
- country_code = options[:country_code]
-
- if area_code
- number.gsub!(/(\d{1,3})(\d{3})(\d{4}$)/,"(\\1) \\2#{delimiter}\\3")
- else
- number.gsub!(/(\d{0,3})(\d{3})(\d{4})$/,"\\1#{delimiter}\\2#{delimiter}\\3")
- number.slice!(0, 1) if number.start_with?(delimiter) && !delimiter.blank?
- end
-
- str = ''
- str << "+#{country_code}#{delimiter}" unless country_code.blank?
- str << number
- str << " x #{extension}" unless extension.blank?
- str
+ NumberToPhoneConverter.convert(number, options)
end
# Formats a +number+ into a currency string (e.g., $13.65). You
@@ -199,25 +83,7 @@ module ActiveSupport
# number_to_currency(1234567890.50, unit: '&pound;', separator: ',', delimiter: '', format: '%n %u')
# # => 1234567890,50 &pound;
def number_to_currency(number, options = {})
- return unless number
- options = options.symbolize_keys
-
- currency = i18n_format_options(options[:locale], :currency)
- currency[:negative_format] ||= "-" + currency[:format] if currency[:format]
-
- defaults = default_format_options(:currency).merge!(currency)
- defaults[:negative_format] = "-" + options[:format] if options[:format]
- options = defaults.merge!(options)
-
- unit = options.delete(:unit)
- format = options.delete(:format)
-
- if number.to_f.phase != 0
- format = options.delete(:negative_format)
- number = number.respond_to?("abs") ? number.abs : number.sub(/^-/, '')
- end
-
- format.gsub('%n', self.number_to_rounded(number, options)).gsub('%u', unit)
+ NumberToCurrencyConverter.convert(number, options)
end
# Formats a +number+ as a percentage string (e.g., 65%). You can
@@ -250,18 +116,11 @@ module ActiveSupport
# number_to_percentage(1000, delimiter: '.', separator: ',') # => 1.000,000%
# number_to_percentage(302.24398923423, precision: 5) # => 302.24399%
# number_to_percentage(1000, locale: :fr) # => 1 000,000%
- # number_to_percentage:(1000, precision: nil) # => 1000%
+ # number_to_percentage:(1000, precision: nil) # => 1000%
# number_to_percentage('98a') # => 98a%
# number_to_percentage(100, format: '%n %') # => 100 %
def number_to_percentage(number, options = {})
- return unless number
- options = options.symbolize_keys
-
- defaults = format_options(options[:locale], :percentage)
- options = defaults.merge!(options)
-
- format = options[:format] || "%n%"
- format.gsub('%n', self.number_to_rounded(number, options))
+ NumberToPercentageConverter.convert(number, options)
end
# Formats a +number+ with grouped thousands using +delimiter+
@@ -290,15 +149,7 @@ module ActiveSupport
# number_to_delimited(98765432.98, delimiter: ' ', separator: ',')
# # => 98 765 432,98
def number_to_delimited(number, options = {})
- options = options.symbolize_keys
-
- return number unless valid_float?(number)
-
- options = format_options(options[:locale]).merge!(options)
-
- parts = number.to_s.split('.')
- parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}")
- parts.join(options[:separator])
+ NumberToDelimitedConverter.convert(number, options)
end
# Formats a +number+ with the specified level of
@@ -342,43 +193,7 @@ module ActiveSupport
# number_to_rounded(1111.2345, precision: 2, separator: ',', delimiter: '.')
# # => 1.111,23
def number_to_rounded(number, options = {})
- return number unless valid_float?(number)
- options = options.symbolize_keys
-
- defaults = format_options(options[:locale], :precision)
- options = defaults.merge!(options)
-
- precision = options.delete :precision
- significant = options.delete :significant
- strip_insignificant_zeros = options.delete :strip_insignificant_zeros
-
- if precision.nil?
- formatted_number = self.number_to_delimited(number, options)
- else
- number = Float(number)
- if significant && precision > 0
- if number == 0
- digits, rounded_number = 1, 0
- else
- digits = (Math.log10(number.abs) + 1).floor
- multiplier = 10 ** (digits - precision)
- rounded_number = (BigDecimal.new(number.to_s) / BigDecimal.new(multiplier.to_f.to_s)).round.to_f * multiplier
- digits = (Math.log10(rounded_number.abs) + 1).floor # After rounding, the number of digits may have changed
- end
- precision -= digits
- precision = 0 if precision < 0 # don't let it be negative
- else
- rounded_number = BigDecimal.new(number.to_s).round(precision).to_f
- rounded_number = rounded_number.abs if rounded_number.zero? # prevent showing negative zeros
- end
- formatted_number = self.number_to_delimited("%01.#{precision}f" % rounded_number, options)
- end
- if strip_insignificant_zeros
- escaped_separator = Regexp.escape(options[:separator])
- formatted_number.sub(/(#{escaped_separator})(\d*[1-9])?0+\z/, '\1\2').sub(/#{escaped_separator}\z/, '')
- else
- formatted_number
- end
+ NumberToRoundedConverter.convert(number, options)
end
# Formats the bytes in +number+ into a more understandable
@@ -419,43 +234,10 @@ module ActiveSupport
# number_to_human_size(1234567, precision: 2) # => 1.2 MB
# number_to_human_size(483989, precision: 2) # => 470 KB
# number_to_human_size(1234567, precision: 2, separator: ',') # => 1,2 MB
- #
- # Non-significant zeros after the fractional separator are stripped out by
- # default (set <tt>:strip_insignificant_zeros</tt> to +false+ to change that):
- #
- # number_to_human_size(1234567890123, precision: 5) # => "1.1229 TB"
- # number_to_human_size(524288000, precision: 5) # => "500 MB"
+ # number_to_human_size(1234567890123, precision: 5) # => "1.1228 TB"
+ # number_to_human_size(524288000, precision: 5) # => "500 MB"
def number_to_human_size(number, options = {})
- options = options.symbolize_keys
-
- return number unless valid_float?(number)
- number = Float(number)
-
- defaults = format_options(options[:locale], :human)
- options = defaults.merge!(options)
-
- #for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
- options[:strip_insignificant_zeros] = true if not options.key?(:strip_insignificant_zeros)
-
- storage_units_format = translate_number_value_with_default('human.storage_units.format', :locale => options[:locale], :raise => true)
-
- base = options[:prefix] == :si ? 1000 : 1024
-
- if number.to_i < base
- unit = translate_number_value_with_default('human.storage_units.units.byte', :locale => options[:locale], :count => number.to_i, :raise => true)
- storage_units_format.gsub(/%n/, number.to_i.to_s).gsub(/%u/, unit)
- else
- max_exp = STORAGE_UNITS.size - 1
- exponent = (Math.log(number) / Math.log(base)).to_i # Convert to base
- exponent = max_exp if exponent > max_exp # we need this to avoid overflow for the highest unit
- number /= base ** exponent
-
- unit_key = STORAGE_UNITS[exponent]
- unit = translate_number_value_with_default("human.storage_units.units.#{unit_key}", :locale => options[:locale], :count => number, :raise => true)
-
- formatted_number = self.number_to_rounded(number, options)
- storage_units_format.gsub(/%n/, formatted_number).gsub(/%u/, unit)
- end
+ NumberToHumanSizeConverter.convert(number, options)
end
# Pretty prints (formats and approximates) a number in a way it
@@ -466,7 +248,7 @@ module ActiveSupport
# See <tt>number_to_human_size</tt> if you want to print a file
# size.
#
- # You can also define you own unit-quantifier names if you want
+ # You can also define your own unit-quantifier names if you want
# to use other decimal units (eg.: 1500 becomes "1.5
# kilometers", 0.150 becomes "150 milliliters", etc). You may
# define a wide range of unit quantifiers, even fractional ones
@@ -492,12 +274,12 @@ module ActiveSupport
# string containing an i18n scope where to find this hash. It
# might have the following keys:
# * *integers*: <tt>:unit</tt>, <tt>:ten</tt>,
- # *<tt>:hundred</tt>, <tt>:thousand</tt>, <tt>:million</tt>,
- # *<tt>:billion</tt>, <tt>:trillion</tt>,
- # *<tt>:quadrillion</tt>
+ # <tt>:hundred</tt>, <tt>:thousand</tt>, <tt>:million</tt>,
+ # <tt>:billion</tt>, <tt>:trillion</tt>,
+ # <tt>:quadrillion</tt>
# * *fractionals*: <tt>:deci</tt>, <tt>:centi</tt>,
- # *<tt>:mili</tt>, <tt>:micro</tt>, <tt>:nano</tt>,
- # *<tt>:pico</tt>, <tt>:femto</tt>
+ # <tt>:mili</tt>, <tt>:micro</tt>, <tt>:nano</tt>,
+ # <tt>:pico</tt>, <tt>:femto</tt>
# * <tt>:format</tt> - Sets the format of the output string
# (defaults to "%n %u"). The field types are:
# * %u - The quantifier (ex.: 'thousand')
@@ -521,12 +303,15 @@ module ActiveSupport
# separator: ',',
# significant: false) # => "1,2 Million"
#
+ # number_to_human(500000000, precision: 5) # => "500 Million"
+ # number_to_human(12345012345, significant: false) # => "12.345 Billion"
+ #
# Non-significant zeros after the decimal separator are stripped
# out by default (set <tt>:strip_insignificant_zeros</tt> to
# +false+ to change that):
#
- # number_to_human(12345012345, significant_digits: 6) # => "12.345 Billion"
- # number_to_human(500000000, precision: 5) # => "500 Million"
+ # number_to_human(12.00001) # => "12"
+ # number_to_human(12.00001, strip_insignificant_zeros: false) # => "12.0"
#
# ==== Custom Unit Quantifiers
#
@@ -556,88 +341,7 @@ module ActiveSupport
# number_to_human(1, units: :distance) # => "1 meter"
# number_to_human(0.34, units: :distance) # => "34 centimeters"
def number_to_human(number, options = {})
- options = options.symbolize_keys
-
- return number unless valid_float?(number)
- number = Float(number)
-
- defaults = format_options(options[:locale], :human)
- options = defaults.merge!(options)
-
- #for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
- options[:strip_insignificant_zeros] = true if not options.key?(:strip_insignificant_zeros)
-
- inverted_du = DECIMAL_UNITS.invert
-
- units = options.delete :units
- unit_exponents = case units
- when Hash
- units
- when String, Symbol
- I18n.translate(:"#{units}", :locale => options[:locale], :raise => true)
- when nil
- translate_number_value_with_default("human.decimal_units.units", :locale => options[:locale], :raise => true)
- else
- raise ArgumentError, ":units must be a Hash or String translation scope."
- end.keys.map{|e_name| inverted_du[e_name] }.sort_by{|e| -e}
-
- number_exponent = number != 0 ? Math.log10(number.abs).floor : 0
- display_exponent = unit_exponents.find{ |e| number_exponent >= e } || 0
- number /= 10 ** display_exponent
-
- unit = case units
- when Hash
- units[DECIMAL_UNITS[display_exponent]] || ''
- when String, Symbol
- I18n.translate(:"#{units}.#{DECIMAL_UNITS[display_exponent]}", :locale => options[:locale], :count => number.to_i)
- else
- translate_number_value_with_default("human.decimal_units.units.#{DECIMAL_UNITS[display_exponent]}", :locale => options[:locale], :count => number.to_i)
- end
-
- decimal_format = options[:format] || translate_number_value_with_default('human.decimal_units.format', :locale => options[:locale])
- formatted_number = self.number_to_rounded(number, options)
- decimal_format.gsub(/%n/, formatted_number).gsub(/%u/, unit).strip
- end
-
- def self.private_module_and_instance_method(method_name) #:nodoc:
- private method_name
- private_class_method method_name
- end
- private_class_method :private_module_and_instance_method
-
- def format_options(locale, namespace = nil) #:nodoc:
- default_format_options(namespace).merge!(i18n_format_options(locale, namespace))
- end
- private_module_and_instance_method :format_options
-
- def default_format_options(namespace = nil) #:nodoc:
- options = DEFAULTS[:format].dup
- options.merge!(DEFAULTS[namespace][:format]) if namespace
- options
- end
- private_module_and_instance_method :default_format_options
-
- def i18n_format_options(locale, namespace = nil) #:nodoc:
- options = I18n.translate(:'number.format', locale: locale, default: {}).dup
- if namespace
- options.merge!(I18n.translate(:"number.#{namespace}.format", locale: locale, default: {}))
- end
- options
- end
- private_module_and_instance_method :i18n_format_options
-
- def translate_number_value_with_default(key, i18n_options = {}) #:nodoc:
- default = key.split('.').reduce(DEFAULTS) { |defaults, k| defaults[k.to_sym] }
-
- I18n.translate(key, { default: default, scope: :number }.merge!(i18n_options))
- end
- private_module_and_instance_method :translate_number_value_with_default
-
- def valid_float?(number) #:nodoc:
- Float(number)
- rescue ArgumentError, TypeError
- false
+ NumberToHumanConverter.convert(number, options)
end
- private_module_and_instance_method :valid_float?
end
end
diff --git a/activesupport/lib/active_support/number_helper/number_converter.rb b/activesupport/lib/active_support/number_helper/number_converter.rb
new file mode 100644
index 0000000000..9d976f1831
--- /dev/null
+++ b/activesupport/lib/active_support/number_helper/number_converter.rb
@@ -0,0 +1,182 @@
+require 'active_support/core_ext/big_decimal/conversions'
+require 'active_support/core_ext/object/blank'
+require 'active_support/core_ext/hash/keys'
+require 'active_support/i18n'
+require 'active_support/core_ext/class/attribute'
+
+module ActiveSupport
+ module NumberHelper
+ class NumberConverter # :nodoc:
+ # Default and i18n option namespace per class
+ class_attribute :namespace
+
+ # Does the object need a number that is a valid float?
+ class_attribute :validate_float
+
+ attr_reader :number, :opts
+
+ DEFAULTS = {
+ # Used in number_to_delimited
+ # These are also the defaults for 'currency', 'percentage', 'precision', and 'human'
+ format: {
+ # Sets the separator between the units, for more precision (e.g. 1.0 / 2.0 == 0.5)
+ separator: ".",
+ # Delimits thousands (e.g. 1,000,000 is a million) (always in groups of three)
+ delimiter: ",",
+ # Number of decimals, behind the separator (the number 1 with a precision of 2 gives: 1.00)
+ precision: 3,
+ # If set to true, precision will mean the number of significant digits instead
+ # of the number of decimal digits (1234 with precision 2 becomes 1200, 1.23543 becomes 1.2)
+ significant: false,
+ # If set, the zeros after the decimal separator will always be stripped (eg.: 1.200 will be 1.2)
+ strip_insignificant_zeros: false
+ },
+
+ # Used in number_to_currency
+ currency: {
+ format: {
+ format: "%u%n",
+ negative_format: "-%u%n",
+ unit: "$",
+ # These five are to override number.format and are optional
+ separator: ".",
+ delimiter: ",",
+ precision: 2,
+ significant: false,
+ strip_insignificant_zeros: false
+ }
+ },
+
+ # Used in number_to_percentage
+ percentage: {
+ format: {
+ delimiter: "",
+ format: "%n%"
+ }
+ },
+
+ # Used in number_to_rounded
+ precision: {
+ format: {
+ delimiter: ""
+ }
+ },
+
+ # Used in number_to_human_size and number_to_human
+ human: {
+ format: {
+ # These five are to override number.format and are optional
+ delimiter: "",
+ precision: 3,
+ significant: true,
+ strip_insignificant_zeros: true
+ },
+ # Used in number_to_human_size
+ storage_units: {
+ # Storage units output formatting.
+ # %u is the storage unit, %n is the number (default: 2 MB)
+ format: "%n %u",
+ units: {
+ byte: "Bytes",
+ kb: "KB",
+ mb: "MB",
+ gb: "GB",
+ tb: "TB"
+ }
+ },
+ # Used in number_to_human
+ decimal_units: {
+ format: "%n %u",
+ # Decimal units output formatting
+ # By default we will only quantify some of the exponents
+ # but the commented ones might be defined or overridden
+ # by the user.
+ units: {
+ # femto: Quadrillionth
+ # pico: Trillionth
+ # nano: Billionth
+ # micro: Millionth
+ # mili: Thousandth
+ # centi: Hundredth
+ # deci: Tenth
+ unit: "",
+ # ten:
+ # one: Ten
+ # other: Tens
+ # hundred: Hundred
+ thousand: "Thousand",
+ million: "Million",
+ billion: "Billion",
+ trillion: "Trillion",
+ quadrillion: "Quadrillion"
+ }
+ }
+ }
+ }
+
+ def self.convert(number, options)
+ new(number, options).execute
+ end
+
+ def initialize(number, options)
+ @number = number
+ @opts = options.symbolize_keys
+ end
+
+ def execute
+ if !number
+ nil
+ elsif validate_float? && !valid_float?
+ number
+ else
+ convert
+ end
+ end
+
+ private
+
+ def options
+ @options ||= format_options.merge(opts)
+ end
+
+ def format_options #:nodoc:
+ default_format_options.merge!(i18n_format_options)
+ end
+
+ def default_format_options #:nodoc:
+ options = DEFAULTS[:format].dup
+ options.merge!(DEFAULTS[namespace][:format]) if namespace
+ options
+ end
+
+ def i18n_format_options #:nodoc:
+ locale = opts[:locale]
+ options = I18n.translate(:'number.format', locale: locale, default: {}).dup
+
+ if namespace
+ options.merge!(I18n.translate(:"number.#{namespace}.format", locale: locale, default: {}))
+ end
+
+ options
+ end
+
+ def translate_number_value_with_default(key, i18n_options = {}) #:nodoc:
+ I18n.translate(key, { default: default_value(key), scope: :number }.merge!(i18n_options))
+ end
+
+ def translate_in_locale(key, i18n_options = {})
+ translate_number_value_with_default(key, { locale: options[:locale] }.merge(i18n_options))
+ end
+
+ def default_value(key)
+ key.split('.').reduce(DEFAULTS) { |defaults, k| defaults[k.to_sym] }
+ end
+
+ def valid_float? #:nodoc:
+ Float(number)
+ rescue ArgumentError, TypeError
+ false
+ end
+ end
+ end
+end
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
new file mode 100644
index 0000000000..fb5adb574a
--- /dev/null
+++ b/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb
@@ -0,0 +1,46 @@
+module ActiveSupport
+ module NumberHelper
+ class NumberToCurrencyConverter < NumberConverter # :nodoc:
+ self.namespace = :currency
+
+ def convert
+ number = self.number.to_s.strip
+ format = options[:format]
+
+ if is_negative?(number)
+ format = options[:negative_format]
+ number = absolute_value(number)
+ end
+
+ rounded_number = NumberToRoundedConverter.convert(number, options)
+ format.gsub(/%n/, rounded_number).gsub(/%u/, options[:unit])
+ end
+
+ private
+
+ def is_negative?(number)
+ number.to_f.phase != 0
+ end
+
+ def absolute_value(number)
+ number.respond_to?("abs") ? number.abs : number.sub(/\A-/, '')
+ end
+
+ def options
+ @options ||= begin
+ defaults = default_format_options.merge(i18n_opts)
+ # Override negative format if format options is given
+ defaults[:negative_format] = "-#{opts[:format]}" if opts[:format]
+ defaults.merge!(opts)
+ end
+ end
+
+ def i18n_opts
+ # Set International negative format if not exists
+ i18n = i18n_format_options
+ i18n[:negative_format] ||= "-#{i18n[:format]}" if i18n[:format]
+ i18n
+ end
+ end
+ end
+end
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
new file mode 100644
index 0000000000..d85cc086d7
--- /dev/null
+++ b/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb
@@ -0,0 +1,23 @@
+module ActiveSupport
+ module NumberHelper
+ class NumberToDelimitedConverter < NumberConverter #:nodoc:
+ self.validate_float = true
+
+ DELIMITED_REGEX = /(\d)(?=(\d\d\d)+(?!\d))/
+
+ def convert
+ parts.join(options[:separator])
+ end
+
+ private
+
+ def parts
+ left, right = number.to_s.split('.')
+ left.gsub!(DELIMITED_REGEX) do |digit_to_delimit|
+ "#{digit_to_delimit}#{options[:delimiter]}"
+ end
+ [left, right].compact
+ end
+ end
+ end
+end
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
new file mode 100644
index 0000000000..6940beb318
--- /dev/null
+++ b/activesupport/lib/active_support/number_helper/number_to_human_converter.rb
@@ -0,0 +1,66 @@
+module ActiveSupport
+ module NumberHelper
+ class NumberToHumanConverter < NumberConverter # :nodoc:
+ DECIMAL_UNITS = { 0 => :unit, 1 => :ten, 2 => :hundred, 3 => :thousand, 6 => :million, 9 => :billion, 12 => :trillion, 15 => :quadrillion,
+ -1 => :deci, -2 => :centi, -3 => :mili, -6 => :micro, -9 => :nano, -12 => :pico, -15 => :femto }
+ INVERTED_DECIMAL_UNITS = DECIMAL_UNITS.invert
+
+ self.namespace = :human
+ self.validate_float = true
+
+ def convert # :nodoc:
+ @number = Float(number)
+
+ # for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
+ unless options.key?(:strip_insignificant_zeros)
+ options[:strip_insignificant_zeros] = true
+ end
+
+ units = opts[:units]
+ exponent = calculate_exponent(units)
+ @number = number / (10 ** exponent)
+
+ unit = determine_unit(units, exponent)
+
+ rounded_number = NumberToRoundedConverter.convert(number, options)
+ format.gsub(/%n/, rounded_number).gsub(/%u/, unit).strip
+ end
+
+ private
+
+ def format
+ options[:format] || translate_in_locale('human.decimal_units.format')
+ end
+
+ def determine_unit(units, exponent)
+ exp = DECIMAL_UNITS[exponent]
+ case units
+ when Hash
+ units[exp] || ''
+ when String, Symbol
+ I18n.translate("#{units}.#{exp}", :locale => options[:locale], :count => number.to_i)
+ else
+ translate_in_locale("human.decimal_units.units.#{exp}", count: number.to_i)
+ end
+ end
+
+ def calculate_exponent(units)
+ exponent = number != 0 ? Math.log10(number.abs).floor : 0
+ unit_exponents(units).find { |e| exponent >= e } || 0
+ end
+
+ def unit_exponents(units)
+ case units
+ when Hash
+ units
+ when String, Symbol
+ I18n.translate(units.to_s, :locale => options[:locale], :raise => true)
+ when nil
+ translate_in_locale("human.decimal_units.units", raise: true)
+ else
+ raise ArgumentError, ":units must be a Hash or String translation scope."
+ end.keys.map { |e_name| INVERTED_DECIMAL_UNITS[e_name] }.sort_by(&:-@)
+ end
+ end
+ end
+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
new file mode 100644
index 0000000000..78d2c9ae6e
--- /dev/null
+++ b/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb
@@ -0,0 +1,58 @@
+module ActiveSupport
+ module NumberHelper
+ class NumberToHumanSizeConverter < NumberConverter #:nodoc:
+ STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb]
+
+ self.namespace = :human
+ self.validate_float = true
+
+ def convert
+ @number = Float(number)
+
+ # for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
+ unless options.key?(:strip_insignificant_zeros)
+ options[:strip_insignificant_zeros] = true
+ end
+
+ if smaller_than_base?
+ number_to_format = number.to_i.to_s
+ else
+ human_size = number / (base ** exponent)
+ number_to_format = NumberToRoundedConverter.convert(human_size, options)
+ end
+ conversion_format.gsub(/%n/, number_to_format).gsub(/%u/, unit)
+ end
+
+ private
+
+ def conversion_format
+ translate_number_value_with_default('human.storage_units.format', :locale => options[:locale], :raise => true)
+ end
+
+ def unit
+ translate_number_value_with_default(storage_unit_key, :locale => options[:locale], :count => number.to_i, :raise => true)
+ end
+
+ def storage_unit_key
+ key_end = smaller_than_base? ? 'byte' : STORAGE_UNITS[exponent]
+ "human.storage_units.units.#{key_end}"
+ end
+
+ def exponent
+ max = STORAGE_UNITS.size - 1
+ exp = (Math.log(number) / Math.log(base)).to_i
+ exp = max if exp > max # avoid overflow for the highest unit
+ exp
+ end
+
+ def smaller_than_base?
+ number.to_i < base
+ end
+
+ def base
+ opts[:prefix] == :si ? 1000 : 1024
+ end
+ end
+ end
+end
+
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
new file mode 100644
index 0000000000..1af294a03e
--- /dev/null
+++ b/activesupport/lib/active_support/number_helper/number_to_percentage_converter.rb
@@ -0,0 +1,12 @@
+module ActiveSupport
+ module NumberHelper
+ class NumberToPercentageConverter < NumberConverter # :nodoc:
+ self.namespace = :percentage
+
+ def convert
+ rounded_number = NumberToRoundedConverter.convert(number, options)
+ options[:format].gsub(/%n/, rounded_number)
+ end
+ end
+ end
+end
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
new file mode 100644
index 0000000000..af2ee56d91
--- /dev/null
+++ b/activesupport/lib/active_support/number_helper/number_to_phone_converter.rb
@@ -0,0 +1,49 @@
+module ActiveSupport
+ module NumberHelper
+ class NumberToPhoneConverter < NumberConverter #:nodoc:
+ def convert
+ str = country_code(opts[:country_code])
+ str << convert_to_phone_number(number.to_s.strip)
+ str << phone_ext(opts[:extension])
+ end
+
+ private
+
+ def convert_to_phone_number(number)
+ if opts[:area_code]
+ convert_with_area_code(number)
+ else
+ convert_without_area_code(number)
+ end
+ end
+
+ def convert_with_area_code(number)
+ number.gsub!(/(\d{1,3})(\d{3})(\d{4}$)/,"(\\1) \\2#{delimiter}\\3")
+ number
+ end
+
+ def convert_without_area_code(number)
+ number.gsub!(/(\d{0,3})(\d{3})(\d{4})$/,"\\1#{delimiter}\\2#{delimiter}\\3")
+ number.slice!(0, 1) if start_with_delimiter?(number)
+ number
+ end
+
+ def start_with_delimiter?(number)
+ delimiter.present? && number.start_with?(delimiter)
+ end
+
+ def delimiter
+ opts[:delimiter] || "-"
+ end
+
+ def country_code(code)
+ code.blank? ? "" : "+#{code}#{delimiter}"
+ end
+
+ def phone_ext(ext)
+ ext.blank? ? "" : " x #{ext}"
+ end
+ end
+ end
+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
new file mode 100644
index 0000000000..df316a08e6
--- /dev/null
+++ b/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb
@@ -0,0 +1,90 @@
+module ActiveSupport
+ module NumberHelper
+ class NumberToRoundedConverter < NumberConverter # :nodoc:
+ self.namespace = :precision
+ self.validate_float = true
+
+ def convert
+ precision = options.delete :precision
+
+ 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)
+ 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 = rounded_number.abs if rounded_number.zero? # prevent showing negative zeros
+ end
+
+ formatted_string =
+ if BigDecimal === rounded_number && rounded_number.finite?
+ s = rounded_number.to_s('F') + '0'*precision
+ a, b = s.split('.', 2)
+ a + '.' + b[0, precision]
+ else
+ "%00.#{precision}f" % rounded_number
+ end
+ else
+ formatted_string = number
+ end
+
+ delimited_number = NumberToDelimitedConverter.convert(formatted_string, options)
+ format_number(delimited_number)
+ end
+
+ 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
+
+ def format_number(number)
+ if strip_insignificant_zeros
+ escaped_separator = Regexp.escape(options[:separator])
+ number.sub(/(#{escaped_separator})(\d*[1-9])?0+\z/, '\1\2').sub(/#{escaped_separator}\z/, '')
+ else
+ 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/option_merger.rb b/activesupport/lib/active_support/option_merger.rb
index e55ffd12c3..dea84e437f 100644
--- a/activesupport/lib/active_support/option_merger.rb
+++ b/activesupport/lib/active_support/option_merger.rb
@@ -12,7 +12,7 @@ module ActiveSupport
private
def method_missing(method, *arguments, &block)
- if arguments.last.is_a?(Proc)
+ if arguments.first.is_a?(Proc)
proc = arguments.pop
arguments << lambda { |*args| @options.deep_merge(proc.call(*args)) }
else
diff --git a/activesupport/lib/active_support/ordered_hash.rb b/activesupport/lib/active_support/ordered_hash.rb
index 1a3693f766..4680d5acb7 100644
--- a/activesupport/lib/active_support/ordered_hash.rb
+++ b/activesupport/lib/active_support/ordered_hash.rb
@@ -28,6 +28,14 @@ module ActiveSupport
coder.represent_seq '!omap', map { |k,v| { k => v } }
end
+ def select(*args, &block)
+ dup.tap { |hash| hash.select!(*args, &block) }
+ end
+
+ def reject(*args, &block)
+ dup.tap { |hash| hash.reject!(*args, &block) }
+ end
+
def nested_under_indifferent_access
self
end
diff --git a/activesupport/lib/active_support/ordered_options.rb b/activesupport/lib/active_support/ordered_options.rb
index e03bb4ca0f..a33e2c58a9 100644
--- a/activesupport/lib/active_support/ordered_options.rb
+++ b/activesupport/lib/active_support/ordered_options.rb
@@ -41,7 +41,7 @@ module ActiveSupport
end
# +InheritableOptions+ provides a constructor to build an +OrderedOptions+
- # hash inherited from the another hash.
+ # hash inherited from another hash.
#
# Use this if you already have some hash and you want to create a new one based on it.
#
diff --git a/activesupport/lib/active_support/per_thread_registry.rb b/activesupport/lib/active_support/per_thread_registry.rb
index aa682fb36c..ca2e4d5625 100644
--- a/activesupport/lib/active_support/per_thread_registry.rb
+++ b/activesupport/lib/active_support/per_thread_registry.rb
@@ -32,21 +32,22 @@ module ActiveSupport
#
# If the class has an initializer, it must accept no arguments.
module PerThreadRegistry
- protected
+ def self.extended(object)
+ object.instance_variable_set '@per_thread_registry_key', object.name.freeze
+ end
+
+ def instance
+ Thread.current[@per_thread_registry_key] ||= new
+ end
+ protected
def method_missing(name, *args, &block) # :nodoc:
# Caches the method definition as a singleton method of the receiver.
define_singleton_method(name) do |*a, &b|
- per_thread_registry_instance.public_send(name, *a, &b)
+ instance.public_send(name, *a, &b)
end
send(name, *args, &block)
end
-
- private
-
- def per_thread_registry_instance
- Thread.current[name] ||= new
- end
end
end
diff --git a/activesupport/lib/active_support/railtie.rb b/activesupport/lib/active_support/railtie.rb
index 133aa6a054..6eba24b569 100644
--- a/activesupport/lib/active_support/railtie.rb
+++ b/activesupport/lib/active_support/railtie.rb
@@ -13,6 +13,13 @@ module ActiveSupport
end
end
+ initializer "active_support.halt_callback_chains_on_return_false", after: :load_config_initializers do |app|
+ if app.config.active_support.key? :halt_callback_chains_on_return_false
+ ActiveSupport::Callbacks::CallbackChain.halt_and_display_warning_on_return_false = \
+ app.config.active_support.halt_callback_chains_on_return_false
+ end
+ end
+
# Sets the default value for Time.zone
# If assigned value cannot be matched to a TimeZone, an exception will be raised.
initializer "active_support.initialize_time_zone" do |app|
diff --git a/activesupport/lib/active_support/rescuable.rb b/activesupport/lib/active_support/rescuable.rb
index a7eba91ac5..1a02acd5b1 100644
--- a/activesupport/lib/active_support/rescuable.rb
+++ b/activesupport/lib/active_support/rescuable.rb
@@ -60,7 +60,7 @@ module ActiveSupport
end
klasses.each do |klass|
- key = if klass.is_a?(Class) && klass <= Exception
+ key = if klass.is_a?(Module) && klass.respond_to?(:===)
klass.name
elsif klass.is_a?(String)
klass
@@ -101,7 +101,7 @@ module ActiveSupport
# itself when rescue_from CONSTANT is executed.
klass = self.class.const_get(klass_name) rescue nil
klass ||= klass_name.constantize rescue nil
- exception.is_a?(klass) if klass
+ klass === exception if klass
end
case rescuer
diff --git a/activesupport/lib/active_support/security_utils.rb b/activesupport/lib/active_support/security_utils.rb
new file mode 100644
index 0000000000..64c4801179
--- /dev/null
+++ b/activesupport/lib/active_support/security_utils.rb
@@ -0,0 +1,20 @@
+module ActiveSupport
+ module SecurityUtils
+ # Constant time string comparison.
+ #
+ # 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
+
+ l = a.unpack "C#{a.bytesize}"
+
+ res = 0
+ b.each_byte { |byte| res |= byte ^ l.shift }
+ res == 0
+ end
+ module_function :secure_compare
+ end
+end
diff --git a/activesupport/lib/active_support/subscriber.rb b/activesupport/lib/active_support/subscriber.rb
index 34c6f900c1..98be78b41b 100644
--- a/activesupport/lib/active_support/subscriber.rb
+++ b/activesupport/lib/active_support/subscriber.rb
@@ -31,22 +31,54 @@ module ActiveSupport
# Attach the subscriber to a namespace.
def attach_to(namespace, subscriber=new, notifier=ActiveSupport::Notifications)
+ @namespace = namespace
+ @subscriber = subscriber
+ @notifier = notifier
+
subscribers << subscriber
+ # Add event subscribers for all existing methods on the class.
subscriber.public_methods(false).each do |event|
- next if %w{ start finish }.include?(event.to_s)
+ add_event_subscriber(event)
+ end
+ end
- notifier.subscribe("#{event}.#{namespace}", subscriber)
+ # Adds event subscribers for all new methods added to the class.
+ def method_added(event)
+ # Only public methods are added as subscribers, and only if a notifier
+ # has been set up. This means that subscribers will only be set up for
+ # classes that call #attach_to.
+ if public_method_defined?(event) && notifier
+ add_event_subscriber(event)
end
end
def subscribers
@@subscribers ||= []
end
+
+ protected
+
+ attr_reader :subscriber, :notifier, :namespace
+
+ def add_event_subscriber(event)
+ return if %w{ start finish }.include?(event.to_s)
+
+ pattern = "#{event}.#{namespace}"
+
+ # don't add multiple subscribers (eg. if methods are redefined)
+ return if subscriber.patterns.include?(pattern)
+
+ subscriber.patterns << pattern
+ notifier.subscribe(pattern, subscriber)
+ end
end
+ attr_reader :patterns # :nodoc:
+
def initialize
@queue_key = [self.class.name, object_id].join "-"
+ @patterns = []
super
end
@@ -71,7 +103,7 @@ module ActiveSupport
private
def event_stack
- SubscriberQueueRegistry.get_queue(@queue_key)
+ SubscriberQueueRegistry.instance.get_queue(@queue_key)
end
end
diff --git a/activesupport/lib/active_support/tagged_logging.rb b/activesupport/lib/active_support/tagged_logging.rb
index 18bc919734..9086a959aa 100644
--- a/activesupport/lib/active_support/tagged_logging.rb
+++ b/activesupport/lib/active_support/tagged_logging.rb
@@ -1,3 +1,4 @@
+require 'active_support/core_ext/module/delegation'
require 'active_support/core_ext/object/blank'
require 'logger'
require 'active_support/logger'
@@ -42,7 +43,9 @@ module ActiveSupport
end
def current_tags
- Thread.current[:activesupport_tagged_logging_tags] ||= []
+ # We use our object ID here to void conflicting with other instances
+ thread_key = @thread_key ||= "activesupport_tagged_logging_tags:#{object_id}".freeze
+ Thread.current[thread_key] ||= []
end
private
diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb
index 2f27aff2bd..739823bd56 100644
--- a/activesupport/lib/active_support/test_case.rb
+++ b/activesupport/lib/active_support/test_case.rb
@@ -7,52 +7,56 @@ require 'active_support/testing/deprecation'
require 'active_support/testing/declarative'
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'
-require 'active_support/deprecation'
-
-begin
- silence_warnings { require 'mocha/setup' }
-rescue LoadError
-end
-
-module Minitest # :nodoc:
- class << self
- remove_method :__run
- end
-
- def self.__run reporter, options # :nodoc:
- # FIXME: MT5's runnables is not ordered. This is needed because
- # we have have tests have cross-class order-dependent bugs.
- suites = Runnable.runnables.sort_by { |ts| ts.name.to_s }
-
- parallel, serial = suites.partition { |s| s.test_order == :parallel }
-
- ParallelEach.new(parallel).map { |suite| suite.run reporter, options } +
- serial.map { |suite| suite.run reporter, options }
- end
-end
module ActiveSupport
class TestCase < ::Minitest::Test
Assertion = Minitest::Assertion
- alias_method :method_name, :name
+ class << self
+ # Sets the order in which test cases are run.
+ #
+ # ActiveSupport::TestCase.test_order = :random # => :random
+ #
+ # Valid values are:
+ # * +:random+ (to run tests in random order)
+ # * +:parallel+ (to run tests in parallel)
+ # * +:sorted+ (to run tests alphabetically by method name)
+ # * +:alpha+ (equivalent to +:sorted+)
+ def test_order=(new_order)
+ ActiveSupport.test_order = new_order
+ end
- $tags = {}
- def self.for_tag(tag)
- yield if $tags[tag]
- end
+ # Returns the order in which test cases are run.
+ #
+ # ActiveSupport::TestCase.test_order # => :random
+ #
+ # Possible values are +:random+, +:parallel+, +:alpha+, +:sorted+.
+ # Defaults to +:random+.
+ def test_order
+ test_order = ActiveSupport.test_order
+
+ if test_order.nil?
+ test_order = :random
+ self.test_order = test_order
+ end
- # FIXME: we have tests that depend on run order, we should fix that and
- # remove this method.
- def self.test_order # :nodoc:
- :sorted
+ test_order
+ end
+
+ alias :my_tests_are_order_dependent! :i_suck_and_my_tests_are_order_dependent!
end
+ alias_method :method_name, :name
+
include ActiveSupport::Testing::TaggedLogging
include ActiveSupport::Testing::SetupAndTeardown
include ActiveSupport::Testing::Assertions
include ActiveSupport::Testing::Deprecation
+ include ActiveSupport::Testing::TimeHelpers
+ include ActiveSupport::Testing::FileFixtures
extend ActiveSupport::Testing::Declarative
# test/unit backwards compatibility methods
diff --git a/activesupport/lib/active_support/testing/assertions.rb b/activesupport/lib/active_support/testing/assertions.rb
index 76a591bc3b..8b649c193f 100644
--- a/activesupport/lib/active_support/testing/assertions.rb
+++ b/activesupport/lib/active_support/testing/assertions.rb
@@ -9,7 +9,7 @@ module ActiveSupport
#
# assert_not nil # => true
# assert_not false # => true
- # assert_not 'foo' # => 'foo' is not nil or false
+ # assert_not 'foo' # => Expected "foo" to be nil or false
#
# An error message can be specified.
#
@@ -66,7 +66,7 @@ module ActiveSupport
exps = expressions.map { |e|
e.respond_to?(:call) ? e : lambda { eval(e, block.binding) }
}
- before = exps.map { |e| e.call }
+ before = exps.map(&:call)
yield
diff --git a/activesupport/lib/active_support/testing/constant_lookup.rb b/activesupport/lib/active_support/testing/constant_lookup.rb
index 1b2a75c35d..07d477c0db 100644
--- a/activesupport/lib/active_support/testing/constant_lookup.rb
+++ b/activesupport/lib/active_support/testing/constant_lookup.rb
@@ -36,12 +36,8 @@ module ActiveSupport
while names.size > 0 do
names.last.sub!(/Test$/, "")
begin
- constant = names.join("::").constantize
+ constant = names.join("::").safe_constantize
break(constant) if yield(constant)
- rescue NoMethodError # subclass of NameError
- raise
- rescue NameError
- # Constant wasn't found, move on
ensure
names.pop
end
diff --git a/activesupport/lib/active_support/testing/declarative.rb b/activesupport/lib/active_support/testing/declarative.rb
index 1fa73caefa..0bf3643a56 100644
--- a/activesupport/lib/active_support/testing/declarative.rb
+++ b/activesupport/lib/active_support/testing/declarative.rb
@@ -1,23 +1,6 @@
module ActiveSupport
module Testing
module Declarative
-
- def self.extended(klass) #:nodoc:
- klass.class_eval do
-
- unless method_defined?(:describe)
- def self.describe(text)
- class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
- def self.name
- "#{text}"
- end
- RUBY_EVAL
- end
- end
-
- end
- end
-
unless defined?(Spec)
# Helper to define a test method using a String. Under the hood, it replaces
# spaces with underscores and defines the test method.
@@ -27,7 +10,7 @@ module ActiveSupport
# end
def test(name, &block)
test_name = "test_#{name.gsub(/\s+/,'_')}".to_sym
- defined = instance_method(test_name) rescue false
+ defined = method_defined? test_name
raise "#{test_name} is already defined in #{self}" if defined
if block_given?
define_method(test_name, &block)
diff --git a/activesupport/lib/active_support/testing/deprecation.rb b/activesupport/lib/active_support/testing/deprecation.rb
index a8342904dc..6c94c611b6 100644
--- a/activesupport/lib/active_support/testing/deprecation.rb
+++ b/activesupport/lib/active_support/testing/deprecation.rb
@@ -19,18 +19,17 @@ module ActiveSupport
result
end
- private
- def collect_deprecations
- old_behavior = ActiveSupport::Deprecation.behavior
- deprecations = []
- ActiveSupport::Deprecation.behavior = Proc.new do |message, callstack|
- deprecations << message
- end
- result = yield
- [result, deprecations]
- ensure
- ActiveSupport::Deprecation.behavior = old_behavior
+ def collect_deprecations
+ old_behavior = ActiveSupport::Deprecation.behavior
+ deprecations = []
+ ActiveSupport::Deprecation.behavior = Proc.new do |message, callstack|
+ deprecations << message
end
+ result = yield
+ [result, deprecations]
+ ensure
+ ActiveSupport::Deprecation.behavior = old_behavior
+ end
end
end
end
diff --git a/activesupport/lib/active_support/testing/file_fixtures.rb b/activesupport/lib/active_support/testing/file_fixtures.rb
new file mode 100644
index 0000000000..4c6a0801b8
--- /dev/null
+++ b/activesupport/lib/active_support/testing/file_fixtures.rb
@@ -0,0 +1,34 @@
+module ActiveSupport
+ module Testing
+ # Adds simple access to sample files called file fixtures.
+ # File fixtures are normal files stored in
+ # <tt>ActiveSupport::TestCase.file_fixture_path</tt>.
+ #
+ # File fixtures are represented as +Pathname+ objects.
+ # This makes it easy to extract specific information:
+ #
+ # file_fixture("example.txt").read # get the file's content
+ # file_fixture("example.mp3").size # get the file size
+ module FileFixtures
+ extend ActiveSupport::Concern
+
+ included do
+ class_attribute :file_fixture_path, instance_writer: false
+ end
+
+ # Returns a +Pathname+ to the fixture file named +fixture_name+.
+ #
+ # Raises ArgumentError if +fixture_name+ can't be found.
+ def file_fixture(fixture_name)
+ path = Pathname.new(File.join(file_fixture_path, fixture_name))
+
+ if path.exist?
+ path
+ else
+ msg = "the directory '%s' does not contain a file named '%s'"
+ raise ArgumentError, msg % [file_fixture_path, fixture_name]
+ end
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/testing/isolation.rb b/activesupport/lib/active_support/testing/isolation.rb
index d5d31cecbe..247df7423b 100644
--- a/activesupport/lib/active_support/testing/isolation.rb
+++ b/activesupport/lib/active_support/testing/isolation.rb
@@ -1,21 +1,16 @@
-require 'rbconfig'
-require 'minitest/parallel_each'
-
module ActiveSupport
module Testing
module Isolation
require 'thread'
def self.included(klass) #:nodoc:
- klass.extend(Module.new {
- def test_methods
- ParallelEach.new super
- end
- })
+ klass.class_eval do
+ parallelize_me!
+ end
end
def self.forking_env?
- !ENV["NO_FORK"] && ((RbConfig::CONFIG['host_os'] !~ /mswin|mingw/) && (RUBY_PLATFORM !~ /java/))
+ !ENV["NO_FORK"] && Process.respond_to?(:fork)
end
@@class_setup_mutex = Mutex.new
@@ -40,6 +35,8 @@ module ActiveSupport
module Forking
def run_in_isolation(&blk)
read, write = IO.pipe
+ read.binmode
+ write.binmode
pid = fork do
read.close
@@ -71,14 +68,24 @@ module ActiveSupport
exit!
else
Tempfile.open("isolation") do |tmpfile|
- ENV["ISOLATION_TEST"] = self.class.name
- ENV["ISOLATION_OUTPUT"] = tmpfile.path
+ env = {
+ ISOLATION_TEST: self.class.name,
+ ISOLATION_OUTPUT: tmpfile.path
+ }
load_paths = $-I.map {|p| "-I\"#{File.expand_path(p)}\"" }.join(" ")
- `#{Gem.ruby} #{load_paths} #{$0} #{ORIG_ARGV.join(" ")}`
-
- ENV.delete("ISOLATION_TEST")
- ENV.delete("ISOLATION_OUTPUT")
+ orig_args = ORIG_ARGV.join(" ")
+ test_opts = "-n#{self.class.name}##{self.name}"
+ command = "#{Gem.ruby} #{load_paths} #{$0} #{orig_args} #{test_opts}"
+
+ # IO.popen lets us pass env in a cross-platform way
+ child = IO.popen([env, command])
+
+ begin
+ Process.wait(child.pid)
+ rescue Errno::ECHILD # The child process may exit before we wait
+ nil
+ end
return tmpfile.read.unpack("m")[0]
end
diff --git a/activesupport/lib/active_support/testing/stream.rb b/activesupport/lib/active_support/testing/stream.rb
new file mode 100644
index 0000000000..895192ad05
--- /dev/null
+++ b/activesupport/lib/active_support/testing/stream.rb
@@ -0,0 +1,42 @@
+module ActiveSupport
+ module Testing
+ module Stream #:nodoc:
+ private
+
+ def silence_stream(stream)
+ old_stream = stream.dup
+ stream.reopen(IO::NULL)
+ stream.sync = true
+ yield
+ ensure
+ stream.reopen(old_stream)
+ old_stream.close
+ end
+
+ def quietly
+ silence_stream(STDOUT) do
+ silence_stream(STDERR) do
+ yield
+ end
+ end
+ end
+
+ def capture(stream)
+ stream = stream.to_s
+ captured_stream = Tempfile.new(stream)
+ stream_io = eval("$#{stream}")
+ origin_stream = stream_io.dup
+ stream_io.reopen(captured_stream)
+
+ yield
+
+ stream_io.rewind
+ return captured_stream.read
+ ensure
+ captured_stream.close
+ captured_stream.unlink
+ stream_io.reopen(origin_stream)
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/testing/tagged_logging.rb b/activesupport/lib/active_support/testing/tagged_logging.rb
index f4cee64091..843ce4a867 100644
--- a/activesupport/lib/active_support/testing/tagged_logging.rb
+++ b/activesupport/lib/active_support/testing/tagged_logging.rb
@@ -6,7 +6,7 @@ module ActiveSupport
attr_writer :tagged_logger
def before_setup
- if tagged_logger
+ if tagged_logger && tagged_logger.info?
heading = "#{self.class}: #{name}"
divider = '-' * heading.size
tagged_logger.info divider
diff --git a/activesupport/lib/active_support/testing/time_helpers.rb b/activesupport/lib/active_support/testing/time_helpers.rb
new file mode 100644
index 0000000000..df5186ddec
--- /dev/null
+++ b/activesupport/lib/active_support/testing/time_helpers.rb
@@ -0,0 +1,132 @@
+module ActiveSupport
+ module Testing
+ class SimpleStubs # :nodoc:
+ Stub = Struct.new(:object, :method_name, :original_method)
+
+ def initialize
+ @stubs = {}
+ end
+
+ def stub_object(object, method_name, return_value)
+ key = [object.object_id, method_name]
+
+ if stub = @stubs[key]
+ unstub_object(stub)
+ end
+
+ new_name = "__simple_stub__#{method_name}"
+
+ @stubs[key] = Stub.new(object, method_name, new_name)
+
+ object.singleton_class.send :alias_method, new_name, method_name
+ object.define_singleton_method(method_name) { return_value }
+ end
+
+ def unstub_all!
+ @stubs.each_value do |stub|
+ unstub_object(stub)
+ end
+ @stubs = {}
+ end
+
+ private
+
+ def unstub_object(stub)
+ singleton_class = stub.object.singleton_class
+ singleton_class.send :undef_method, stub.method_name
+ singleton_class.send :alias_method, stub.method_name, stub.original_method
+ singleton_class.send :undef_method, stub.original_method
+ end
+ end
+
+ # Containing helpers that helps you test passage of time.
+ module TimeHelpers
+ # Changes current time to the time in the future or in the past by a given time difference by
+ # stubbing +Time.now+ and +Date.today+.
+ #
+ # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
+ # travel 1.day
+ # Time.current # => Sun, 10 Nov 2013 15:34:49 EST -05:00
+ # Date.current # => Sun, 10 Nov 2013
+ #
+ # 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 # => Sat, 09 Nov 2013 15:34:49 EST -05:00
+ # travel 1.day do
+ # User.create.created_at # => Sun, 10 Nov 2013 15:34:49 EST -05:00
+ # end
+ # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
+ def travel(duration, &block)
+ travel_to Time.now + duration, &block
+ end
+
+ # Changes current time to the given time by stubbing +Time.now+ and
+ # +Date.today+ to return the time or date passed into this method.
+ #
+ # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
+ # travel_to Time.new(2004, 11, 24, 01, 04, 44)
+ # Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00
+ # Date.current # => Wed, 24 Nov 2004
+ #
+ # Dates are taken as their timestamp at the beginning of the day in the
+ # application time zone. <tt>Time.current</tt> returns said timestamp,
+ # and <tt>Time.now</tt> its equivalent in the system time zone. Similarly,
+ # <tt>Date.current</tt> returns a date equal to the argument, and
+ # <tt>Date.today</tt> the date according to <tt>Time.now</tt>, which may
+ # be different. (Note that you rarely want to deal with <tt>Time.now</tt>,
+ # or <tt>Date.today</tt>, in order to honor the application time zone
+ # please always use <tt>Time.current</tt> and <tt>Date.current</tt>.)
+ #
+ # Note that the usec for the time passed will be set to 0 to prevent rounding
+ # errors with external services, like MySQL (which will round instead of floor,
+ # leading to off-by-one-second errors).
+ #
+ # 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 # => Sat, 09 Nov 2013 15:34:49 EST -05:00
+ # travel_to Time.new(2004, 11, 24, 01, 04, 44) do
+ # Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00
+ # end
+ # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
+ def travel_to(date_or_time)
+ if date_or_time.is_a?(Date) && !date_or_time.is_a?(DateTime)
+ now = date_or_time.midnight.to_time
+ else
+ now = date_or_time.to_time.change(usec: 0)
+ end
+
+ simple_stubs.stub_object(Time, :now, now)
+ simple_stubs.stub_object(Date, :today, now.to_date)
+ simple_stubs.stub_object(DateTime, :now, now.to_datetime)
+
+ if block_given?
+ begin
+ yield
+ ensure
+ travel_back
+ end
+ end
+ end
+
+ # Returns the current time back to its original state, by removing the stubs added by
+ # `travel` and `travel_to`.
+ #
+ # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
+ # travel_to Time.new(2004, 11, 24, 01, 04, 44)
+ # Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00
+ # travel_back
+ # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
+ def travel_back
+ simple_stubs.unstub_all!
+ end
+
+ private
+
+ def simple_stubs
+ @simple_stubs ||= SimpleStubs.new
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/time.rb b/activesupport/lib/active_support/time.rb
index 92a593965e..ea2d3391bd 100644
--- a/activesupport/lib/active_support/time.rb
+++ b/activesupport/lib/active_support/time.rb
@@ -1,5 +1,3 @@
-require 'active_support'
-
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 95b9b8e5ae..8ddf233b3e 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -45,7 +45,7 @@ module ActiveSupport
def initialize(utc_time, time_zone, local_time = nil, period = nil)
@utc, @time_zone, @time = utc_time, time_zone, local_time
- @period = @utc ? period : get_period_and_ensure_valid_local_time
+ @period = @utc ? period : get_period_and_ensure_valid_local_time(period)
end
# Returns a Time or DateTime instance that represents the time in +time_zone+.
@@ -75,8 +75,8 @@ module ActiveSupport
# Returns a <tt>Time.local()</tt> instance of the simultaneous time in your
# system's <tt>ENV['TZ']</tt> zone.
- def localtime
- utc.respond_to?(:getlocal) ? utc.getlocal : utc.to_time.getlocal
+ def localtime(utc_offset = nil)
+ utc.respond_to?(:getlocal) ? utc.getlocal(utc_offset) : utc.to_time.getlocal(utc_offset)
end
alias_method :getlocal, :localtime
@@ -121,19 +121,28 @@ module ActiveSupport
utc? && alternate_utc_string || TimeZone.seconds_to_utc_offset(utc_offset, colon)
end
- # Time uses +zone+ to display the time zone abbreviation, so we're
- # duck-typing it.
+ # Returns the time zone abbreviation.
+ #
+ # Time.zone = 'Eastern Time (US & Canada)' # => "Eastern Time (US & Canada)"
+ # Time.zone.now.zone # => "EST"
def zone
period.zone_identifier.to_s
end
+ # Returns a string of the object's date, time, zone and offset from UTC.
+ #
+ # Time.zone.now.httpdate # => "Thu, 04 Dec 2014 11:00:25 EST -05:00"
def inspect
"#{time.strftime('%a, %d %b %Y %H:%M:%S')} #{zone} #{formatted_offset}"
end
+ # Returns a string of the object's date and time in the ISO 8601 standard
+ # format.
+ #
+ # Time.zone.now.xmlschema # => "2014-12-04T11:02:37-05:00"
def xmlschema(fraction_digits = 0)
- fraction = if fraction_digits > 0
- (".%06i" % time.usec)[0, fraction_digits + 1]
+ fraction = if fraction_digits.to_i > 0
+ (".%06i" % time.usec)[0, fraction_digits.to_i + 1]
end
"#{time.strftime("%Y-%m-%dT%H:%M:%S")}#{fraction}#{formatted_offset(true, 'Z')}"
@@ -146,15 +155,15 @@ module ActiveSupport
# to +false+.
#
# # With ActiveSupport::JSON::Encoding.use_standard_json_time_format = true
- # Time.utc(2005,2,1,15,15,10).in_time_zone.to_json
- # # => "2005-02-01T15:15:10Z"
+ # Time.utc(2005,2,1,15,15,10).in_time_zone("Hawaii").to_json
+ # # => "2005-02-01T05:15:10.000-10:00"
#
# # With ActiveSupport::JSON::Encoding.use_standard_json_time_format = false
- # Time.utc(2005,2,1,15,15,10).in_time_zone.to_json
- # # => "2005/02/01 15:15:10 +0000"
+ # Time.utc(2005,2,1,15,15,10).in_time_zone("Hawaii").to_json
+ # # => "2005/02/01 05:15:10 -1000"
def as_json(options = nil)
if ActiveSupport::JSON::Encoding.use_standard_json_time_format
- xmlschema(3)
+ xmlschema(ActiveSupport::JSON::Encoding.time_precision)
else
%(#{time.strftime("%Y/%m/%d %H:%M:%S")} #{formatted_offset(false)})
end
@@ -185,28 +194,27 @@ module ActiveSupport
end
alias_method :rfc822, :rfc2822
- # <tt>:db</tt> format outputs time in UTC; all others output time in local.
- # Uses TimeWithZone's +strftime+, so <tt>%Z</tt> and <tt>%z</tt> work correctly.
+ # Returns a string of the object's date and time.
+ # Accepts an optional <tt>format</tt>:
+ # * <tt>:default</tt> - default value, mimics Ruby Time#to_s format.
+ # * <tt>:db</tt> - format outputs time in UTC :db time. See Time#to_formatted_s(:db).
+ # * Any key in <tt>Time::DATE_FORMATS</tt> can be used. See active_support/core_ext/time/conversions.rb.
def to_s(format = :default)
if format == :db
utc.to_s(format)
elsif formatter = ::Time::DATE_FORMATS[format]
formatter.respond_to?(:call) ? formatter.call(self).to_s : strftime(formatter)
else
- "#{time.strftime("%Y-%m-%d %H:%M:%S")} #{formatted_offset(false, 'UTC')}" # mimicking Ruby 1.9 Time#to_s format
+ "#{time.strftime("%Y-%m-%d %H:%M:%S")} #{formatted_offset(false, 'UTC')}" # mimicking Ruby Time#to_s format
end
end
alias_method :to_formatted_s, :to_s
- # Replaces <tt>%Z</tt> and <tt>%z</tt> directives with +zone+ and
- # +formatted_offset+, respectively, before passing to Time#strftime, so
- # that zone information is correct
+ # Replaces <tt>%Z</tt> directive with +zone before passing to Time#strftime,
+ # so that zone information is correct.
def strftime(format)
- format = format.gsub('%Z', zone)
- .gsub('%z', formatted_offset(false))
- .gsub('%:z', formatted_offset(true))
- .gsub('%::z', formatted_offset(true) + ":00")
- time.strftime(format)
+ format = format.gsub(/((?:\A|[^%])(?:%%)*)%Z/, "\\1#{zone}")
+ getlocal(utc_offset).strftime(format)
end
# Use the time in UTC for comparisons.
@@ -244,9 +252,23 @@ module ActiveSupport
utc.hash
end
+ # Adds an interval of time to the current object's time and return that
+ # value as a new TimeWithZone object.
+ #
+ # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)'
+ # now = Time.zone.now # => Sun, 02 Nov 2014 01:26:28 EDT -04:00
+ # now + 1000 # => Sun, 02 Nov 2014 01:43:08 EDT -04:00
+ #
+ # If we're adding a Duration of variable length (i.e., years, months, days),
+ # move forward from #time, otherwise move forward from #utc, for accuracy
+ # when moving across DST boundaries.
+ #
+ # For instance, a time + 24.hours will advance exactly 24 hours, while a
+ # time + 1.day will advance 23-25 hours, depending on the day.
+ #
+ # now + 24.hours # => Mon, 03 Nov 2014 00:26:28 EST -05:00
+ # now + 1.day # => Mon, 03 Nov 2014 01:26:28 EST -05:00
def +(other)
- # If we're adding a Duration of variable length (i.e., years, months, days), move forward from #time,
- # otherwise move forward from #utc, for accuracy when moving across DST boundaries
if duration_of_variable_length?(other)
method_missing(:+, other)
else
@@ -254,12 +276,27 @@ module ActiveSupport
result.in_time_zone(time_zone)
end
end
+ alias_method :since, :+
+ # Returns a new TimeWithZone object that represents the difference between
+ # the current object's time and the +other+ time.
+ #
+ # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)'
+ # now = Time.zone.now # => Sun, 02 Nov 2014 01:26:28 EST -05:00
+ # now - 1000 # => Sun, 02 Nov 2014 01:09:48 EST -05:00
+ #
+ # If subtracting a Duration of variable length (i.e., years, months, days),
+ # move backward from #time, otherwise move backward from #utc, for accuracy
+ # when moving across DST boundaries.
+ #
+ # For instance, a time - 24.hours will go subtract exactly 24 hours, while a
+ # time - 1.day will subtract 23-25 hours, depending on the day.
+ #
+ # now - 24.hours # => Sat, 01 Nov 2014 02:26:28 EDT -04:00
+ # now - 1.day # => Sat, 01 Nov 2014 01:26:28 EDT -04:00
def -(other)
- # If we're subtracting a Duration of variable length (i.e., years, months, days), move backwards from #time,
- # otherwise move backwards #utc, for accuracy when moving across DST boundaries
if other.acts_like?(:time)
- utc.to_f - other.to_f
+ to_time - other.to_time
elsif duration_of_variable_length?(other)
method_missing(:-, other)
else
@@ -268,16 +305,6 @@ module ActiveSupport
end
end
- def since(other)
- # If we're adding a Duration of variable length (i.e., years, months, days), move forward from #time,
- # otherwise move forward from #utc, for accuracy when moving across DST boundaries
- if duration_of_variable_length?(other)
- method_missing(:since, other)
- else
- utc.since(other).in_time_zone(time_zone)
- end
- end
-
def ago(other)
since(-other)
end
@@ -304,15 +331,27 @@ module ActiveSupport
[time.sec, time.min, time.hour, time.day, time.mon, time.year, time.wday, time.yday, dst?, zone]
end
+ # Returns the object's date and time as a floating point number of seconds
+ # since the Epoch (January 1, 1970 00:00 UTC).
+ #
+ # Time.zone.now.to_f # => 1417709320.285418
def to_f
utc.to_f
end
+ # Returns the object's date and time as an integer number of seconds
+ # since the Epoch (January 1, 1970 00:00 UTC).
+ #
+ # Time.zone.now.to_i # => 1417709320
def to_i
utc.to_i
end
alias_method :tv_sec, :to_i
+ # Returns the object's date and time as a rational number of seconds
+ # since the Epoch (January 1, 1970 00:00 UTC).
+ #
+ # Time.zone.now.to_r # => (708854548642709/500000)
def to_r
utc.to_r
end
@@ -350,6 +389,14 @@ module ActiveSupport
initialize(variables[0].utc, ::Time.find_zone(variables[1]), variables[2].utc)
end
+ # respond_to_missing? is not called in some cases, such as when type conversion is
+ # performed with Kernel#String
+ def respond_to?(sym, include_priv = false)
+ # ensure that we're not going to throw and rescue from NoMethodError in method_missing which is slow
+ return false if sym.to_sym == :to_str
+ super
+ end
+
# Ensure proxy class responds to all methods that underlying time instance
# responds to.
def respond_to_missing?(sym, include_priv)
@@ -367,12 +414,12 @@ module ActiveSupport
end
private
- def get_period_and_ensure_valid_local_time
+ def get_period_and_ensure_valid_local_time(period)
# we don't want a Time.local instance enforcing its own DST rules as well,
# so transfer time values to a utc constructor if necessary
@time = transfer_time_values_to_utc_constructor(@time) unless @time.utc?
begin
- @time_zone.period_for_local(@time)
+ period || @time_zone.period_for_local(@time)
rescue ::TZInfo::PeriodNotFound
# time is in the "spring forward" hour gap, so we're moving the time forward one hour and trying again
@time += 1.hour
@@ -390,7 +437,8 @@ module ActiveSupport
def wrap_with_time_zone(time)
if time.acts_like?(:time)
- self.class.new(nil, time_zone, time)
+ periods = time_zone.periods_for_local(time)
+ self.class.new(nil, time_zone, time, periods.include?(period) ? period : nil)
elsif time.is_a?(Range)
wrap_with_time_zone(time.begin)..wrap_with_time_zone(time.end)
else
diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb
index 3cf82a24b9..728b53849d 100644
--- a/activesupport/lib/active_support/values/time_zone.rb
+++ b/activesupport/lib/active_support/values/time_zone.rb
@@ -1,3 +1,5 @@
+require 'tzinfo'
+require 'thread_safe'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/object/try'
@@ -109,9 +111,11 @@ module ActiveSupport
"Jerusalem" => "Asia/Jerusalem",
"Harare" => "Africa/Harare",
"Pretoria" => "Africa/Johannesburg",
+ "Kaliningrad" => "Europe/Kaliningrad",
"Moscow" => "Europe/Moscow",
"St. Petersburg" => "Europe/Moscow",
- "Volgograd" => "Europe/Moscow",
+ "Volgograd" => "Europe/Volgograd",
+ "Samara" => "Europe/Samara",
"Kuwait" => "Asia/Kuwait",
"Riyadh" => "Asia/Riyadh",
"Nairobi" => "Africa/Nairobi",
@@ -168,6 +172,7 @@ module ActiveSupport
"Guam" => "Pacific/Guam",
"Port Moresby" => "Pacific/Port_Moresby",
"Magadan" => "Asia/Magadan",
+ "Srednekolymsk" => "Asia/Srednekolymsk",
"Solomon Is." => "Pacific/Guadalcanal",
"New Caledonia" => "Pacific/Noumea",
"Fiji" => "Pacific/Fiji",
@@ -182,18 +187,76 @@ module ActiveSupport
}
UTC_OFFSET_WITH_COLON = '%s%02d:%02d'
- UTC_OFFSET_WITHOUT_COLON = UTC_OFFSET_WITH_COLON.sub(':', '')
+ UTC_OFFSET_WITHOUT_COLON = UTC_OFFSET_WITH_COLON.tr(':', '')
- # Assumes self represents an offset from UTC in seconds (as returned from
- # Time#utc_offset) and turns this into an +HH:MM formatted string.
- #
- # TimeZone.seconds_to_utc_offset(-21_600) # => "-06:00"
- def self.seconds_to_utc_offset(seconds, colon = true)
- format = colon ? UTC_OFFSET_WITH_COLON : UTC_OFFSET_WITHOUT_COLON
- sign = (seconds < 0 ? '-' : '+')
- hours = seconds.abs / 3600
- minutes = (seconds.abs % 3600) / 60
- format % [sign, hours, minutes]
+ @lazy_zones_map = ThreadSafe::Cache.new
+
+ class << self
+ # Assumes self represents an offset from UTC in seconds (as returned from
+ # Time#utc_offset) and turns this into an +HH:MM formatted string.
+ #
+ # TimeZone.seconds_to_utc_offset(-21_600) # => "-06:00"
+ def seconds_to_utc_offset(seconds, colon = true)
+ format = colon ? UTC_OFFSET_WITH_COLON : UTC_OFFSET_WITHOUT_COLON
+ sign = (seconds < 0 ? '-' : '+')
+ hours = seconds.abs / 3600
+ minutes = (seconds.abs % 3600) / 60
+ format % [sign, hours, minutes]
+ end
+
+ def find_tzinfo(name)
+ TZInfo::Timezone.new(MAPPING[name] || name)
+ end
+
+ alias_method :create, :new
+
+ # Returns a TimeZone instance with the given name, or +nil+ if no
+ # such TimeZone instance exists. (This exists to support the use of
+ # this class with the +composed_of+ macro.)
+ def new(name)
+ self[name]
+ end
+
+ # Returns an array of all TimeZone objects. There are multiple
+ # TimeZone objects per time zone, in many cases, to make it easier
+ # for users to find their own time zone.
+ def all
+ @zones ||= zones_map.values.sort
+ end
+
+ def zones_map
+ @zones_map ||= begin
+ MAPPING.each_key {|place| self[place]} # load all the zones
+ @lazy_zones_map
+ end
+ end
+
+ # Locate a specific time zone object. If the argument is a string, it
+ # is interpreted to mean the name of the timezone to locate. If it is a
+ # numeric value it is either the hour offset, or the second offset, of the
+ # timezone to find. (The first one with that offset will be returned.)
+ # Returns +nil+ if no such time zone is known to the system.
+ def [](arg)
+ case arg
+ when String
+ begin
+ @lazy_zones_map[arg] ||= create(arg)
+ rescue TZInfo::InvalidTimezoneIdentifier
+ nil
+ end
+ when Numeric, ActiveSupport::Duration
+ arg *= 3600 if arg.abs <= 13
+ all.find { |z| z.utc_offset == arg.to_i }
+ else
+ raise ArgumentError, "invalid argument to TimeZone[]: #{arg.inspect}"
+ end
+ end
+
+ # A convenience method for returning a collection of TimeZone objects
+ # for time zones in the USA.
+ def us_zones
+ @us_zones ||= all.find_all { |z| z.name =~ /US|Arizona|Indiana|Hawaii|Alaska/ }
+ end
end
include Comparable
@@ -205,8 +268,6 @@ module ActiveSupport
# (GMT). Seconds were chosen as the offset unit because that is the unit
# that Ruby uses to represent time zone offsets (see Time#utc_offset).
def initialize(name, utc_offset = nil, tzinfo = nil)
- self.class.send(:require_tzinfo)
-
@name = name
@utc_offset = utc_offset
@tzinfo = tzinfo || TimeZone.find_tzinfo(name)
@@ -218,8 +279,8 @@ module ActiveSupport
if @utc_offset
@utc_offset
else
- @current_period ||= tzinfo.try(:current_period)
- @current_period.try(:utc_offset)
+ @current_period ||= tzinfo.current_period if tzinfo
+ @current_period.utc_offset if @current_period
end
end
@@ -232,6 +293,7 @@ module ActiveSupport
# Compare this time zone to the parameter. The two are compared first on
# their offsets, and then by name.
def <=>(zone)
+ return unless zone.respond_to? :utc_offset
result = (utc_offset <=> zone.utc_offset)
result = (name <=> zone.name) if result == 0
result
@@ -279,14 +341,19 @@ module ActiveSupport
#
# Time.zone.now # => Fri, 31 Dec 1999 14:00:00 HST -10:00
# Time.zone.parse('22:30:00') # => Fri, 31 Dec 1999 22:30:00 HST -10:00
- def parse(str, now=now)
+ #
+ # However, if the date component is not provided, but any other upper
+ # 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
+ def parse(str, now=now())
parts = Date._parse(str, false)
return if parts.empty?
time = Time.new(
parts.fetch(:year, now.year),
parts.fetch(:mon, now.month),
- parts.fetch(:mday, now.day),
+ parts.fetch(:mday, parts[:year] || parts[:mon] ? 1 : now.day),
parts.fetch(:hour, 0),
parts.fetch(:min, 0),
parts.fetch(:sec, 0) + parts.fetch(:sec_fraction, 0),
@@ -314,6 +381,16 @@ module ActiveSupport
tzinfo.now.to_date
end
+ # Returns the next date in this time zone.
+ def tomorrow
+ today + 1
+ end
+
+ # Returns the previous date in this time zone.
+ def yesterday
+ today - 1
+ end
+
# Adjust the given time to the simultaneous time in the time zone
# represented by +self+. Returns a Time.utc() instance -- if you want an
# ActiveSupport::TimeWithZone instance, use Time#in_time_zone() instead.
@@ -339,91 +416,13 @@ module ActiveSupport
tzinfo.period_for_local(time, dst)
end
- def self.find_tzinfo(name)
- TZInfo::TimezoneProxy.new(MAPPING[name] || name)
- end
-
- class << self
- alias_method :create, :new
-
- # Return a TimeZone instance with the given name, or +nil+ if no
- # such TimeZone instance exists. (This exists to support the use of
- # this class with the +composed_of+ macro.)
- def new(name)
- self[name]
- end
-
- # Return an array of all TimeZone objects. There are multiple
- # TimeZone objects per time zone, in many cases, to make it easier
- # for users to find their own time zone.
- def all
- @zones ||= zones_map.values.sort
- end
-
- def zones_map
- @zones_map ||= begin
- new_zones_names = MAPPING.keys - lazy_zones_map.keys
- new_zones = Hash[new_zones_names.map { |place| [place, create(place)] }]
-
- lazy_zones_map.merge(new_zones)
- end
- end
-
- # Locate a specific time zone object. If the argument is a string, it
- # is interpreted to mean the name of the timezone to locate. If it is a
- # numeric value it is either the hour offset, or the second offset, of the
- # timezone to find. (The first one with that offset will be returned.)
- # Returns +nil+ if no such time zone is known to the system.
- def [](arg)
- case arg
- when String
- begin
- lazy_zones_map[arg] ||= lookup(arg).tap { |tz| tz.utc_offset }
- rescue TZInfo::InvalidTimezoneIdentifier
- nil
- end
- when Numeric, ActiveSupport::Duration
- arg *= 3600 if arg.abs <= 13
- all.find { |z| z.utc_offset == arg.to_i }
- else
- raise ArgumentError, "invalid argument to TimeZone[]: #{arg.inspect}"
- end
- end
-
- # A convenience method for returning a collection of TimeZone objects
- # for time zones in the USA.
- def us_zones
- @us_zones ||= all.find_all { |z| z.name =~ /US|Arizona|Indiana|Hawaii|Alaska/ }
- end
-
- protected
-
- def require_tzinfo
- require 'tzinfo' unless defined?(::TZInfo)
- rescue LoadError
- $stderr.puts "You don't have tzinfo installed in your application. Please add it to your Gemfile and run bundle install"
- raise
- end
-
- private
-
- def lookup(name)
- (tzinfo = find_tzinfo(name)) && create(tzinfo.name.freeze)
- end
-
- def lazy_zones_map
- require_tzinfo
-
- @lazy_zones_map ||= Hash.new do |hash, place|
- hash[place] = create(place) if MAPPING.has_key?(place)
- end
- end
+ def periods_for_local(time) #:nodoc:
+ tzinfo.periods_for_local(time)
end
private
-
- def time_now
- Time.now
- end
+ def time_now
+ Time.now
+ end
end
end
diff --git a/activesupport/lib/active_support/values/unicode_tables.dat b/activesupport/lib/active_support/values/unicode_tables.dat
index 2571faa019..760be4c07a 100644
--- a/activesupport/lib/active_support/values/unicode_tables.dat
+++ b/activesupport/lib/active_support/values/unicode_tables.dat
Binary files differ
diff --git a/activesupport/lib/active_support/version.rb b/activesupport/lib/active_support/version.rb
index 8762330a6e..fe03984546 100644
--- a/activesupport/lib/active_support/version.rb
+++ b/activesupport/lib/active_support/version.rb
@@ -1,11 +1,8 @@
+require_relative 'gem_version'
+
module ActiveSupport
- # Returns the version of the currently loaded ActiveSupport as a Gem::Version
+ # Returns the version of the currently loaded ActiveSupport as a <tt>Gem::Version</tt>
def self.version
- Gem::Version.new "4.1.0.beta"
- end
-
- module VERSION #:nodoc:
- MAJOR, MINOR, TINY, PRE = ActiveSupport.version.segments
- STRING = ActiveSupport.version.to_s
+ gem_version
end
end
diff --git a/activesupport/lib/active_support/xml_mini.rb b/activesupport/lib/active_support/xml_mini.rb
index d082a0a499..009ee4db90 100644
--- a/activesupport/lib/active_support/xml_mini.rb
+++ b/activesupport/lib/active_support/xml_mini.rb
@@ -1,7 +1,9 @@
require 'time'
require 'base64'
+require 'bigdecimal'
require 'active_support/core_ext/module/delegation'
require 'active_support/core_ext/string/inflections'
+require 'active_support/core_ext/date_time/calculations'
module ActiveSupport
# = XmlMini
@@ -56,13 +58,13 @@ module ActiveSupport
# TODO use regexp instead of Date.parse
unless defined?(PARSING)
PARSING = {
- "symbol" => Proc.new { |symbol| symbol.to_sym },
+ "symbol" => Proc.new { |symbol| symbol.to_s.to_sym },
"date" => Proc.new { |date| ::Date.parse(date) },
"datetime" => Proc.new { |time| Time.xmlschema(time).utc rescue ::DateTime.parse(time).utc },
"integer" => Proc.new { |integer| integer.to_i },
"float" => Proc.new { |float| float.to_f },
"decimal" => Proc.new { |number| BigDecimal(number) },
- "boolean" => Proc.new { |boolean| %w(1 true).include?(boolean.strip) },
+ "boolean" => Proc.new { |boolean| %w(1 true).include?(boolean.to_s.strip) },
"string" => Proc.new { |string| string.to_s },
"yaml" => Proc.new { |yaml| YAML::load(yaml) rescue yaml },
"base64Binary" => Proc.new { |bin| ::Base64.decode64(bin) },
diff --git a/activesupport/lib/active_support/xml_mini/jdom.rb b/activesupport/lib/active_support/xml_mini/jdom.rb
index 27c64c4dca..f303daa1a7 100644
--- a/activesupport/lib/active_support/xml_mini/jdom.rb
+++ b/activesupport/lib/active_support/xml_mini/jdom.rb
@@ -141,7 +141,7 @@ module ActiveSupport
(0...attributes.length).each do |i|
attribute_hash[CONTENT_KEY] ||= ''
attribute_hash[attributes.item(i).name] = attributes.item(i).value
- end
+ end
attribute_hash
end
diff --git a/activesupport/lib/active_support/xml_mini/libxml.rb b/activesupport/lib/active_support/xml_mini/libxml.rb
index 47a2824186..bb0ea9c582 100644
--- a/activesupport/lib/active_support/xml_mini/libxml.rb
+++ b/activesupport/lib/active_support/xml_mini/libxml.rb
@@ -75,5 +75,5 @@ module LibXML #:nodoc:
end
end
-LibXML::XML::Document.send(:include, LibXML::Conversions::Document)
-LibXML::XML::Node.send(:include, LibXML::Conversions::Node)
+LibXML::XML::Document.include(LibXML::Conversions::Document)
+LibXML::XML::Node.include(LibXML::Conversions::Node)
diff --git a/activesupport/lib/active_support/xml_mini/nokogiri.rb b/activesupport/lib/active_support/xml_mini/nokogiri.rb
index 7398d4fa82..619cc7522d 100644
--- a/activesupport/lib/active_support/xml_mini/nokogiri.rb
+++ b/activesupport/lib/active_support/xml_mini/nokogiri.rb
@@ -77,7 +77,7 @@ module ActiveSupport
end
end
- Nokogiri::XML::Document.send(:include, Conversions::Document)
- Nokogiri::XML::Node.send(:include, Conversions::Node)
+ Nokogiri::XML::Document.include(Conversions::Document)
+ Nokogiri::XML::Node.include(Conversions::Node)
end
end
diff --git a/activesupport/test/abstract_unit.rb b/activesupport/test/abstract_unit.rb
index 939bd7bfa8..f65ec962f9 100644
--- a/activesupport/test/abstract_unit.rb
+++ b/activesupport/test/abstract_unit.rb
@@ -15,7 +15,6 @@ silence_warnings do
end
require 'active_support/testing/autorun'
-require 'empty_bool'
ENV['NO_RELOAD'] = '1'
require 'active_support'
@@ -24,3 +23,23 @@ Thread.abort_on_exception = true
# Show backtraces for deprecated behavior for quicker cleanup.
ActiveSupport::Deprecation.debug = true
+
+# Disable available locale checks to avoid warnings running the test suite.
+I18n.enforce_available_locales = false
+
+# Skips the current run on Rubinius using Minitest::Assertions#skip
+def rubinius_skip(message = '')
+ skip message if RUBY_ENGINE == 'rbx'
+end
+
+# Skips the current run on JRuby using Minitest::Assertions#skip
+def jruby_skip(message = '')
+ skip message if defined?(JRUBY_VERSION)
+end
+
+require 'mocha/setup' # FIXME: stop using mocha
+
+# FIXME: we have tests that depend on run order, we should fix that and
+# remove this method call.
+require 'active_support/test_case'
+ActiveSupport::TestCase.test_order = :sorted
diff --git a/activesupport/test/autoload_test.rb b/activesupport/test/autoload_test.rb
index 7d02d835a8..c18b007612 100644
--- a/activesupport/test/autoload_test.rb
+++ b/activesupport/test/autoload_test.rb
@@ -11,6 +11,11 @@ class TestAutoloadModule < ActiveSupport::TestCase
end
end
+ def setup
+ @some_class_path = File.expand_path("test/fixtures/autoload/some_class.rb")
+ @another_class_path = File.expand_path("test/fixtures/autoload/another_class.rb")
+ end
+
test "the autoload module works like normal autoload" do
module ::Fixtures::Autoload
autoload :SomeClass, "fixtures/autoload/some_class"
@@ -21,10 +26,12 @@ class TestAutoloadModule < ActiveSupport::TestCase
test "when specifying an :eager constant it still works like normal autoload by default" do
module ::Fixtures::Autoload
- autoload :SomeClass, "fixtures/autoload/some_class"
+ eager_autoload do
+ autoload :SomeClass, "fixtures/autoload/some_class"
+ end
end
- assert !$LOADED_FEATURES.include?("fixtures/autoload/some_class.rb")
+ assert !$LOADED_FEATURES.include?(@some_class_path)
assert_nothing_raised { ::Fixtures::Autoload::SomeClass }
end
@@ -33,16 +40,20 @@ class TestAutoloadModule < ActiveSupport::TestCase
autoload :SomeClass
end
- assert !$LOADED_FEATURES.include?("fixtures/autoload/some_class.rb")
+ assert !$LOADED_FEATURES.include?(@some_class_path)
assert_nothing_raised { ::Fixtures::Autoload::SomeClass }
end
test "the location of :eager autoloaded constants defaults to :name.underscore" do
module ::Fixtures::Autoload
- autoload :SomeClass
+ eager_autoload do
+ autoload :SomeClass
+ end
end
+ assert !$LOADED_FEATURES.include?(@some_class_path)
::Fixtures::Autoload.eager_load!
+ assert $LOADED_FEATURES.include?(@some_class_path)
assert_nothing_raised { ::Fixtures::Autoload::SomeClass }
end
@@ -53,7 +64,7 @@ class TestAutoloadModule < ActiveSupport::TestCase
end
end
- assert !$LOADED_FEATURES.include?("fixtures/autoload/another_class.rb")
+ assert !$LOADED_FEATURES.include?(@another_class_path)
assert_nothing_raised { ::Fixtures::AnotherClass }
end
@@ -64,7 +75,7 @@ class TestAutoloadModule < ActiveSupport::TestCase
end
end
- assert !$LOADED_FEATURES.include?("fixtures/autoload/another_class.rb")
+ assert !$LOADED_FEATURES.include?(@another_class_path)
assert_nothing_raised { ::Fixtures::AnotherClass }
end
end \ No newline at end of file
diff --git a/activesupport/test/autoloading_fixtures/html/some_class.rb b/activesupport/test/autoloading_fixtures/html/some_class.rb
new file mode 100644
index 0000000000..b43d15d891
--- /dev/null
+++ b/activesupport/test/autoloading_fixtures/html/some_class.rb
@@ -0,0 +1,4 @@
+module HTML
+ class SomeClass
+ end
+end
diff --git a/activesupport/test/autoloading_fixtures/typo.rb b/activesupport/test/autoloading_fixtures/typo.rb
new file mode 100644
index 0000000000..8e047f5fd4
--- /dev/null
+++ b/activesupport/test/autoloading_fixtures/typo.rb
@@ -0,0 +1,2 @@
+TypO = 1
+
diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb
index df570d485a..98f3ea7a14 100644
--- a/activesupport/test/caching_test.rb
+++ b/activesupport/test/caching_test.rb
@@ -3,6 +3,42 @@ require 'abstract_unit'
require 'active_support/cache'
require 'dependencies_test_helpers'
+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_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
@@ -24,36 +60,25 @@ class CacheKeyTest < ActiveSupport::TestCase
end
def test_expand_cache_key_with_rails_cache_id
- begin
- ENV['RAILS_CACHE_ID'] = 'c99'
+ 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)
- ensure
- ENV['RAILS_CACHE_ID'] = nil
end
end
def test_expand_cache_key_with_rails_app_version
- begin
- ENV['RAILS_APP_VERSION'] = 'rails3'
+ with_env('RAILS_APP_VERSION' => 'rails3') do
assert_equal 'rails3/foo', ActiveSupport::Cache.expand_cache_key(:foo)
- ensure
- ENV['RAILS_APP_VERSION'] = nil
end
end
def test_expand_cache_key_rails_cache_id_should_win_over_rails_app_version
- begin
- ENV['RAILS_CACHE_ID'] = 'c99'
- ENV['RAILS_APP_VERSION'] = 'rails3'
+ with_env('RAILS_CACHE_ID' => 'c99', 'RAILS_APP_VERSION' => 'rails3') do
assert_equal 'c99/foo', ActiveSupport::Cache.expand_cache_key(:foo)
- ensure
- ENV['RAILS_CACHE_ID'] = nil
- ENV['RAILS_APP_VERSION'] = nil
end
end
@@ -88,6 +113,16 @@ class CacheKeyTest < ActiveSupport::TestCase
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
@@ -110,12 +145,12 @@ class CacheStoreSettingTest < ActiveSupport::TestCase
assert_kind_of(ActiveSupport::Cache::MemCacheStore, store)
end
- def test_mem_cache_fragment_cache_store_with_given_mem_cache_like_object
+ def test_mem_cache_fragment_cache_store_with_not_dalli_client
Dalli::Client.expects(:new).never
memcache = Object.new
- def memcache.get() true end
- store = ActiveSupport::Cache.lookup_store :mem_cache_store, memcache
- assert_kind_of(ActiveSupport::Cache::MemCacheStore, store)
+ assert_raises(ArgumentError) do
+ ActiveSupport::Cache.lookup_store :mem_cache_store, memcache
+ end
end
def test_mem_cache_fragment_cache_store_with_multiple_servers
@@ -261,20 +296,21 @@ module CacheStoreBehavior
@cache.write('foo', 'bar')
@cache.write('fud', 'biz')
- values = @cache.fetch_multi('foo', 'fu', 'fud') {|value| value * 2 }
+ values = @cache.fetch_multi('foo', 'fu', 'fud') { |value| value * 2 }
- assert_equal(["bar", "fufu", "biz"], values)
- assert_equal("fufu", @cache.read('fu'))
+ assert_equal({ 'foo' => 'bar', 'fu' => 'fufu', 'fud' => 'biz' }, values)
+ assert_equal('fufu', @cache.read('fu'))
end
def test_multi_with_objects
- foo = stub(:title => "FOO!", :cache_key => "foo")
- bar = stub(:cache_key => "bar")
+ foo = stub(:title => 'FOO!', :cache_key => 'foo')
+ bar = stub(:cache_key => 'bar')
+
+ @cache.write('bar', 'BAM!')
- @cache.write('bar', "BAM!")
+ values = @cache.fetch_multi(foo, bar) { |object| object.title }
- values = @cache.fetch_multi(foo, bar) {|object| object.title }
- assert_equal(["FOO!", "BAM!"], values)
+ assert_equal({ foo => 'FOO!', bar => 'BAM!' }, values)
end
def test_read_and_write_compressed_small_data
@@ -577,6 +613,7 @@ module LocalCacheBehavior
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({})
@@ -654,6 +691,11 @@ class FileStoreTest < ActiveSupport::TestCase
assert File.exist?(filepath)
end
+ def test_long_keys
+ @cache.write("a"*10000, 1)
+ assert_equal 1, @cache.read("a"*10000)
+ end
+
def test_key_transformation
key = @cache.send(:key_file_path, "views/index?id=1")
assert_equal "views/index?id=1", @cache.send(:file_path_key, key)
@@ -709,6 +751,25 @@ class FileStoreTest < ActiveSupport::TestCase
@cache.send(:read_entry, "winston", {})
assert @buffer.string.present?
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.stubs(:now).returns(time + 15)
+ @cache.cleanup
+ assert_not @cache.exist?('foo')
+ assert @cache.exist?('baz')
+ assert @cache.exist?('quux')
+ 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
@@ -878,8 +939,8 @@ class MemCacheStoreTest < ActiveSupport::TestCase
def test_read_should_return_a_different_object_id_each_time_it_is_called
@cache.write('foo', 'bar')
- assert_not_equal @cache.read('foo').object_id, @cache.read('foo').object_id
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
@@ -986,30 +1047,4 @@ class CacheEntryTest < ActiveSupport::TestCase
assert_equal value, entry.value
assert_equal value.bytesize, entry.size
end
-
- def test_restoring_version_4beta1_entries
- version_4beta1_entry = ActiveSupport::Cache::Entry.allocate
- version_4beta1_entry.instance_variable_set(:@v, "hello")
- version_4beta1_entry.instance_variable_set(:@x, Time.now.to_i + 60)
- entry = Marshal.load(Marshal.dump(version_4beta1_entry))
- assert_equal "hello", entry.value
- assert_equal false, entry.expired?
- end
-
- def test_restoring_compressed_version_4beta1_entries
- version_4beta1_entry = ActiveSupport::Cache::Entry.allocate
- version_4beta1_entry.instance_variable_set(:@v, Zlib::Deflate.deflate(Marshal.dump("hello")))
- version_4beta1_entry.instance_variable_set(:@c, true)
- entry = Marshal.load(Marshal.dump(version_4beta1_entry))
- assert_equal "hello", entry.value
- end
-
- def test_restoring_expired_version_4beta1_entries
- version_4beta1_entry = ActiveSupport::Cache::Entry.allocate
- version_4beta1_entry.instance_variable_set(:@v, "hello")
- version_4beta1_entry.instance_variable_set(:@x, Time.now.to_i - 1)
- entry = Marshal.load(Marshal.dump(version_4beta1_entry))
- assert_equal "hello", entry.value
- assert_equal true, entry.expired?
- end
end
diff --git a/activesupport/test/callbacks_test.rb b/activesupport/test/callbacks_test.rb
index f8e2ce22fa..f6abef8cee 100644
--- a/activesupport/test/callbacks_test.rb
+++ b/activesupport/test/callbacks_test.rb
@@ -1,27 +1,6 @@
require 'abstract_unit'
module CallbacksTest
- class Phone
- include ActiveSupport::Callbacks
- define_callbacks :save
-
- set_callback :save, :before, :before_save1
- set_callback :save, :after, :after_save1
-
- def before_save1; self.history << :before; end
- def after_save1; self.history << :after; end
-
- def save
- run_callbacks :save do
- raise 'boom'
- end
- end
-
- def history
- @history ||= []
- end
- end
-
class Record
include ActiveSupport::Callbacks
@@ -70,7 +49,7 @@ module CallbacksTest
def self.before(model)
model.history << [:before_save, :class]
end
-
+
def self.after(model)
model.history << [:after_save, :class]
end
@@ -522,21 +501,18 @@ module CallbacksTest
end
end
- class CallbackTerminator
+ class AbstractCallbackTerminator
include ActiveSupport::Callbacks
- define_callbacks :save, :terminator => ->(_,result) { result == :halt }
-
- set_callback :save, :before, :first
- set_callback :save, :before, :second
- set_callback :save, :around, :around_it
- set_callback :save, :before, :third
- set_callback :save, :after, :first
- set_callback :save, :around, :around_it
- set_callback :save, :after, :second
- set_callback :save, :around, :around_it
- set_callback :save, :after, :third
-
+ def self.set_save_callbacks
+ set_callback :save, :before, :first
+ set_callback :save, :before, :second
+ set_callback :save, :around, :around_it
+ set_callback :save, :before, :third
+ set_callback :save, :after, :first
+ set_callback :save, :around, :around_it
+ set_callback :save, :after, :third
+ end
attr_reader :history, :saved, :halted
def initialize
@@ -573,6 +549,39 @@ module CallbacksTest
end
end
+ class CallbackTerminator < AbstractCallbackTerminator
+ define_callbacks :save, terminator: ->(_, result_lambda) { result_lambda.call == :halt }
+ set_save_callbacks
+ end
+
+ class CallbackTerminatorSkippingAfterCallbacks < AbstractCallbackTerminator
+ define_callbacks :save, terminator: ->(_, result_lambda) { result_lambda.call == :halt },
+ skip_after_callbacks_if_terminated: true
+ set_save_callbacks
+ end
+
+ class CallbackDefaultTerminator < AbstractCallbackTerminator
+ define_callbacks :save
+
+ def second
+ @history << "second"
+ throw(:abort)
+ end
+
+ set_save_callbacks
+ end
+
+ class CallbackFalseTerminator < AbstractCallbackTerminator
+ define_callbacks :save
+
+ def second
+ @history << "second"
+ false
+ end
+
+ set_save_callbacks
+ end
+
class CallbackObject
def before(caller)
caller.record << "before"
@@ -709,10 +718,10 @@ module CallbacksTest
end
class CallbackTerminatorTest < ActiveSupport::TestCase
- def test_termination
+ def test_termination_skips_following_before_and_around_callbacks
terminator = CallbackTerminator.new
terminator.save
- assert_equal ["first", "second", "third", "second", "first"], terminator.history
+ assert_equal ["first", "second", "third", "first"], terminator.history
end
def test_termination_invokes_hook
@@ -728,6 +737,73 @@ module CallbacksTest
end
end
+ class CallbackTerminatorSkippingAfterCallbacksTest < ActiveSupport::TestCase
+ def test_termination_skips_after_callbacks
+ terminator = CallbackTerminatorSkippingAfterCallbacks.new
+ terminator.save
+ assert_equal ["first", "second"], terminator.history
+ end
+ end
+
+ class CallbackDefaultTerminatorTest < ActiveSupport::TestCase
+ def test_default_termination
+ terminator = CallbackDefaultTerminator.new
+ terminator.save
+ assert_equal ["first", "second", "third", "first"], terminator.history
+ end
+
+ def test_default_termination_invokes_hook
+ terminator = CallbackDefaultTerminator.new
+ terminator.save
+ assert_equal :second, terminator.halted
+ end
+
+ def test_block_never_called_if_abort_is_thrown
+ obj = CallbackDefaultTerminator.new
+ obj.save
+ assert !obj.saved
+ end
+ end
+
+ class CallbackFalseTerminatorWithoutConfigTest < ActiveSupport::TestCase
+ def test_returning_false_halts_callback_if_config_variable_is_not_set
+ obj = CallbackFalseTerminator.new
+ assert_deprecated do
+ obj.save
+ assert_equal :second, obj.halted
+ assert !obj.saved
+ end
+ end
+ end
+
+ class CallbackFalseTerminatorWithConfigTrueTest < ActiveSupport::TestCase
+ def setup
+ ActiveSupport::Callbacks::CallbackChain.halt_and_display_warning_on_return_false = true
+ end
+
+ def test_returning_false_halts_callback_if_config_variable_is_true
+ obj = CallbackFalseTerminator.new
+ assert_deprecated do
+ obj.save
+ assert_equal :second, obj.halted
+ assert !obj.saved
+ end
+ end
+ end
+
+ class CallbackFalseTerminatorWithConfigFalseTest < ActiveSupport::TestCase
+ def setup
+ ActiveSupport::Callbacks::CallbackChain.halt_and_display_warning_on_return_false = false
+ end
+
+ def test_returning_false_does_not_halt_callback_if_config_variable_is_false
+ obj = CallbackFalseTerminator.new
+ obj.save
+ assert_equal nil, obj.halted
+ assert obj.saved
+ end
+ end
+
class HyphenatedKeyTest < ActiveSupport::TestCase
def test_save
obj = HyphenatedCallbacks.new
diff --git a/activesupport/test/clean_backtrace_test.rb b/activesupport/test/clean_backtrace_test.rb
index dd67a45cf6..05580352a9 100644
--- a/activesupport/test/clean_backtrace_test.rb
+++ b/activesupport/test/clean_backtrace_test.rb
@@ -34,6 +34,11 @@ class BacktraceCleanerSilencerTest < ActiveSupport::TestCase
[ "/other/class.rb" ],
@bc.clean([ "/mongrel/class.rb", "/other/class.rb", "/mongrel/stuff.rb" ])
end
+
+ test "backtrace cleaner should allow removing silencer" do
+ @bc.remove_silencers!
+ assert_equal ["/mongrel/stuff.rb"], @bc.clean(["/mongrel/stuff.rb"])
+ end
end
class BacktraceCleanerMultipleSilencersTest < ActiveSupport::TestCase
diff --git a/activesupport/test/concern_test.rb b/activesupport/test/concern_test.rb
index a74ee880b2..253c1adc23 100644
--- a/activesupport/test/concern_test.rb
+++ b/activesupport/test/concern_test.rb
@@ -5,7 +5,7 @@ class ConcernTest < ActiveSupport::TestCase
module Baz
extend ActiveSupport::Concern
- module ClassMethods
+ class_methods do
def baz
"baz"
end
@@ -33,6 +33,12 @@ class ConcernTest < ActiveSupport::TestCase
include Baz
+ module ClassMethods
+ def baz
+ "bar's baz + " + super
+ end
+ end
+
def bar
"bar"
end
@@ -53,32 +59,32 @@ class ConcernTest < ActiveSupport::TestCase
end
def test_module_is_included_normally
- @klass.send(:include, Baz)
+ @klass.include(Baz)
assert_equal "baz", @klass.new.baz
assert @klass.included_modules.include?(ConcernTest::Baz)
end
def test_class_methods_are_extended
- @klass.send(:include, Baz)
+ @klass.include(Baz)
assert_equal "baz", @klass.baz
assert_equal ConcernTest::Baz::ClassMethods, (class << @klass; self.included_modules; end)[0]
end
def test_included_block_is_ran
- @klass.send(:include, Baz)
+ @klass.include(Baz)
assert_equal true, @klass.included_ran
end
def test_modules_dependencies_are_met
- @klass.send(:include, Bar)
+ @klass.include(Bar)
assert_equal "bar", @klass.new.bar
assert_equal "bar+baz", @klass.new.baz
- assert_equal "baz", @klass.baz
+ assert_equal "bar's baz + baz", @klass.baz
assert @klass.included_modules.include?(ConcernTest::Bar)
end
def test_dependencies_with_multiple_modules
- @klass.send(:include, Foo)
+ @klass.include(Foo)
assert_equal [ConcernTest::Foo, ConcernTest::Bar, ConcernTest::Baz], @klass.included_modules[0..2]
end
diff --git a/activesupport/test/configurable_test.rb b/activesupport/test/configurable_test.rb
index d00273a028..5d22ded2de 100644
--- a/activesupport/test/configurable_test.rb
+++ b/activesupport/test/configurable_test.rb
@@ -95,6 +95,28 @@ class ConfigurableActiveSupport < ActiveSupport::TestCase
config_accessor "invalid attribute name"
end
end
+
+ assert_raises NameError do
+ Class.new do
+ include ActiveSupport::Configurable
+ config_accessor "invalid\nattribute"
+ end
+ end
+
+ assert_raises NameError do
+ Class.new do
+ include ActiveSupport::Configurable
+ config_accessor "invalid\n"
+ end
+ end
+ end
+
+ test 'the config_accessor method should not be publicly callable' do
+ assert_raises NoMethodError do
+ Class.new {
+ include ActiveSupport::Configurable
+ }.config_accessor :foo
+ end
end
def assert_method_defined(object, method)
diff --git a/activesupport/test/constantize_test_cases.rb b/activesupport/test/constantize_test_cases.rb
index bbeb710a0c..366e4e5ef0 100644
--- a/activesupport/test/constantize_test_cases.rb
+++ b/activesupport/test/constantize_test_cases.rb
@@ -1,3 +1,5 @@
+require 'dependencies_test_helpers'
+
module Ace
module Base
class Case
@@ -23,17 +25,30 @@ class Object
end
module ConstantizeTestCases
+ include DependenciesTestHelpers
+
def run_constantize_tests_on
assert_equal Ace::Base::Case, yield("Ace::Base::Case")
assert_equal Ace::Base::Case, yield("::Ace::Base::Case")
assert_equal Ace::Base::Case::Dice, yield("Ace::Base::Case::Dice")
+ assert_equal Ace::Base::Case::Dice, yield("Ace::Base::Fase::Dice")
assert_equal Ace::Base::Fase::Dice, yield("Ace::Base::Fase::Dice")
+
assert_equal Ace::Gas::Case, yield("Ace::Gas::Case")
assert_equal Ace::Gas::Case::Dice, yield("Ace::Gas::Case::Dice")
+ assert_equal Ace::Base::Case::Dice, yield("Ace::Gas::Case::Dice")
+
assert_equal Case::Dice, yield("Case::Dice")
+ assert_equal AddtlGlobalConstants::Case::Dice, yield("Case::Dice")
+ assert_equal Object::AddtlGlobalConstants::Case::Dice, yield("Case::Dice")
+
assert_equal Case::Dice, yield("Object::Case::Dice")
+ assert_equal AddtlGlobalConstants::Case::Dice, yield("Object::Case::Dice")
+ assert_equal Object::AddtlGlobalConstants::Case::Dice, yield("Case::Dice")
+
assert_equal ConstantizeTestCases, yield("ConstantizeTestCases")
assert_equal ConstantizeTestCases, yield("::ConstantizeTestCases")
+
assert_raises(NameError) { yield("UnknownClass") }
assert_raises(NameError) { yield("UnknownClass::Ace") }
assert_raises(NameError) { yield("UnknownClass::Ace::Base") }
@@ -45,6 +60,19 @@ module ConstantizeTestCases
assert_raises(NameError) { yield("Ace::Gas::ConstantizeTestCases") }
assert_raises(NameError) { yield("") }
assert_raises(NameError) { yield("::") }
+ assert_raises(NameError) { yield("Ace::gas") }
+
+ assert_raises(NameError) do
+ with_autoloading_fixtures do
+ yield("RaisesNameError")
+ end
+ end
+
+ assert_raises(NoMethodError) do
+ with_autoloading_fixtures do
+ yield("RaisesNoMethodError")
+ end
+ end
end
def run_safe_constantize_tests_on
@@ -71,5 +99,18 @@ module ConstantizeTestCases
assert_nil yield("Ace::Gas::Base")
assert_nil yield("Ace::Gas::ConstantizeTestCases")
assert_nil yield("#<Class:0x7b8b718b>::Nested_1")
+ assert_nil yield("Ace::gas")
+
+ assert_raises(NameError) do
+ with_autoloading_fixtures do
+ yield("RaisesNameError")
+ end
+ end
+
+ assert_raises(NoMethodError) do
+ with_autoloading_fixtures do
+ yield("RaisesNoMethodError")
+ end
+ end
end
end
diff --git a/activesupport/test/core_ext/array/access_test.rb b/activesupport/test/core_ext/array/access_test.rb
new file mode 100644
index 0000000000..f14f64421d
--- /dev/null
+++ b/activesupport/test/core_ext/array/access_test.rb
@@ -0,0 +1,30 @@
+require 'abstract_unit'
+require 'active_support/core_ext/array'
+
+class AccessTest < ActiveSupport::TestCase
+ def test_from
+ assert_equal %w( a b c d ), %w( a b c d ).from(0)
+ assert_equal %w( c d ), %w( a b c d ).from(2)
+ assert_equal %w(), %w( a b c d ).from(10)
+ assert_equal %w( d e ), %w( a b c d e ).from(-2)
+ assert_equal %w(), %w( a b c d e ).from(-10)
+ end
+
+ def test_to
+ assert_equal %w( a ), %w( a b c d ).to(0)
+ assert_equal %w( a b c ), %w( a b c d ).to(2)
+ assert_equal %w( a b c d ), %w( a b c d ).to(10)
+ assert_equal %w( a b c ), %w( a b c d ).to(-2)
+ assert_equal %w(), %w( a b c ).to(-10)
+ end
+
+ def test_specific_accessor
+ array = (1..42).to_a
+
+ assert_equal array[1], array.second
+ assert_equal array[2], array.third
+ assert_equal array[3], array.fourth
+ assert_equal array[4], array.fifth
+ assert_equal array[41], array.forty_two
+ end
+end
diff --git a/activesupport/test/core_ext/array/conversions_test.rb b/activesupport/test/core_ext/array/conversions_test.rb
new file mode 100644
index 0000000000..577b889410
--- /dev/null
+++ b/activesupport/test/core_ext/array/conversions_test.rb
@@ -0,0 +1,197 @@
+require 'abstract_unit'
+require 'active_support/core_ext/array'
+require 'active_support/core_ext/big_decimal'
+require 'active_support/core_ext/hash'
+require 'active_support/core_ext/string'
+
+class ToSentenceTest < ActiveSupport::TestCase
+ def test_plain_array_to_sentence
+ assert_equal "", [].to_sentence
+ assert_equal "one", ['one'].to_sentence
+ assert_equal "one and two", ['one', 'two'].to_sentence
+ assert_equal "one, two, and three", ['one', 'two', 'three'].to_sentence
+ end
+
+ def test_to_sentence_with_words_connector
+ assert_equal "one two, and three", ['one', 'two', 'three'].to_sentence(words_connector: ' ')
+ assert_equal "one & two, and three", ['one', 'two', 'three'].to_sentence(words_connector: ' & ')
+ assert_equal "onetwo, and three", ['one', 'two', 'three'].to_sentence(words_connector: nil)
+ end
+
+ def test_to_sentence_with_last_word_connector
+ assert_equal "one, two, and also three", ['one', 'two', 'three'].to_sentence(last_word_connector: ', and also ')
+ assert_equal "one, twothree", ['one', 'two', 'three'].to_sentence(last_word_connector: nil)
+ assert_equal "one, two three", ['one', 'two', 'three'].to_sentence(last_word_connector: ' ')
+ assert_equal "one, two and three", ['one', 'two', 'three'].to_sentence(last_word_connector: ' and ')
+ end
+
+ def test_two_elements
+ assert_equal "one and two", ['one', 'two'].to_sentence
+ assert_equal "one two", ['one', 'two'].to_sentence(two_words_connector: ' ')
+ end
+
+ def test_one_element
+ assert_equal "one", ['one'].to_sentence
+ end
+
+ def test_one_element_not_same_object
+ elements = ["one"]
+ assert_not_equal elements[0].object_id, elements.to_sentence.object_id
+ end
+
+ def test_one_non_string_element
+ assert_equal '1', [1].to_sentence
+ end
+
+ def test_does_not_modify_given_hash
+ options = { words_connector: ' ' }
+ assert_equal "one two, and three", ['one', 'two', 'three'].to_sentence(options)
+ assert_equal({ words_connector: ' ' }, options)
+ end
+
+ def test_with_blank_elements
+ assert_equal ", one, , two, and three", [nil, 'one', '', 'two', 'three'].to_sentence
+ end
+
+ def test_with_invalid_options
+ exception = assert_raise ArgumentError do
+ ['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"
+ end
+end
+
+class ToSTest < ActiveSupport::TestCase
+ class TestDB
+ @@counter = 0
+ def id
+ @@counter += 1
+ end
+ end
+
+ def test_to_s_db
+ collection = [TestDB.new, TestDB.new, TestDB.new]
+
+ assert_equal "null", [].to_s(:db)
+ assert_equal "1,2,3", collection.to_s(:db)
+ end
+end
+
+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') }
+ ].to_xml(skip_instruct: true, indent: 0)
+
+ assert_equal '<objects type="array"><object>', xml.first(30)
+ assert xml.include?(%(<age type="integer">26</age>)), xml
+ assert xml.include?(%(<age-in-millis type="integer">820497600000</age-in-millis>)), xml
+ assert xml.include?(%(<name>David</name>)), xml
+ assert xml.include?(%(<age type="integer">31</age>)), xml
+ assert xml.include?(%(<age-in-millis type="decimal">1.0</age-in-millis>)), xml
+ assert xml.include?(%(<name>Jason</name>)), xml
+ end
+
+ def test_to_xml_with_non_hash_elements
+ xml = [1, 2, 3].to_xml(skip_instruct: true, indent: 0)
+
+ assert_equal '<fixnums type="array"><fixnum', xml.first(29)
+ assert xml.include?(%(<fixnum type="integer">2</fixnum>)), xml
+ end
+
+ def test_to_xml_with_non_hash_different_type_elements
+ xml = [1, 2.0, '3'].to_xml(skip_instruct: true, indent: 0)
+
+ assert_equal '<objects type="array"><object', xml.first(29)
+ assert xml.include?(%(<object type="integer">1</object>)), xml
+ assert xml.include?(%(<object type="float">2.0</object>)), xml
+ assert xml.include?(%(object>3</object>)), xml
+ end
+
+ def test_to_xml_with_dedicated_name
+ xml = [
+ { name: "David", age: 26, age_in_millis: 820497600000 }, { name: "Jason", age: 31 }
+ ].to_xml(skip_instruct: true, indent: 0, root: "people")
+
+ assert_equal '<people type="array"><person>', xml.first(29)
+ end
+
+ def test_to_xml_with_options
+ xml = [
+ { name: "David", street_address: "Paulina" }, { name: "Jason", street_address: "Evergreen" }
+ ].to_xml(skip_instruct: true, skip_types: true, indent: 0)
+
+ assert_equal "<objects><object>", xml.first(17)
+ assert xml.include?(%(<street-address>Paulina</street-address>))
+ assert xml.include?(%(<name>David</name>))
+ assert xml.include?(%(<street-address>Evergreen</street-address>))
+ assert xml.include?(%(<name>Jason</name>))
+ end
+
+ def test_to_xml_with_indent_set
+ xml = [
+ { name: "David", street_address: "Paulina" }, { name: "Jason", street_address: "Evergreen" }
+ ].to_xml(skip_instruct: true, skip_types: true, indent: 4)
+
+ assert_equal "<objects>\n <object>", xml.first(22)
+ assert xml.include?(%(\n <street-address>Paulina</street-address>))
+ assert xml.include?(%(\n <name>David</name>))
+ assert xml.include?(%(\n <street-address>Evergreen</street-address>))
+ assert xml.include?(%(\n <name>Jason</name>))
+ end
+
+ def test_to_xml_with_dasherize_false
+ xml = [
+ { name: "David", street_address: "Paulina" }, { name: "Jason", street_address: "Evergreen" }
+ ].to_xml(skip_instruct: true, skip_types: true, indent: 0, dasherize: false)
+
+ assert_equal "<objects><object>", xml.first(17)
+ assert xml.include?(%(<street_address>Paulina</street_address>))
+ assert xml.include?(%(<street_address>Evergreen</street_address>))
+ end
+
+ def test_to_xml_with_dasherize_true
+ xml = [
+ { name: "David", street_address: "Paulina" }, { name: "Jason", street_address: "Evergreen" }
+ ].to_xml(skip_instruct: true, skip_types: true, indent: 0, dasherize: true)
+
+ assert_equal "<objects><object>", xml.first(17)
+ assert xml.include?(%(<street-address>Paulina</street-address>))
+ assert xml.include?(%(<street-address>Evergreen</street-address>))
+ end
+
+ 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') }
+ ].to_xml(skip_instruct: false, indent: 0)
+
+ assert_match(/^<\?xml [^>]*/, xml)
+ assert_equal 0, xml.rindex(/<\?xml /)
+ end
+
+ 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') }
+ ].to_xml(skip_instruct: true, indent: 0) do |builder|
+ builder.count 2
+ end
+
+ assert xml.include?(%(<count>2</count>)), xml
+ end
+
+ def test_to_xml_with_empty
+ xml = [].to_xml
+ assert_match(/type="array"\/>/, xml)
+ end
+
+ def test_to_xml_dups_options
+ options = { skip_instruct: true }
+ [].to_xml(options)
+ # :builder, etc, shouldn't be added to options
+ assert_equal({ skip_instruct: true }, options)
+ end
+end
diff --git a/activesupport/test/core_ext/array/extract_options_test.rb b/activesupport/test/core_ext/array/extract_options_test.rb
new file mode 100644
index 0000000000..0481a507cf
--- /dev/null
+++ b/activesupport/test/core_ext/array/extract_options_test.rb
@@ -0,0 +1,45 @@
+require 'abstract_unit'
+require 'active_support/core_ext/array'
+require 'active_support/core_ext/hash'
+
+class ExtractOptionsTest < ActiveSupport::TestCase
+ class HashSubclass < Hash
+ end
+
+ class ExtractableHashSubclass < Hash
+ def extractable_options?
+ true
+ end
+ end
+
+ def test_extract_options
+ assert_equal({}, [].extract_options!)
+ assert_equal({}, [1].extract_options!)
+ assert_equal({ a: :b }, [{ a: :b }].extract_options!)
+ assert_equal({ a: :b }, [1, { a: :b }].extract_options!)
+ end
+
+ def test_extract_options_doesnt_extract_hash_subclasses
+ hash = HashSubclass.new
+ hash[:foo] = 1
+ array = [hash]
+ options = array.extract_options!
+ assert_equal({}, options)
+ assert_equal([hash], array)
+ end
+
+ def test_extract_options_extracts_extractable_subclass
+ hash = ExtractableHashSubclass.new
+ hash[:foo] = 1
+ array = [hash]
+ options = array.extract_options!
+ assert_equal({ foo: 1 }, options)
+ assert_equal([], array)
+ end
+
+ def test_extract_options_extracts_hash_with_indifferent_access
+ array = [{ foo: 1 }.with_indifferent_access]
+ options = array.extract_options!
+ assert_equal(1, options[:foo])
+ end
+end
diff --git a/activesupport/test/core_ext/array/grouping_test.rb b/activesupport/test/core_ext/array/grouping_test.rb
new file mode 100644
index 0000000000..2eb0f05141
--- /dev/null
+++ b/activesupport/test/core_ext/array/grouping_test.rb
@@ -0,0 +1,126 @@
+require 'abstract_unit'
+require 'active_support/core_ext/array'
+
+class GroupingTest < ActiveSupport::TestCase
+ def setup
+ Fixnum.send :private, :/ # test we avoid Integer#/ (redefined by mathn)
+ end
+
+ def teardown
+ Fixnum.send :public, :/
+ end
+
+ def test_in_groups_of_with_perfect_fit
+ groups = []
+ ('a'..'i').to_a.in_groups_of(3) do |group|
+ groups << group
+ end
+
+ assert_equal [%w(a b c), %w(d e f), %w(g h i)], groups
+ assert_equal [%w(a b c), %w(d e f), %w(g h i)], ('a'..'i').to_a.in_groups_of(3)
+ end
+
+ def test_in_groups_of_with_padding
+ groups = []
+ ('a'..'g').to_a.in_groups_of(3) do |group|
+ groups << group
+ end
+
+ assert_equal [%w(a b c), %w(d e f), ['g', nil, nil]], groups
+ end
+
+ def test_in_groups_of_pads_with_specified_values
+ groups = []
+
+ ('a'..'g').to_a.in_groups_of(3, 'foo') do |group|
+ groups << group
+ end
+
+ assert_equal [%w(a b c), %w(d e f), %w(g foo foo)], groups
+ end
+
+ def test_in_groups_of_without_padding
+ groups = []
+
+ ('a'..'g').to_a.in_groups_of(3, false) do |group|
+ groups << group
+ end
+
+ assert_equal [%w(a b c), %w(d e f), %w(g)], groups
+ end
+
+ def test_in_groups_returned_array_size
+ array = (1..7).to_a
+
+ 1.upto(array.size + 1) do |number|
+ assert_equal number, array.in_groups(number).size
+ end
+ end
+
+ def test_in_groups_with_empty_array
+ assert_equal [[], [], []], [].in_groups(3)
+ end
+
+ def test_in_groups_with_block
+ array = (1..9).to_a
+ groups = []
+
+ array.in_groups(3) do |group|
+ groups << group
+ end
+
+ assert_equal array.in_groups(3), groups
+ end
+
+ def test_in_groups_with_perfect_fit
+ assert_equal [[1, 2, 3], [4, 5, 6], [7, 8, 9]],
+ (1..9).to_a.in_groups(3)
+ end
+
+ def test_in_groups_with_padding
+ array = (1..7).to_a
+
+ assert_equal [[1, 2, 3], [4, 5, nil], [6, 7, nil]],
+ array.in_groups(3)
+ assert_equal [[1, 2, 3], [4, 5, 'foo'], [6, 7, 'foo']],
+ array.in_groups(3, 'foo')
+ end
+
+ def test_in_groups_without_padding
+ assert_equal [[1, 2, 3], [4, 5], [6, 7]],
+ (1..7).to_a.in_groups(3, false)
+ end
+
+ def test_in_groups_invalid_argument
+ assert_raises(ArgumentError) { [].in_groups_of(0) }
+ assert_raises(ArgumentError) { [].in_groups_of(-1) }
+ assert_raises(ArgumentError) { [].in_groups_of(nil) }
+ end
+end
+
+class SplitTest < ActiveSupport::TestCase
+ def test_split_with_empty_array
+ assert_equal [[]], [].split(0)
+ end
+
+ def test_split_with_argument
+ a = [1, 2, 3, 4, 5]
+ assert_equal [[1, 2], [4, 5]], a.split(3)
+ assert_equal [[1, 2, 3, 4, 5]], a.split(0)
+ assert_equal [1, 2, 3, 4, 5], a
+ end
+
+ 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
+ end
+
+ def test_split_with_edge_values
+ a = [1, 2, 3, 4, 5]
+ assert_equal [[], [2, 3, 4, 5]], a.split(1)
+ assert_equal [[1, 2, 3, 4], []], a.split(5)
+ assert_equal [[], [2, 3, 4], []], a.split { |i| i == 1 || i == 5 }
+ assert_equal [1, 2, 3, 4, 5], a
+ end
+end
diff --git a/activesupport/test/core_ext/array/prepend_append_test.rb b/activesupport/test/core_ext/array/prepend_append_test.rb
new file mode 100644
index 0000000000..762aa69b2b
--- /dev/null
+++ b/activesupport/test/core_ext/array/prepend_append_test.rb
@@ -0,0 +1,12 @@
+require 'abstract_unit'
+require 'active_support/core_ext/array'
+
+class PrependAppendTest < ActiveSupport::TestCase
+ def test_append
+ assert_equal [1, 2], [1].append(2)
+ end
+
+ def test_prepend
+ assert_equal [2, 1], [1].prepend(2)
+ end
+end
diff --git a/activesupport/test/core_ext/array/wrap_test.rb b/activesupport/test/core_ext/array/wrap_test.rb
new file mode 100644
index 0000000000..baf426506f
--- /dev/null
+++ b/activesupport/test/core_ext/array/wrap_test.rb
@@ -0,0 +1,77 @@
+require 'abstract_unit'
+require 'active_support/core_ext/array'
+
+class WrapTest < ActiveSupport::TestCase
+ class FakeCollection
+ def to_ary
+ ["foo", "bar"]
+ end
+ end
+
+ class Proxy
+ def initialize(target) @target = target end
+ def method_missing(*a) @target.send(*a) end
+ end
+
+ class DoubtfulToAry
+ def to_ary
+ :not_an_array
+ end
+ end
+
+ class NilToAry
+ def to_ary
+ nil
+ end
+ end
+
+ def test_array
+ ary = %w(foo bar)
+ assert_same ary, Array.wrap(ary)
+ end
+
+ def test_nil
+ assert_equal [], Array.wrap(nil)
+ end
+
+ def test_object
+ o = Object.new
+ assert_equal [o], Array.wrap(o)
+ end
+
+ def test_string
+ assert_equal ["foo"], Array.wrap("foo")
+ end
+
+ def test_string_with_newline
+ assert_equal ["foo\nbar"], Array.wrap("foo\nbar")
+ end
+
+ def test_object_with_to_ary
+ assert_equal ["foo", "bar"], Array.wrap(FakeCollection.new)
+ end
+
+ def test_proxy_object
+ p = Proxy.new(Object.new)
+ assert_equal [p], Array.wrap(p)
+ end
+
+ def test_proxy_to_object_with_to_ary
+ p = Proxy.new(FakeCollection.new)
+ assert_equal [p], Array.wrap(p)
+ end
+
+ def test_struct
+ o = Struct.new(:foo).new(123)
+ assert_equal [o], Array.wrap(o)
+ end
+
+ def test_wrap_returns_wrapped_if_to_ary_returns_nil
+ o = NilToAry.new
+ assert_equal [o], Array.wrap(o)
+ end
+
+ def test_wrap_does_not_complain_if_to_ary_does_not_return_an_array
+ assert_equal DoubtfulToAry.new.to_ary, Array.wrap(DoubtfulToAry.new)
+ end
+end
diff --git a/activesupport/test/core_ext/array_ext_test.rb b/activesupport/test/core_ext/array_ext_test.rb
deleted file mode 100644
index 6cd0eb39b7..0000000000
--- a/activesupport/test/core_ext/array_ext_test.rb
+++ /dev/null
@@ -1,445 +0,0 @@
-require 'abstract_unit'
-require 'active_support/core_ext/array'
-require 'active_support/core_ext/big_decimal'
-require 'active_support/core_ext/object/conversions'
-
-require 'active_support/core_ext' # FIXME: pulling in all to_xml extensions
-require 'active_support/hash_with_indifferent_access'
-
-class ArrayExtAccessTests < ActiveSupport::TestCase
- def test_from
- assert_equal %w( a b c d ), %w( a b c d ).from(0)
- assert_equal %w( c d ), %w( a b c d ).from(2)
- assert_equal %w(), %w( a b c d ).from(10)
- end
-
- def test_to
- assert_equal %w( a ), %w( a b c d ).to(0)
- assert_equal %w( a b c ), %w( a b c d ).to(2)
- assert_equal %w( a b c d ), %w( a b c d ).to(10)
- end
-
- def test_second_through_tenth
- array = (1..42).to_a
-
- assert_equal array[1], array.second
- assert_equal array[2], array.third
- assert_equal array[3], array.fourth
- assert_equal array[4], array.fifth
- assert_equal array[41], array.forty_two
- end
-end
-
-class ArrayExtToParamTests < ActiveSupport::TestCase
- class ToParam < String
- def to_param
- "#{self}1"
- end
- end
-
- def test_string_array
- assert_equal '', %w().to_param
- assert_equal 'hello/world', %w(hello world).to_param
- assert_equal 'hello/10', %w(hello 10).to_param
- end
-
- def test_number_array
- assert_equal '10/20', [10, 20].to_param
- end
-
- def test_to_param_array
- assert_equal 'custom1/param1', [ToParam.new('custom'), ToParam.new('param')].to_param
- end
-end
-
-class ArrayExtToSentenceTests < ActiveSupport::TestCase
- def test_plain_array_to_sentence
- assert_equal "", [].to_sentence
- assert_equal "one", ['one'].to_sentence
- assert_equal "one and two", ['one', 'two'].to_sentence
- assert_equal "one, two, and three", ['one', 'two', 'three'].to_sentence
- end
-
- def test_to_sentence_with_words_connector
- assert_equal "one two, and three", ['one', 'two', 'three'].to_sentence(:words_connector => ' ')
- assert_equal "one & two, and three", ['one', 'two', 'three'].to_sentence(:words_connector => ' & ')
- assert_equal "onetwo, and three", ['one', 'two', 'three'].to_sentence(:words_connector => nil)
- end
-
- def test_to_sentence_with_last_word_connector
- assert_equal "one, two, and also three", ['one', 'two', 'three'].to_sentence(:last_word_connector => ', and also ')
- assert_equal "one, twothree", ['one', 'two', 'three'].to_sentence(:last_word_connector => nil)
- assert_equal "one, two three", ['one', 'two', 'three'].to_sentence(:last_word_connector => ' ')
- assert_equal "one, two and three", ['one', 'two', 'three'].to_sentence(:last_word_connector => ' and ')
- end
-
- def test_two_elements
- assert_equal "one and two", ['one', 'two'].to_sentence
- assert_equal "one two", ['one', 'two'].to_sentence(:two_words_connector => ' ')
- end
-
- def test_one_element
- assert_equal "one", ['one'].to_sentence
- end
-
- def test_one_element_not_same_object
- elements = ["one"]
- assert_not_equal elements[0].object_id, elements.to_sentence.object_id
- end
-
- def test_one_non_string_element
- assert_equal '1', [1].to_sentence
- end
-
- def test_does_not_modify_given_hash
- options = { words_connector: ' ' }
- assert_equal "one two, and three", ['one', 'two', 'three'].to_sentence(options)
- assert_equal({ words_connector: ' ' }, options)
- end
-
- def test_with_blank_elements
- assert_equal ", one, , two, and three", [nil, 'one', '', 'two', 'three'].to_sentence
- end
-end
-
-class ArrayExtToSTests < ActiveSupport::TestCase
- def test_to_s_db
- collection = [
- Class.new { def id() 1 end }.new,
- Class.new { def id() 2 end }.new,
- Class.new { def id() 3 end }.new
- ]
-
- assert_equal "null", [].to_s(:db)
- assert_equal "1,2,3", collection.to_s(:db)
- end
-end
-
-class ArrayExtGroupingTests < ActiveSupport::TestCase
- def setup
- Fixnum.send :private, :/ # test we avoid Integer#/ (redefined by mathn)
- end
-
- def teardown
- Fixnum.send :public, :/
- end
-
- def test_in_groups_of_with_perfect_fit
- groups = []
- ('a'..'i').to_a.in_groups_of(3) do |group|
- groups << group
- end
-
- assert_equal [%w(a b c), %w(d e f), %w(g h i)], groups
- assert_equal [%w(a b c), %w(d e f), %w(g h i)], ('a'..'i').to_a.in_groups_of(3)
- end
-
- def test_in_groups_of_with_padding
- groups = []
- ('a'..'g').to_a.in_groups_of(3) do |group|
- groups << group
- end
-
- assert_equal [%w(a b c), %w(d e f), ['g', nil, nil]], groups
- end
-
- def test_in_groups_of_pads_with_specified_values
- groups = []
-
- ('a'..'g').to_a.in_groups_of(3, 'foo') do |group|
- groups << group
- end
-
- assert_equal [%w(a b c), %w(d e f), ['g', 'foo', 'foo']], groups
- end
-
- def test_in_groups_of_without_padding
- groups = []
-
- ('a'..'g').to_a.in_groups_of(3, false) do |group|
- groups << group
- end
-
- assert_equal [%w(a b c), %w(d e f), ['g']], groups
- end
-
- def test_in_groups_returned_array_size
- array = (1..7).to_a
-
- 1.upto(array.size + 1) do |number|
- assert_equal number, array.in_groups(number).size
- end
- end
-
- def test_in_groups_with_empty_array
- assert_equal [[], [], []], [].in_groups(3)
- end
-
- def test_in_groups_with_block
- array = (1..9).to_a
- groups = []
-
- array.in_groups(3) do |group|
- groups << group
- end
-
- assert_equal array.in_groups(3), groups
- end
-
- def test_in_groups_with_perfect_fit
- assert_equal [[1, 2, 3], [4, 5, 6], [7, 8, 9]],
- (1..9).to_a.in_groups(3)
- end
-
- def test_in_groups_with_padding
- array = (1..7).to_a
-
- assert_equal [[1, 2, 3], [4, 5, nil], [6, 7, nil]],
- array.in_groups(3)
- assert_equal [[1, 2, 3], [4, 5, 'foo'], [6, 7, 'foo']],
- array.in_groups(3, 'foo')
- end
-
- def test_in_groups_without_padding
- assert_equal [[1, 2, 3], [4, 5], [6, 7]],
- (1..7).to_a.in_groups(3, false)
- end
-end
-
-class ArraySplitTests < ActiveSupport::TestCase
- def test_split_with_empty_array
- assert_equal [[]], [].split(0)
- end
-
- def test_split_with_argument
- assert_equal [[1, 2], [4, 5]], [1, 2, 3, 4, 5].split(3)
- assert_equal [[1, 2, 3, 4, 5]], [1, 2, 3, 4, 5].split(0)
- end
-
- def test_split_with_block
- assert_equal [[1, 2], [4, 5], [7, 8], [10]], (1..10).to_a.split { |i| i % 3 == 0 }
- end
-
- def test_split_with_edge_values
- assert_equal [[], [2, 3, 4, 5]], [1, 2, 3, 4, 5].split(1)
- assert_equal [[1, 2, 3, 4], []], [1, 2, 3, 4, 5].split(5)
- assert_equal [[], [2, 3, 4], []], [1, 2, 3, 4, 5].split { |i| i == 1 || i == 5 }
- end
-end
-
-class ArrayToXmlTests < ActiveSupport::TestCase
- def test_to_xml
- xml = [
- { :name => "David", :age => 26, :age_in_millis => 820497600000 },
- { :name => "Jason", :age => 31, :age_in_millis => BigDecimal.new('1.0') }
- ].to_xml(:skip_instruct => true, :indent => 0)
-
- assert_equal '<objects type="array"><object>', xml.first(30)
- assert xml.include?(%(<age type="integer">26</age>)), xml
- assert xml.include?(%(<age-in-millis type="integer">820497600000</age-in-millis>)), xml
- assert xml.include?(%(<name>David</name>)), xml
- assert xml.include?(%(<age type="integer">31</age>)), xml
- assert xml.include?(%(<age-in-millis type="decimal">1.0</age-in-millis>)), xml
- assert xml.include?(%(<name>Jason</name>)), xml
- end
-
- def test_to_xml_with_dedicated_name
- xml = [
- { :name => "David", :age => 26, :age_in_millis => 820497600000 }, { :name => "Jason", :age => 31 }
- ].to_xml(:skip_instruct => true, :indent => 0, :root => "people")
-
- assert_equal '<people type="array"><person>', xml.first(29)
- end
-
- def test_to_xml_with_options
- xml = [
- { :name => "David", :street_address => "Paulina" }, { :name => "Jason", :street_address => "Evergreen" }
- ].to_xml(:skip_instruct => true, :skip_types => true, :indent => 0)
-
- assert_equal "<objects><object>", xml.first(17)
- assert xml.include?(%(<street-address>Paulina</street-address>))
- assert xml.include?(%(<name>David</name>))
- assert xml.include?(%(<street-address>Evergreen</street-address>))
- assert xml.include?(%(<name>Jason</name>))
- end
-
- def test_to_xml_with_dasherize_false
- xml = [
- { :name => "David", :street_address => "Paulina" }, { :name => "Jason", :street_address => "Evergreen" }
- ].to_xml(:skip_instruct => true, :skip_types => true, :indent => 0, :dasherize => false)
-
- assert_equal "<objects><object>", xml.first(17)
- assert xml.include?(%(<street_address>Paulina</street_address>))
- assert xml.include?(%(<street_address>Evergreen</street_address>))
- end
-
- def test_to_xml_with_dasherize_true
- xml = [
- { :name => "David", :street_address => "Paulina" }, { :name => "Jason", :street_address => "Evergreen" }
- ].to_xml(:skip_instruct => true, :skip_types => true, :indent => 0, :dasherize => true)
-
- assert_equal "<objects><object>", xml.first(17)
- assert xml.include?(%(<street-address>Paulina</street-address>))
- assert xml.include?(%(<street-address>Evergreen</street-address>))
- end
-
- def test_to_with_instruct
- xml = [
- { :name => "David", :age => 26, :age_in_millis => 820497600000 },
- { :name => "Jason", :age => 31, :age_in_millis => BigDecimal.new('1.0') }
- ].to_xml(:skip_instruct => false, :indent => 0)
-
- assert_match(/^<\?xml [^>]*/, xml)
- assert_equal 0, xml.rindex(/<\?xml /)
- end
-
- 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') }
- ].to_xml(:skip_instruct => true, :indent => 0) do |builder|
- builder.count 2
- end
-
- assert xml.include?(%(<count>2</count>)), xml
- end
-
- def test_to_xml_with_empty
- xml = [].to_xml
- assert_match(/type="array"\/>/, xml)
- end
-
- def test_to_xml_dups_options
- options = {:skip_instruct => true}
- [].to_xml(options)
- # :builder, etc, shouldn't be added to options
- assert_equal({:skip_instruct => true}, options)
- end
-end
-
-class ArrayExtractOptionsTests < ActiveSupport::TestCase
- class HashSubclass < Hash
- end
-
- class ExtractableHashSubclass < Hash
- def extractable_options?
- true
- end
- end
-
- def test_extract_options
- assert_equal({}, [].extract_options!)
- assert_equal({}, [1].extract_options!)
- assert_equal({:a=>:b}, [{:a=>:b}].extract_options!)
- assert_equal({:a=>:b}, [1, {:a=>:b}].extract_options!)
- end
-
- def test_extract_options_doesnt_extract_hash_subclasses
- hash = HashSubclass.new
- hash[:foo] = 1
- array = [hash]
- options = array.extract_options!
- assert_equal({}, options)
- assert_equal [hash], array
- end
-
- def test_extract_options_extracts_extractable_subclass
- hash = ExtractableHashSubclass.new
- hash[:foo] = 1
- array = [hash]
- options = array.extract_options!
- assert_equal({:foo => 1}, options)
- assert_equal [], array
- end
-
- def test_extract_options_extracts_hwia
- hash = [{:foo => 1}.with_indifferent_access]
- options = hash.extract_options!
- assert_equal 1, options[:foo]
- end
-end
-
-class ArrayWrapperTests < ActiveSupport::TestCase
- class FakeCollection
- def to_ary
- ["foo", "bar"]
- end
- end
-
- class Proxy
- def initialize(target) @target = target end
- def method_missing(*a) @target.send(*a) end
- end
-
- class DoubtfulToAry
- def to_ary
- :not_an_array
- end
- end
-
- class NilToAry
- def to_ary
- nil
- end
- end
-
- def test_array
- ary = %w(foo bar)
- assert_same ary, Array.wrap(ary)
- end
-
- def test_nil
- assert_equal [], Array.wrap(nil)
- end
-
- def test_object
- o = Object.new
- assert_equal [o], Array.wrap(o)
- end
-
- def test_string
- assert_equal ["foo"], Array.wrap("foo")
- end
-
- def test_string_with_newline
- assert_equal ["foo\nbar"], Array.wrap("foo\nbar")
- end
-
- def test_object_with_to_ary
- assert_equal ["foo", "bar"], Array.wrap(FakeCollection.new)
- end
-
- def test_proxy_object
- p = Proxy.new(Object.new)
- assert_equal [p], Array.wrap(p)
- end
-
- def test_proxy_to_object_with_to_ary
- p = Proxy.new(FakeCollection.new)
- assert_equal [p], Array.wrap(p)
- end
-
- def test_struct
- o = Struct.new(:foo).new(123)
- assert_equal [o], Array.wrap(o)
- end
-
- def test_wrap_returns_wrapped_if_to_ary_returns_nil
- o = NilToAry.new
- assert_equal [o], Array.wrap(o)
- end
-
- def test_wrap_does_not_complain_if_to_ary_does_not_return_an_array
- assert_equal DoubtfulToAry.new.to_ary, Array.wrap(DoubtfulToAry.new)
- end
-end
-
-class ArrayPrependAppendTest < ActiveSupport::TestCase
- def test_append
- assert_equal [1, 2], [1].append(2)
- end
-
- def test_prepend
- assert_equal [2, 1], [1].prepend(2)
- end
-end
diff --git a/activesupport/test/core_ext/bigdecimal_test.rb b/activesupport/test/core_ext/bigdecimal_test.rb
index a5987044b9..423a3f2e9d 100644
--- a/activesupport/test/core_ext/bigdecimal_test.rb
+++ b/activesupport/test/core_ext/bigdecimal_test.rb
@@ -1,20 +1,7 @@
require 'abstract_unit'
-require 'bigdecimal'
require 'active_support/core_ext/big_decimal'
class BigDecimalTest < ActiveSupport::TestCase
- def test_to_yaml
- assert_match("--- 100000.30020320320000000000000000000000000000001\n", BigDecimal.new('100000.30020320320000000000000000000000000000001').to_yaml)
- assert_match("--- .Inf\n", BigDecimal.new('Infinity').to_yaml)
- assert_match("--- .NaN\n", BigDecimal.new('NaN').to_yaml)
- assert_match("--- -.Inf\n", BigDecimal.new('-Infinity').to_yaml)
- end
-
- def test_to_d
- bd = BigDecimal.new '10'
- assert_equal bd, bd.to_d
- end
-
def test_to_s
bd = BigDecimal.new '0.01'
assert_equal '0.01', bd.to_s
diff --git a/activesupport/test/core_ext/class/attribute_accessor_test.rb b/activesupport/test/core_ext/class/attribute_accessor_test.rb
deleted file mode 100644
index 0d5f39a72b..0000000000
--- a/activesupport/test/core_ext/class/attribute_accessor_test.rb
+++ /dev/null
@@ -1,61 +0,0 @@
-require 'abstract_unit'
-require 'active_support/core_ext/class/attribute_accessors'
-
-class ClassAttributeAccessorTest < ActiveSupport::TestCase
- def setup
- @class = Class.new do
- cattr_accessor :foo
- cattr_accessor :bar, :instance_writer => false
- cattr_reader :shaq, :instance_reader => false
- cattr_accessor :camp, :instance_accessor => false
- end
- @object = @class.new
- end
-
- def test_should_use_mattr_default
- assert_nil @class.foo
- assert_nil @object.foo
- end
-
- def test_should_set_mattr_value
- @class.foo = :test
- assert_equal :test, @object.foo
-
- @object.foo = :test2
- assert_equal :test2, @class.foo
- end
-
- def test_should_not_create_instance_writer
- assert_respond_to @class, :foo
- assert_respond_to @class, :foo=
- assert_respond_to @object, :bar
- assert !@object.respond_to?(:bar=)
- end
-
- def test_should_not_create_instance_reader
- assert_respond_to @class, :shaq
- assert !@object.respond_to?(:shaq)
- end
-
- def test_should_not_create_instance_accessors
- assert_respond_to @class, :camp
- assert !@object.respond_to?(:camp)
- assert !@object.respond_to?(:camp=)
- end
-
- def test_should_raise_name_error_if_attribute_name_is_invalid
- exception = assert_raises NameError do
- Class.new do
- cattr_reader "1nvalid"
- end
- end
- assert_equal "invalid class attribute name: 1nvalid", exception.message
-
- exception = assert_raises NameError do
- Class.new do
- cattr_writer "1nvalid"
- end
- end
- assert_equal "invalid class attribute name: 1nvalid", exception.message
- end
-end
diff --git a/activesupport/test/core_ext/class/delegating_attributes_test.rb b/activesupport/test/core_ext/class/delegating_attributes_test.rb
index 148f82946c..447b1d10ad 100644
--- a/activesupport/test/core_ext/class/delegating_attributes_test.rb
+++ b/activesupport/test/core_ext/class/delegating_attributes_test.rb
@@ -6,14 +6,18 @@ module DelegatingFixtures
end
class Child < Parent
- superclass_delegating_accessor :some_attribute
+ ActiveSupport::Deprecation.silence do
+ superclass_delegating_accessor :some_attribute
+ end
end
class Mokopuna < Child
end
class PercysMom
- superclass_delegating_accessor :superpower
+ ActiveSupport::Deprecation.silence do
+ superclass_delegating_accessor :superpower
+ end
end
class Percy < PercysMom
@@ -29,7 +33,10 @@ class DelegatingAttributesTest < ActiveSupport::TestCase
end
def test_simple_accessor_declaration
- single_class.superclass_delegating_accessor :both
+ assert_deprecated do
+ single_class.superclass_delegating_accessor :both
+ end
+
# Class should have accessor and mutator
# the instance should have an accessor only
assert_respond_to single_class, :both
@@ -39,14 +46,23 @@ class DelegatingAttributesTest < ActiveSupport::TestCase
end
def test_simple_accessor_declaration_with_instance_reader_false
- single_class.superclass_delegating_accessor :no_instance_reader, :instance_reader => false
+ _instance_methods = single_class.public_instance_methods
+
+ assert_deprecated do
+ single_class.superclass_delegating_accessor :no_instance_reader, :instance_reader => false
+ end
+
assert_respond_to single_class, :no_instance_reader
assert_respond_to single_class, :no_instance_reader=
- assert !single_class.public_instance_methods.map(&:to_s).include?("no_instance_reader")
+ assert !_instance_methods.include?(:no_instance_reader)
+ assert !_instance_methods.include?(:no_instance_reader?)
+ assert !_instance_methods.include?(:_no_instance_reader)
end
def test_working_with_simple_attributes
- single_class.superclass_delegating_accessor :both
+ assert_deprecated do
+ single_class.superclass_delegating_accessor :both
+ end
single_class.both = "HMMM"
@@ -62,7 +78,11 @@ class DelegatingAttributesTest < ActiveSupport::TestCase
def test_child_class_delegates_to_parent_but_can_be_overridden
parent = Class.new
- parent.superclass_delegating_accessor :both
+
+ assert_deprecated do
+ parent.superclass_delegating_accessor :both
+ end
+
child = Class.new(parent)
parent.both = "1"
assert_equal "1", child.both
@@ -94,4 +114,9 @@ class DelegatingAttributesTest < ActiveSupport::TestCase
Child.some_attribute=nil
end
+ def test_deprecation_warning
+ assert_deprecated(/superclass_delegating_accessor is deprecated/) do
+ single_class.superclass_delegating_accessor :test_attribute
+ end
+ end
end
diff --git a/activesupport/test/core_ext/date_and_time_behavior.rb b/activesupport/test/core_ext/date_and_time_behavior.rb
index b4ef5a0597..784547bdf8 100644
--- a/activesupport/test/core_ext/date_and_time_behavior.rb
+++ b/activesupport/test/core_ext/date_and_time_behavior.rb
@@ -6,11 +6,21 @@ module DateAndTimeBehavior
assert_equal date_time_init(2005,2,28,10,10,10), date_time_init(2005,3,2,10,10,10).yesterday.yesterday
end
+ def test_prev_day
+ 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
+
def test_tomorrow
assert_equal date_time_init(2005,2,23,10,10,10), date_time_init(2005,2,22,10,10,10).tomorrow
assert_equal date_time_init(2005,3,2,10,10,10), date_time_init(2005,2,28,10,10,10).tomorrow.tomorrow
end
+ def test_next_day
+ 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
+
def test_days_ago
assert_equal date_time_init(2005,6,4,10,10,10), date_time_init(2005,6,5,10,10,10).days_ago(1)
assert_equal date_time_init(2005,5,31,10,10,10), date_time_init(2005,6,5,10,10,10).days_ago(5)
@@ -115,6 +125,28 @@ module DateAndTimeBehavior
end
end
+ def test_next_week_at_same_time
+ assert_equal date_time_init(2005,2,28,15,15,10), date_time_init(2005,2,22,15,15,10).next_week(:monday, same_time: true)
+ assert_equal date_time_init(2005,3,4,15,15,10), date_time_init(2005,2,22,15,15,10).next_week(:friday, same_time: true)
+ assert_equal date_time_init(2006,10,30,0,0,0), date_time_init(2006,10,23,0,0,0).next_week(:monday, same_time: true)
+ assert_equal date_time_init(2006,11,1,0,0,0), date_time_init(2006,10,23,0,0,0).next_week(:wednesday, same_time: true)
+ end
+
+ def test_next_weekday_on_wednesday
+ assert_equal date_time_init(2015,1,8,0,0,0), date_time_init(2015,1,7,0,0,0).next_weekday
+ assert_equal date_time_init(2015,1,8,15,15,10), date_time_init(2015,1,7,15,15,10).next_weekday
+ end
+
+ def test_next_weekday_on_friday
+ assert_equal date_time_init(2015,1,5,0,0,0), date_time_init(2015,1,2,0,0,0).next_weekday
+ assert_equal date_time_init(2015,1,5,15,15,10), date_time_init(2015,1,2,15,15,10).next_weekday
+ end
+
+ def test_next_weekday_on_saturday
+ assert_equal date_time_init(2015,1,5,0,0,0), date_time_init(2015,1,3,0,0,0).next_weekday
+ 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_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
@@ -144,6 +176,29 @@ module DateAndTimeBehavior
end
end
+ def test_prev_week_at_same_time
+ assert_equal date_time_init(2005,2,21,15,15,10), date_time_init(2005,3,1,15,15,10).prev_week(:monday, same_time: true)
+ assert_equal date_time_init(2005,2,22,15,15,10), date_time_init(2005,3,1,15,15,10).prev_week(:tuesday, same_time: true)
+ assert_equal date_time_init(2005,2,25,15,15,10), date_time_init(2005,3,1,15,15,10).prev_week(:friday, same_time: true)
+ assert_equal date_time_init(2006,10,30,0,0,0), date_time_init(2006,11,6,0,0,0).prev_week(:monday, same_time: true)
+ assert_equal date_time_init(2006,11,15,0,0,0), date_time_init(2006,11,23,0,0,0).prev_week(:wednesday, same_time: true)
+ end
+
+ def test_prev_weekday_on_wednesday
+ assert_equal date_time_init(2015,1,6,0,0,0), date_time_init(2015,1,7,0,0,0).prev_weekday
+ assert_equal date_time_init(2015,1,6,15,15,10), date_time_init(2015,1,7,15,15,10).prev_weekday
+ end
+
+ def test_prev_weekday_on_monday
+ assert_equal date_time_init(2015,1,2,0,0,0), date_time_init(2015,1,5,0,0,0).prev_weekday
+ assert_equal date_time_init(2015,1,2,15,15,10), date_time_init(2015,1,5,15,15,10).prev_weekday
+ end
+
+ def test_prev_weekday_on_sunday
+ assert_equal date_time_init(2015,1,2,0,0,0), date_time_init(2015,1,4,0,0,0).prev_weekday
+ 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_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
@@ -231,6 +286,21 @@ module DateAndTimeBehavior
end
end
+ def test_on_weekend_on_saturday
+ assert date_time_init(2015,1,3,0,0,0).on_weekend?
+ assert date_time_init(2015,1,3,15,15,10).on_weekend?
+ end
+
+ def test_on_weekend_on_sunday
+ assert date_time_init(2015,1,4,0,0,0).on_weekend?
+ assert date_time_init(2015,1,4,15,15,10).on_weekend?
+ end
+
+ def test_on_weekend_on_monday
+ assert_not date_time_init(2015,1,5,0,0,0).on_weekend?
+ assert_not date_time_init(2015,1,5,15,15,10).on_weekend?
+ end
+
def with_bw_default(bw = :monday)
old_bw = Date.beginning_of_week
Date.beginning_of_week = bw
diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb
index eab3aa7a6e..e89be25b53 100644
--- a/activesupport/test/core_ext/date_ext_test.rb
+++ b/activesupport/test/core_ext/date_ext_test.rb
@@ -1,6 +1,7 @@
require 'abstract_unit'
require 'active_support/time'
require 'core_ext/date_and_time_behavior'
+require 'time_zone_test_helpers'
class DateExtCalculationsTest < ActiveSupport::TestCase
def date_time_init(year,month,day,*args)
@@ -8,6 +9,7 @@ class DateExtCalculationsTest < ActiveSupport::TestCase
end
include DateAndTimeBehavior
+ include TimeZoneTestHelpers
def test_yesterday_in_calendar_reform
assert_equal Date.new(1582,10,4), Date.new(1582,10,15).yesterday
@@ -276,6 +278,23 @@ class DateExtCalculationsTest < ActiveSupport::TestCase
end
end
+ def test_all_week
+ assert_equal Date.new(2011,6,6)..Date.new(2011,6,12), Date.new(2011,6,7).all_week
+ assert_equal Date.new(2011,6,5)..Date.new(2011,6,11), Date.new(2011,6,7).all_week(:sunday)
+ end
+
+ def test_all_month
+ assert_equal Date.new(2011,6,1)..Date.new(2011,6,30), Date.new(2011,6,7).all_month
+ end
+
+ def test_all_quarter
+ assert_equal Date.new(2011,4,1)..Date.new(2011,6,30), Date.new(2011,6,7).all_quarter
+ end
+
+ def test_all_year
+ assert_equal Date.new(2011,1,1)..Date.new(2011,12,31), Date.new(2011,6,7).all_year
+ end
+
def test_xmlschema
with_env_tz 'US/Eastern' do
assert_match(/^1980-02-28T00:00:00-05:?00$/, Date.new(1980, 2, 28).xmlschema)
@@ -332,22 +351,6 @@ class DateExtCalculationsTest < ActiveSupport::TestCase
Date.new(2005,2,28).advance(options)
assert_equal({ :years => 3, :months => 11, :days => 2 }, options)
end
-
- protected
- def with_env_tz(new_tz = 'US/Eastern')
- old_tz, ENV['TZ'] = ENV['TZ'], new_tz
- yield
- ensure
- old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ')
- end
-
- def with_tz_default(tz = nil)
- old_tz = Time.zone
- Time.zone = tz
- yield
- ensure
- Time.zone = old_tz
- end
end
class DateExtBehaviorTest < ActiveSupport::TestCase
diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb
index 0a40aeb96c..74319ecd09 100644
--- a/activesupport/test/core_ext/date_time_ext_test.rb
+++ b/activesupport/test/core_ext/date_time_ext_test.rb
@@ -1,6 +1,7 @@
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)
@@ -8,6 +9,7 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
end
include DateAndTimeBehavior
+ include TimeZoneTestHelpers
def test_to_s
datetime = DateTime.new(2005, 2, 21, 14, 30, 0, 0)
@@ -162,6 +164,12 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal DateTime.civil(2013,10,17,20,22,19), DateTime.civil(2005,2,28,15,15,10).advance(:years => 7, :months => 19, :weeks => 2, :days => 5, :hours => 5, :minutes => 7, :seconds => 9)
end
+ def test_advance_partial_days
+ assert_equal DateTime.civil(2012,9,29,13,15,10), DateTime.civil(2012,9,28,1,15,10).advance(:days => 1.5)
+ assert_equal DateTime.civil(2012,9,28,13,15,10), DateTime.civil(2012,9,28,1,15,10).advance(:days => 0.5)
+ assert_equal DateTime.civil(2012,10,29,13,15,10), DateTime.civil(2012,9,28,1,15,10).advance(:days => 1.5, :months => 1)
+ end
+
def test_advanced_processes_first_the_date_deltas_and_then_the_time_deltas
# If the time deltas were processed first, the following datetimes would be advanced to 2010/04/01 instead.
assert_equal DateTime.civil(2010, 3, 29), DateTime.civil(2010, 2, 28, 23, 59, 59).advance(:months => 1, :seconds => 1)
@@ -330,6 +338,7 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
def test_to_f
assert_equal 946684800.0, DateTime.civil(2000).to_f
assert_equal 946684800.0, DateTime.civil(1999,12,31,19,0,0,Rational(-5,24)).to_f
+ assert_equal 946684800.5, DateTime.civil(1999,12,31,19,0,0.5,Rational(-5,24)).to_f
end
def test_to_i
@@ -346,12 +355,4 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal 0, DateTime.civil(2000).nsec
assert_equal 500000000, DateTime.civil(2000, 1, 1, 0, 0, Rational(1,2)).nsec
end
-
- protected
- def with_env_tz(new_tz = 'US/Eastern')
- old_tz, ENV['TZ'] = ENV['TZ'], new_tz
- yield
- ensure
- old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ')
- end
end
diff --git a/activesupport/test/core_ext/digest/uuid_test.rb b/activesupport/test/core_ext/digest/uuid_test.rb
new file mode 100644
index 0000000000..08e0a1d6e1
--- /dev/null
+++ b/activesupport/test/core_ext/digest/uuid_test.rb
@@ -0,0 +1,24 @@
+require 'abstract_unit'
+require 'active_support/core_ext/digest/uuid'
+
+class DigestUUIDExt < ActiveSupport::TestCase
+ def test_v3_uuids
+ assert_equal "3d813cbb-47fb-32ba-91df-831e1593ac29", Digest::UUID.uuid_v3(Digest::UUID::DNS_NAMESPACE, "www.widgets.com")
+ assert_equal "86df55fb-428e-3843-8583-ba3c05f290bc", Digest::UUID.uuid_v3(Digest::UUID::URL_NAMESPACE, "http://www.widgets.com")
+ assert_equal "8c29ab0e-a2dc-3482-b5eb-20cb2e2387a1", Digest::UUID.uuid_v3(Digest::UUID::OID_NAMESPACE, "1.2.3")
+ assert_equal "ee49149d-53a4-304a-890b-468229f6afc3", Digest::UUID.uuid_v3(Digest::UUID::X500_NAMESPACE, "cn=John Doe, ou=People, o=Acme, Inc., c=US")
+ end
+
+ def test_v5_uuids
+ assert_equal "21f7f8de-8051-5b89-8680-0195ef798b6a", Digest::UUID.uuid_v5(Digest::UUID::DNS_NAMESPACE, "www.widgets.com")
+ assert_equal "4e570fd8-186d-5a74-90f0-4d28e34673a1", Digest::UUID.uuid_v5(Digest::UUID::URL_NAMESPACE, "http://www.widgets.com")
+ assert_equal "42d5e23b-3a02-5135-85c6-52d1102f1f00", Digest::UUID.uuid_v5(Digest::UUID::OID_NAMESPACE, "1.2.3")
+ assert_equal "fd5b2ddf-bcfe-58b6-90d6-db50f74db527", Digest::UUID.uuid_v5(Digest::UUID::X500_NAMESPACE, "cn=John Doe, ou=People, o=Acme, Inc., c=US")
+ end
+
+ def test_invalid_hash_class
+ assert_raise ArgumentError do
+ Digest::UUID.uuid_from_hash(Digest::SHA2, Digest::UUID::OID_NAMESPACE, '1.2.3')
+ end
+ end
+end
diff --git a/activesupport/test/core_ext/duplicable_test.rb b/activesupport/test/core_ext/duplicable_test.rb
deleted file mode 100644
index e0566e012c..0000000000
--- a/activesupport/test/core_ext/duplicable_test.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-require 'abstract_unit'
-require 'bigdecimal'
-require 'active_support/core_ext/object/duplicable'
-require 'active_support/core_ext/numeric/time'
-
-class DuplicableTest < ActiveSupport::TestCase
- RAISE_DUP = [nil, false, true, :symbol, 1, 2.3, 5.seconds]
- YES = ['1', Object.new, /foo/, [], {}, Time.now, Class.new, Module.new]
- NO = []
-
- begin
- bd = BigDecimal.new('4.56')
- YES << bd.dup
- rescue TypeError
- RAISE_DUP << bd
- end
-
-
- def test_duplicable
- (RAISE_DUP + NO).each do |v|
- assert !v.duplicable?
- end
-
- YES.each do |v|
- assert v.duplicable?, "#{v.class} should be duplicable"
- end
-
- (YES + NO).each do |v|
- assert_nothing_raised { v.dup }
- end
-
- RAISE_DUP.each do |v|
- assert_raises(TypeError, v.class.name) do
- v.dup
- end
- end
- end
-end
diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb
index 5e3987265b..c283b546e6 100644
--- a/activesupport/test/core_ext/duration_test.rb
+++ b/activesupport/test/core_ext/duration_test.rb
@@ -2,8 +2,11 @@ require 'abstract_unit'
require 'active_support/inflector'
require 'active_support/time'
require 'active_support/json'
+require 'time_zone_test_helpers'
class DurationTest < ActiveSupport::TestCase
+ include TimeZoneTestHelpers
+
def test_is_a
d = 1.day
assert d.is_a?(ActiveSupport::Duration)
@@ -17,19 +20,43 @@ class DurationTest < ActiveSupport::TestCase
assert !d.is_a?(k)
end
+ def test_instance_of
+ assert 1.minute.instance_of?(Fixnum)
+ assert 2.days.instance_of?(ActiveSupport::Duration)
+ assert !3.second.instance_of?(Numeric)
+ end
+
def test_threequals
assert ActiveSupport::Duration === 1.day
assert !(ActiveSupport::Duration === 1.day.to_i)
assert !(ActiveSupport::Duration === 'foo')
- assert !(ActiveSupport::Duration === ActiveSupport::ProxyObject.new)
end
def test_equals
assert 1.day == 1.day
assert 1.day == 1.day.to_i
+ assert 1.day.to_i == 1.day
assert !(1.day == 'foo')
end
+ def test_to_s
+ assert_equal "1", 1.second.to_s
+ end
+
+ def test_eql
+ rubinius_skip "Rubinius' #eql? definition relies on #instance_of? " \
+ "which behaves oddly for the sake of backward-compatibility."
+
+ assert 1.minute.eql?(1.minute)
+ assert 1.minute.eql?(60.seconds)
+ assert 2.days.eql?(48.hours)
+ assert !1.second.eql?(1)
+ assert !1.eql?(1.second)
+ assert 1.minute.eql?(180.seconds - 2.minutes)
+ assert !1.minute.eql?(60)
+ assert !1.minute.eql?('foo')
+ end
+
def test_inspect
assert_equal '0 seconds', 0.seconds.inspect
assert_equal '1 month', 1.month.inspect
@@ -37,10 +64,21 @@ class DurationTest < ActiveSupport::TestCase
assert_equal '6 months and -2 days', (6.months - 2.days).inspect
assert_equal '10 seconds', 10.seconds.inspect
assert_equal '10 years, 2 months, and 1 day', (10.years + 2.months + 1.day).inspect
+ assert_equal '10 years, 2 months, and 1 day', (10.years + 1.month + 1.day + 1.month).inspect
+ assert_equal '10 years, 2 months, and 1 day', (1.day + 10.years + 2.months).inspect
assert_equal '7 days', 1.week.inspect
assert_equal '14 days', 1.fortnight.inspect
end
+ def test_inspect_locale
+ current_locale = I18n.default_locale
+ I18n.default_locale = :de
+ I18n.backend.store_translations(:de, { support: { array: { last_word_connector: ' und ' } } })
+ assert_equal '10 years, 1 month und 1 day', (10.years + 1.month + 1.day).inspect
+ ensure
+ I18n.default_locale = current_locale
+ end
+
def test_minus_with_duration_does_not_break_subtraction_of_date_from_date
assert_nothing_raised { Date.today - Date.today }
end
@@ -50,12 +88,10 @@ class DurationTest < ActiveSupport::TestCase
end
def test_argument_error
- 1.second.ago('')
- flunk("no exception was raised")
- rescue ArgumentError => e
+ e = assert_raise ArgumentError do
+ 1.second.ago('')
+ end
assert_equal 'expected a time or date, got ""', e.message, "ensure ArgumentError is not being raised by dependencies.rb"
- rescue Exception => e
- flunk("ArgumentError should be raised, but we got #{e.class} instead")
end
def test_fractional_weeks
@@ -68,6 +104,19 @@ class DurationTest < ActiveSupport::TestCase
assert_equal 86400 * 1.7, 1.7.days
end
+ def test_since_and_ago
+ t = Time.local(2000)
+ assert_equal t + 1, 1.second.since(t)
+ assert_equal t - 1, 1.second.ago(t)
+ end
+
+ def test_since_and_ago_without_argument
+ now = Time.now
+ assert 1.second.since >= now + 1
+ now = Time.now
+ assert 1.second.ago >= now - 1
+ end
+
def test_since_and_ago_with_fractional_days
t = Time.local(2000)
# since
@@ -93,10 +142,10 @@ class DurationTest < ActiveSupport::TestCase
with_env_tz 'US/Eastern' do
Time.stubs(:now).returns Time.local(2000)
# since
- assert_equal false, 5.seconds.since.is_a?(ActiveSupport::TimeWithZone)
+ assert_not_instance_of ActiveSupport::TimeWithZone, 5.seconds.since
assert_equal Time.local(2000,1,1,0,0,5), 5.seconds.since
# ago
- assert_equal false, 5.seconds.ago.is_a?(ActiveSupport::TimeWithZone)
+ assert_not_instance_of ActiveSupport::TimeWithZone, 5.seconds.ago
assert_equal Time.local(1999,12,31,23,59,55), 5.seconds.ago
end
end
@@ -106,11 +155,11 @@ class DurationTest < ActiveSupport::TestCase
with_env_tz 'US/Eastern' do
Time.stubs(:now).returns Time.local(2000)
# since
- assert_equal true, 5.seconds.since.is_a?(ActiveSupport::TimeWithZone)
+ assert_instance_of ActiveSupport::TimeWithZone, 5.seconds.since
assert_equal Time.utc(2000,1,1,0,0,5), 5.seconds.since.time
assert_equal 'Eastern Time (US & Canada)', 5.seconds.since.time_zone.name
# ago
- assert_equal true, 5.seconds.ago.is_a?(ActiveSupport::TimeWithZone)
+ assert_instance_of ActiveSupport::TimeWithZone, 5.seconds.ago
assert_equal Time.utc(1999,12,31,23,59,55), 5.seconds.ago.time
assert_equal 'Eastern Time (US & Canada)', 5.seconds.ago.time_zone.name
end
@@ -138,15 +187,37 @@ class DurationTest < ActiveSupport::TestCase
assert_equal counter, 60
end
+ def test_as_json
+ assert_equal 172800, 2.days.as_json
+ end
+
def test_to_json
assert_equal '172800', 2.days.to_json
end
- protected
- def with_env_tz(new_tz = 'US/Eastern')
- old_tz, ENV['TZ'] = ENV['TZ'], new_tz
- yield
- ensure
- old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ')
- end
+ def test_case_when
+ cased = case 1.day when 1.day then "ok" end
+ assert_equal cased, "ok"
+ end
+
+ def test_respond_to
+ assert_respond_to 1.day, :since
+ assert_respond_to 1.day, :zero?
+ end
+
+ def test_hash
+ assert_equal 1.minute.hash, 60.seconds.hash
+ end
+
+ def test_comparable
+ assert_equal(-1, (0.seconds <=> 1.second))
+ assert_equal(-1, (1.second <=> 1.minute))
+ assert_equal(-1, (1 <=> 1.minute))
+ assert_equal(0, (0.seconds <=> 0.seconds))
+ assert_equal(0, (0.seconds <=> 0.minutes))
+ assert_equal(0, (1.second <=> 1.second))
+ assert_equal(1, (1.second <=> 0.second))
+ assert_equal(1, (1.minute <=> 1.second))
+ assert_equal(1, (61 <=> 1.minute))
+ end
end
diff --git a/activesupport/test/core_ext/enumerable_test.rb b/activesupport/test/core_ext/enumerable_test.rb
index 6781e3c20e..346dc3d208 100644
--- a/activesupport/test/core_ext/enumerable_test.rb
+++ b/activesupport/test/core_ext/enumerable_test.rb
@@ -8,7 +8,6 @@ class SummablePayment < Payment
end
class EnumerableTests < ActiveSupport::TestCase
- Enumerator = [].each.class
class GenericEnumerable
include Enumerable
@@ -21,26 +20,6 @@ class EnumerableTests < ActiveSupport::TestCase
end
end
- def test_group_by
- names = %w(marcel sam david jeremy)
- klass = Struct.new(:name)
- objects = (1..50).map do
- klass.new names.sample
- end
-
- enum = GenericEnumerable.new(objects)
- grouped = enum.group_by { |object| object.name }
-
- grouped.each do |name, group|
- assert group.all? { |person| person.name == name }
- end
-
- assert_equal objects.uniq.map(&:name), grouped.keys
- assert({}.merge(grouped), "Could not convert ActiveSupport::OrderedHash into Hash")
- assert_equal Enumerator, enum.group_by.class
- assert_equal grouped, enum.group_by.each(&:name)
- end
-
def test_sums
enum = GenericEnumerable.new([5, 15, 10])
assert_equal 30, enum.sum
@@ -92,10 +71,14 @@ class EnumerableTests < ActiveSupport::TestCase
def test_index_by
payments = GenericEnumerable.new([ Payment.new(5), Payment.new(15), Payment.new(10) ])
assert_equal({ 5 => Payment.new(5), 15 => Payment.new(15), 10 => Payment.new(10) },
- payments.index_by { |p| p.price })
+ payments.index_by(&:price))
assert_equal Enumerator, payments.index_by.class
+ if Enumerator.method_defined? :size
+ assert_equal nil, payments.index_by.size
+ assert_equal 42, (1..42).index_by.size
+ end
assert_equal({ 5 => Payment.new(5), 15 => Payment.new(15), 10 => Payment.new(10) },
- payments.index_by.each { |p| p.price })
+ payments.index_by.each(&:price))
end
def test_many
diff --git a/activesupport/test/core_ext/file_test.rb b/activesupport/test/core_ext/file_test.rb
index 2c04e9687c..cde0132b97 100644
--- a/activesupport/test/core_ext/file_test.rb
+++ b/activesupport/test/core_ext/file_test.rb
@@ -57,6 +57,16 @@ class AtomicWriteTest < ActiveSupport::TestCase
File.unlink(file_name) rescue nil
end
+ def test_atomic_write_returns_result_from_yielded_block
+ block_return_value = File.atomic_write(file_name, Dir.pwd) do |file|
+ "Hello world!"
+ end
+
+ assert_equal "Hello world!", block_return_value
+ ensure
+ File.unlink(file_name) rescue nil
+ end
+
private
def file_name
"atomic.file"
diff --git a/activesupport/test/core_ext/hash/transform_keys_test.rb b/activesupport/test/core_ext/hash/transform_keys_test.rb
new file mode 100644
index 0000000000..a7e12117f3
--- /dev/null
+++ b/activesupport/test/core_ext/hash/transform_keys_test.rb
@@ -0,0 +1,32 @@
+require 'abstract_unit'
+require 'active_support/core_ext/hash/keys'
+
+class TransformKeysTest < ActiveSupport::TestCase
+ test "transform_keys returns a new hash with the keys computed from the block" do
+ original = { a: 'a', b: 'b' }
+ mapped = original.transform_keys { |k| "#{k}!".to_sym }
+
+ assert_equal({ a: 'a', b: 'b' }, original)
+ assert_equal({ a!: 'a', b!: 'b' }, mapped)
+ end
+
+ test "transform_keys! modifies the keys of the original" do
+ original = { a: 'a', b: 'b' }
+ mapped = original.transform_keys! { |k| "#{k}!".to_sym }
+
+ assert_equal({ a!: 'a', b!: 'b' }, original)
+ assert_same original, mapped
+ end
+
+ test "transform_keys returns an Enumerator if no block is given" do
+ original = { a: 'a', b: 'b' }
+ enumerator = original.transform_keys
+ assert_equal Enumerator, enumerator.class
+ end
+
+ test "transform_keys is chainable with Enumerable methods" do
+ original = { a: 'a', b: 'b' }
+ mapped = original.transform_keys.with_index { |k, i| [k, i].join.to_sym }
+ assert_equal({ a0: 'a', b1: 'b' }, mapped)
+ end
+end
diff --git a/activesupport/test/core_ext/hash/transform_values_test.rb b/activesupport/test/core_ext/hash/transform_values_test.rb
new file mode 100644
index 0000000000..45ed11fef7
--- /dev/null
+++ b/activesupport/test/core_ext/hash/transform_values_test.rb
@@ -0,0 +1,61 @@
+require 'abstract_unit'
+require 'active_support/core_ext/hash/indifferent_access'
+require 'active_support/core_ext/hash/transform_values'
+
+class TransformValuesTest < ActiveSupport::TestCase
+ test "transform_values returns a new hash with the values computed from the block" do
+ original = { a: 'a', b: 'b' }
+ mapped = original.transform_values { |v| v + '!' }
+
+ assert_equal({ a: 'a', b: 'b' }, original)
+ assert_equal({ a: 'a!', b: 'b!' }, mapped)
+ end
+
+ test "transform_values! modifies the values of the original" do
+ original = { a: 'a', b: 'b' }
+ mapped = original.transform_values! { |v| v + '!' }
+
+ assert_equal({ a: 'a!', b: 'b!' }, original)
+ assert_same original, mapped
+ end
+
+ test "indifferent access is still indifferent after mapping values" do
+ original = { a: 'a', b: 'b' }.with_indifferent_access
+ mapped = original.transform_values { |v| v + '!' }
+
+ assert_equal 'a!', mapped[:a]
+ assert_equal 'a!', mapped['a']
+ end
+
+ # This is to be consistent with the behavior of Ruby's built in methods
+ # (e.g. #select, #reject) as of 2.2
+ test "default values do not persist during mapping" do
+ original = Hash.new('foo')
+ original[:a] = 'a'
+ mapped = original.transform_values { |v| v + '!' }
+
+ assert_equal 'a!', mapped[:a]
+ assert_nil mapped[:b]
+ end
+
+ test "default procs do not persist after mapping" do
+ original = Hash.new { 'foo' }
+ original[:a] = 'a'
+ mapped = original.transform_values { |v| v + '!' }
+
+ assert_equal 'a!', mapped[:a]
+ assert_nil mapped[:b]
+ end
+
+ test "transform_values returns an Enumerator if no block is given" do
+ original = { a: 'a', b: 'b' }
+ enumerator = original.transform_values
+ assert_equal Enumerator, enumerator.class
+ end
+
+ test "transform_values is chainable with Enumerable methods" do
+ original = { a: 'a', b: 'b' }
+ mapped = original.transform_values.with_index { |v, i| [v, i].join }
+ assert_equal({ a: 'a0', b: 'b1' }, mapped)
+ end
+end
diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb
index 2d0c56bef5..e10bee5e00 100644
--- a/activesupport/test/core_ext/hash_ext_test.rb
+++ b/activesupport/test/core_ext/hash_ext_test.rb
@@ -23,6 +23,16 @@ class HashExtTest < ActiveSupport::TestCase
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 } } }
@@ -36,6 +46,10 @@ class HashExtTest < ActiveSupport::TestCase
@nested_illegal_symbols = { [] => { [] => 3} }
@upcase_strings = { 'A' => 1, 'B' => 2 }
@nested_upcase_strings = { 'A' => { 'B' => { 'C' => 3 } } }
+ @string_array_of_hashes = { 'a' => [ { 'b' => 2 }, { 'c' => 3 }, 4 ] }
+ @symbol_array_of_hashes = { :a => [ { :b => 2 }, { :c => 3 }, 4 ] }
+ @mixed_array_of_hashes = { :a => [ { :b => 2 }, { 'c' => 3 }, 4 ] }
+ @upcase_array_of_hashes = { 'A' => [ { 'B' => 2 }, { 'C' => 3 }, 4 ] }
end
def test_methods
@@ -54,6 +68,10 @@ class HashExtTest < ActiveSupport::TestCase
assert_respond_to h, :deep_stringify_keys!
assert_respond_to h, :to_options
assert_respond_to h, :to_options!
+ assert_respond_to h, :compact
+ assert_respond_to h, :compact!
+ assert_respond_to h, :except
+ assert_respond_to h, :except!
end
def test_transform_keys
@@ -72,6 +90,9 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal @nested_upcase_strings, @nested_symbols.deep_transform_keys{ |key| key.to_s.upcase }
assert_equal @nested_upcase_strings, @nested_strings.deep_transform_keys{ |key| key.to_s.upcase }
assert_equal @nested_upcase_strings, @nested_mixed.deep_transform_keys{ |key| key.to_s.upcase }
+ assert_equal @upcase_array_of_hashes, @string_array_of_hashes.deep_transform_keys{ |key| key.to_s.upcase }
+ assert_equal @upcase_array_of_hashes, @symbol_array_of_hashes.deep_transform_keys{ |key| key.to_s.upcase }
+ assert_equal @upcase_array_of_hashes, @mixed_array_of_hashes.deep_transform_keys{ |key| key.to_s.upcase }
end
def test_deep_transform_keys_not_mutates
@@ -97,6 +118,9 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal @nested_upcase_strings, @nested_symbols.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase }
assert_equal @nested_upcase_strings, @nested_strings.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase }
assert_equal @nested_upcase_strings, @nested_mixed.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase }
+ assert_equal @upcase_array_of_hashes, @string_array_of_hashes.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase }
+ assert_equal @upcase_array_of_hashes, @symbol_array_of_hashes.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase }
+ assert_equal @upcase_array_of_hashes, @mixed_array_of_hashes.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase }
end
def test_deep_transform_keys_with_bang_mutates
@@ -122,6 +146,9 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal @nested_symbols, @nested_symbols.deep_symbolize_keys
assert_equal @nested_symbols, @nested_strings.deep_symbolize_keys
assert_equal @nested_symbols, @nested_mixed.deep_symbolize_keys
+ assert_equal @symbol_array_of_hashes, @string_array_of_hashes.deep_symbolize_keys
+ assert_equal @symbol_array_of_hashes, @symbol_array_of_hashes.deep_symbolize_keys
+ assert_equal @symbol_array_of_hashes, @mixed_array_of_hashes.deep_symbolize_keys
end
def test_deep_symbolize_keys_not_mutates
@@ -147,6 +174,9 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal @nested_symbols, @nested_symbols.deep_dup.deep_symbolize_keys!
assert_equal @nested_symbols, @nested_strings.deep_dup.deep_symbolize_keys!
assert_equal @nested_symbols, @nested_mixed.deep_dup.deep_symbolize_keys!
+ assert_equal @symbol_array_of_hashes, @string_array_of_hashes.deep_dup.deep_symbolize_keys!
+ assert_equal @symbol_array_of_hashes, @symbol_array_of_hashes.deep_dup.deep_symbolize_keys!
+ assert_equal @symbol_array_of_hashes, @mixed_array_of_hashes.deep_dup.deep_symbolize_keys!
end
def test_deep_symbolize_keys_with_bang_mutates
@@ -192,6 +222,9 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal @nested_strings, @nested_symbols.deep_stringify_keys
assert_equal @nested_strings, @nested_strings.deep_stringify_keys
assert_equal @nested_strings, @nested_mixed.deep_stringify_keys
+ assert_equal @string_array_of_hashes, @string_array_of_hashes.deep_stringify_keys
+ assert_equal @string_array_of_hashes, @symbol_array_of_hashes.deep_stringify_keys
+ assert_equal @string_array_of_hashes, @mixed_array_of_hashes.deep_stringify_keys
end
def test_deep_stringify_keys_not_mutates
@@ -217,6 +250,9 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal @nested_strings, @nested_symbols.deep_dup.deep_stringify_keys!
assert_equal @nested_strings, @nested_strings.deep_dup.deep_stringify_keys!
assert_equal @nested_strings, @nested_mixed.deep_dup.deep_stringify_keys!
+ assert_equal @string_array_of_hashes, @string_array_of_hashes.deep_dup.deep_stringify_keys!
+ assert_equal @string_array_of_hashes, @symbol_array_of_hashes.deep_dup.deep_stringify_keys!
+ assert_equal @string_array_of_hashes, @mixed_array_of_hashes.deep_dup.deep_stringify_keys!
end
def test_deep_stringify_keys_with_bang_mutates
@@ -329,7 +365,7 @@ class HashExtTest < ActiveSupport::TestCase
:member? => true }
hashes.each do |name, hash|
- method_map.sort_by { |m| m.to_s }.each do |meth, expected|
+ 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),
@@ -409,6 +445,12 @@ class HashExtTest < ActiveSupport::TestCase
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 hash['a'], 1
+ end
+
def test_indifferent_merging
hash = HashWithIndifferentAccess.new
hash[:a] = 'failure'
@@ -428,6 +470,12 @@ class HashExtTest < ActiveSupport::TestCase
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 merged['a'], 1
+ end
+
def test_indifferent_replace
hash = HashWithIndifferentAccess.new
hash[:a] = 42
@@ -440,6 +488,18 @@ class HashExtTest < ActiveSupport::TestCase
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
@@ -526,6 +586,8 @@ class HashExtTest < ActiveSupport::TestCase
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)
@@ -599,7 +661,15 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal 1, h['first']
end
- def test_indifferent_subhashes
+ 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"}}
@@ -623,11 +693,31 @@ class HashExtTest < ActiveSupport::TestCase
{ :failure => "stuff", :funny => "business" }.assert_valid_keys([ :failure, :funny ])
{ :failure => "stuff", :funny => "business" }.assert_valid_keys(:failure, :funny)
end
+ # not all valid keys are required to be present
+ assert_nothing_raised do
+ { :failure => "stuff", :funny => "business" }.assert_valid_keys([ :failure, :funny, :sunny ])
+ { :failure => "stuff", :funny => "business" }.assert_valid_keys(:failure, :funny, :sunny)
+ end
- assert_raise(ArgumentError, "Unknown key: failore") do
+ exception = assert_raise ArgumentError do
{ :failore => "stuff", :funny => "business" }.assert_valid_keys([ :failure, :funny ])
+ end
+ assert_equal "Unknown key: :failore. Valid keys are: :failure, :funny", exception.message
+
+ exception = assert_raise ArgumentError do
{ :failore => "stuff", :funny => "business" }.assert_valid_keys(:failure, :funny)
end
+ assert_equal "Unknown key: :failore. Valid keys are: :failure, :funny", exception.message
+
+ exception = assert_raise ArgumentError do
+ { :failore => "stuff", :funny => "business" }.assert_valid_keys([ :failure ])
+ end
+ assert_equal "Unknown key: :failore. Valid keys are: :failure", exception.message
+
+ exception = assert_raise ArgumentError do
+ { :failore => "stuff", :funny => "business" }.assert_valid_keys(:failure)
+ end
+ assert_equal "Unknown key: :failore. Valid keys are: :failure", exception.message
end
def test_assorted_keys_not_stringified
@@ -656,6 +746,16 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal expected, hash_1
end
+ def test_deep_merge_with_falsey_values
+ hash_1 = { e: false }
+ hash_2 = { e: 'e' }
+ expected = { e: [:e, false, 'e'] }
+ assert_equal(expected, hash_1.deep_merge(hash_2) { |k, o, n| [k, o, n] })
+
+ hash_1.deep_merge!(hash_2) { |k, o, n| [k, o, n] }
+ 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" } } })
@@ -781,6 +881,24 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal 'bender', slice['login']
end
+ def test_slice_bang_does_not_override_default
+ hash = Hash.new(0)
+ hash.update(a: 1, b: 2)
+
+ hash.slice!(:a)
+
+ assert_equal 0, hash[:c]
+ end
+
+ def test_slice_bang_does_not_override_default_proc
+ hash = Hash.new { |h, k| h[k] = [] }
+ hash.update(a: 1, b: 2)
+
+ hash.slice!(:a)
+
+ assert_equal [], hash[:c]
+ end
+
def test_extract
original = {:a => 1, :b => 2, :c => 3, :d => 4}
expected = {:a => 1, :b => 2}
@@ -828,13 +946,19 @@ class HashExtTest < ActiveSupport::TestCase
def test_except_with_more_than_one_argument
original = { :a => 'x', :b => 'y', :c => 10 }
expected = { :a => 'x' }
+
assert_equal expected, original.except(:b, :c)
+
+ assert_equal expected, original.except!(:b, :c)
+ assert_equal expected, original
end
def test_except_with_original_frozen
original = { :a => 'x', :b => 'y' }
original.freeze
assert_nothing_raised { original.except(:a) }
+
+ assert_raise(RuntimeError) { original.except!(:a) }
end
def test_except_with_mocha_expectation_on_original
@@ -842,6 +966,38 @@ class HashExtTest < ActiveSupport::TestCase
original.expects(:delete).never
original.except(:a)
end
+
+ def test_compact
+ hash_contain_nil_value = @symbols.merge(z: nil)
+ hash_with_only_nil_values = { a: nil, b: nil }
+
+ h = hash_contain_nil_value.dup
+ assert_equal(@symbols, h.compact)
+ assert_equal(hash_contain_nil_value, h)
+
+ h = hash_with_only_nil_values.dup
+ assert_equal({}, h.compact)
+ assert_equal(hash_with_only_nil_values, h)
+ end
+
+ def test_compact!
+ hash_contain_nil_value = @symbols.merge(z: nil)
+ hash_with_only_nil_values = { a: nil, b: nil }
+
+ h = hash_contain_nil_value.dup
+ assert_equal(@symbols, h.compact!)
+ assert_equal(@symbols, h)
+
+ h = hash_with_only_nil_values.dup
+ assert_equal({}, h.compact!)
+ assert_equal({}, 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
end
class IWriteMyOwnXML
@@ -1346,12 +1502,32 @@ class HashToXmlTest < ActiveSupport::TestCase
end
end
+ def test_from_xml_array_one
+ expected = { 'numbers' => { 'type' => 'Array', 'value' => '1' }}
+ assert_equal expected, Hash.from_xml('<numbers type="Array"><value>1</value></numbers>')
+ end
+
+ def test_from_xml_array_many
+ expected = { 'numbers' => { 'type' => 'Array', 'value' => [ '1', '2' ] }}
+ assert_equal expected, Hash.from_xml('<numbers type="Array"><value>1</value><value>2</value></numbers>')
+ end
+
def test_from_trusted_xml_allows_symbol_and_yaml_types
expected = { 'product' => { 'name' => :value }}
assert_equal expected, Hash.from_trusted_xml('<product><name type="symbol">value</name></product>')
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_use_default_proc_if_no_key_is_supplied
+ hash_wia = HashWithIndifferentAccess.new { 1 + 2 }
+ assert_equal 3, 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]
@@ -1373,12 +1549,31 @@ class HashToXmlTest < ActiveSupport::TestCase
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/kernel/concern_test.rb b/activesupport/test/core_ext/kernel/concern_test.rb
new file mode 100644
index 0000000000..478a00d2d2
--- /dev/null
+++ b/activesupport/test/core_ext/kernel/concern_test.rb
@@ -0,0 +1,13 @@
+require 'abstract_unit'
+require 'active_support/core_ext/kernel/concern'
+
+class KernelConcernTest < ActiveSupport::TestCase
+ def test_may_be_defined_at_toplevel
+ mod = ::TOPLEVEL_BINDING.eval 'concern(:ToplevelConcern) { }'
+ assert_equal mod, ::ToplevelConcern
+ assert_kind_of ActiveSupport::Concern, ::ToplevelConcern
+ assert_not Object.ancestors.include?(::ToplevelConcern), mod.ancestors.inspect
+ ensure
+ Object.send :remove_const, :ToplevelConcern
+ end
+end
diff --git a/activesupport/test/core_ext/kernel_test.rb b/activesupport/test/core_ext/kernel_test.rb
index b8951de402..503e6595cb 100644
--- a/activesupport/test/core_ext/kernel_test.rb
+++ b/activesupport/test/core_ext/kernel_test.rb
@@ -15,7 +15,6 @@ class KernelTest < ActiveSupport::TestCase
assert_equal old_verbose, $VERBOSE
end
-
def test_enable_warnings
enable_warnings { assert_equal true, $VERBOSE }
assert_equal 1234, enable_warnings { 1234 }
@@ -29,43 +28,11 @@ class KernelTest < ActiveSupport::TestCase
assert_equal old_verbose, $VERBOSE
end
-
- def test_silence_stderr
- old_stderr_position = STDERR.tell
- silence_stderr { STDERR.puts 'hello world' }
- assert_equal old_stderr_position, STDERR.tell
- rescue Errno::ESPIPE
- # Skip if we can't STDERR.tell
- end
-
- def test_quietly
- old_stdout_position, old_stderr_position = STDOUT.tell, STDERR.tell
- quietly do
- puts 'see me, feel me'
- STDERR.puts 'touch me, heal me'
- end
- assert_equal old_stdout_position, STDOUT.tell
- assert_equal old_stderr_position, STDERR.tell
- rescue Errno::ESPIPE
- # Skip if we can't STDERR.tell
- end
-
- def test_silence_stderr_with_return_value
- assert_equal 1, silence_stderr { 1 }
- end
-
def test_class_eval
o = Object.new
class << o; @x = 1; end
assert_equal 1, o.class_eval { @x }
end
-
- def test_capture
- assert_equal 'STDERR', capture(:stderr) { $stderr.print 'STDERR' }
- assert_equal 'STDOUT', capture(:stdout) { print 'STDOUT' }
- assert_equal "STDERR\n", capture(:stderr) { system('echo STDERR 1>&2') }
- assert_equal "STDOUT\n", capture(:stdout) { system('echo STDOUT') }
- end
end
class KernelSuppressTest < ActiveSupport::TestCase
@@ -98,27 +65,3 @@ class MockStdErr
puts(message)
end
end
-
-class KernelDebuggerTest < ActiveSupport::TestCase
- def test_debugger_not_available_message_to_stderr
- old_stderr = $stderr
- $stderr = MockStdErr.new
- debugger
- assert_match(/Debugger requested/, $stderr.output.first)
- ensure
- $stderr = old_stderr
- end
-
- def test_debugger_not_available_message_to_rails_logger
- rails = Class.new do
- def self.logger
- @logger ||= MockStdErr.new
- end
- end
- Object.const_set(:Rails, rails)
- debugger
- assert_match(/Debugger requested/, rails.logger.output.first)
- ensure
- Object.send(:remove_const, :Rails)
- end
-end
diff --git a/activesupport/test/core_ext/load_error_test.rb b/activesupport/test/core_ext/load_error_test.rb
index 31863d0aca..b2a75a2bcc 100644
--- a/activesupport/test/core_ext/load_error_test.rb
+++ b/activesupport/test/core_ext/load_error_test.rb
@@ -1,17 +1,11 @@
require 'abstract_unit'
require 'active_support/core_ext/load_error'
+
class TestMissingSourceFile < ActiveSupport::TestCase
- def test_with_require
- assert_raise(MissingSourceFile) { require 'no_this_file_don\'t_exist' }
- end
- def test_with_load
- assert_raise(MissingSourceFile) { load 'nor_does_this_one' }
- end
- def test_path
- begin load 'nor/this/one.rb'
- rescue MissingSourceFile => e
- assert_equal 'nor/this/one.rb', e.path
+ def test_it_is_deprecated
+ assert_deprecated do
+ MissingSourceFile.new
end
end
end
@@ -29,4 +23,4 @@ class TestLoadError < ActiveSupport::TestCase
assert_equal 'nor/this/one.rb', e.path
end
end
-end \ No newline at end of file
+end
diff --git a/activesupport/test/core_ext/module/attribute_accessor_test.rb b/activesupport/test/core_ext/module/attribute_accessor_test.rb
index a577f90bdd..48f3cc579f 100644
--- a/activesupport/test/core_ext/module/attribute_accessor_test.rb
+++ b/activesupport/test/core_ext/module/attribute_accessor_test.rb
@@ -8,6 +8,11 @@ class ModuleAttributeAccessorTest < ActiveSupport::TestCase
mattr_accessor :bar, :instance_writer => false
mattr_reader :shaq, :instance_reader => false
mattr_accessor :camp, :instance_accessor => false
+
+ cattr_accessor(:defa) { 'default_accessor_value' }
+ cattr_reader(:defr) { 'default_reader_value' }
+ cattr_writer(:defw) { 'default_writer_value' }
+ cattr_accessor(:quux) { :quux }
end
@class = Class.new
@class.instance_eval { include m }
@@ -27,6 +32,11 @@ class ModuleAttributeAccessorTest < ActiveSupport::TestCase
assert_equal :test2, @module.foo
end
+ def test_cattr_accessor_default_value
+ assert_equal :quux, @module.quux
+ assert_equal :quux, @object.quux
+ end
+
def test_should_not_create_instance_writer
assert_respond_to @module, :foo
assert_respond_to @module, :foo=
@@ -46,16 +56,24 @@ class ModuleAttributeAccessorTest < ActiveSupport::TestCase
end
def test_should_raise_name_error_if_attribute_name_is_invalid
- assert_raises NameError do
+ exception = assert_raises NameError do
Class.new do
- mattr_reader "invalid attribute name"
+ cattr_reader "1nvalid"
end
end
+ assert_equal "invalid attribute name: 1nvalid", exception.message
- assert_raises NameError do
+ exception = assert_raises NameError do
Class.new do
- mattr_writer "invalid attribute name"
+ cattr_writer "1nvalid"
end
end
+ assert_equal "invalid attribute name: 1nvalid", exception.message
+ end
+
+ def test_should_use_default_value_if_block_passed
+ assert_equal 'default_accessor_value', @module.defa
+ assert_equal 'default_reader_value', @module.defr
+ assert_equal 'default_writer_value', @module.class_variable_get('@@defw')
end
end
diff --git a/activesupport/test/core_ext/module/concerning_test.rb b/activesupport/test/core_ext/module/concerning_test.rb
new file mode 100644
index 0000000000..07d860b71c
--- /dev/null
+++ b/activesupport/test/core_ext/module/concerning_test.rb
@@ -0,0 +1,65 @@
+require 'abstract_unit'
+require 'active_support/core_ext/module/concerning'
+
+class ModuleConcerningTest < ActiveSupport::TestCase
+ def test_concerning_declares_a_concern_and_includes_it_immediately
+ klass = Class.new { concerning(:Foo) { } }
+ assert klass.ancestors.include?(klass::Foo), klass.ancestors.inspect
+ end
+end
+
+class ModuleConcernTest < ActiveSupport::TestCase
+ def test_concern_creates_a_module_extended_with_active_support_concern
+ klass = Class.new do
+ concern :Baz do
+ included { @foo = 1 }
+ def should_be_public; end
+ end
+ end
+
+ # Declares a concern but doesn't include it
+ assert klass.const_defined?(:Baz, false)
+ assert !ModuleConcernTest.const_defined?(:Baz)
+ assert_kind_of ActiveSupport::Concern, klass::Baz
+ assert !klass.ancestors.include?(klass::Baz), klass.ancestors.inspect
+
+ # Public method visibility by default
+ assert klass::Baz.public_instance_methods.map(&:to_s).include?('should_be_public')
+
+ # Calls included hook
+ assert_equal 1, Class.new { include klass::Baz }.instance_variable_get('@foo')
+ end
+
+ class Foo
+ concerning :Bar do
+ module ClassMethods
+ def will_be_orphaned; end
+ end
+
+ const_set :ClassMethods, Module.new {
+ def hacked_on; end
+ }
+
+ # Doesn't overwrite existing ClassMethods module.
+ class_methods do
+ def nicer_dsl; end
+ end
+
+ # Doesn't overwrite previous class_methods definitions.
+ class_methods do
+ def doesnt_clobber; end
+ end
+ end
+ end
+
+ def test_using_class_methods_blocks_instead_of_ClassMethods_module
+ assert !Foo.respond_to?(:will_be_orphaned)
+ assert Foo.respond_to?(:hacked_on)
+ assert Foo.respond_to?(:nicer_dsl)
+ assert Foo.respond_to?(:doesnt_clobber)
+
+ # Orphan in Foo::ClassMethods, not Bar::ClassMethods.
+ assert Foo.const_defined?(:ClassMethods)
+ assert Foo::ClassMethods.method_defined?(:will_be_orphaned)
+ end
+end
diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb
index 283b13ff8b..3c49c4d14f 100644
--- a/activesupport/test/core_ext/module_test.rb
+++ b/activesupport/test/core_ext/module_test.rb
@@ -12,12 +12,6 @@ class Ab
Constant3 = "Goodbye World"
end
-module Xy
- class Bc
- include One
- end
-end
-
module Yz
module Zy
class Cd
@@ -62,8 +56,14 @@ Developer = Struct.new(:client) do
delegate :name, :to => :client, :prefix => nil
end
+Event = Struct.new(:case) do
+ delegate :foo, :to => :case
+end
+
Tester = Struct.new(:client) do
delegate :name, :to => :client, :prefix => false
+
+ def foo; 1; end
end
Product = Struct.new(:name) do
@@ -78,7 +78,7 @@ Product = Struct.new(:name) do
def type
@type ||= begin
- nil.type_name
+ :thing_without_same_method_name_as_delegated.name
end
end
end
@@ -251,6 +251,16 @@ class ModuleTest < ActiveSupport::TestCase
end
end
+ def test_delegation_line_number
+ _, line = Someone.instance_method(:foo).source_location
+ assert_equal Someone::FAILED_DELEGATE_LINE, line
+ end
+
+ def test_delegate_line_with_nil
+ _, line = Someone.instance_method(:bar).source_location
+ assert_equal Someone::FAILED_DELEGATE_LINE_2, line
+ end
+
def test_delegation_exception_backtrace
someone = Someone.new("foo", "bar")
someone.foo
@@ -491,4 +501,9 @@ class MethodAliasingTest < ActiveSupport::TestCase
assert_equal 'duck_with_orange', @instance.duck
assert FooClassWithBarMethod.public_method_defined?(:duck)
end
+
+ def test_delegate_with_case
+ event = Event.new(Tester.new)
+ assert_equal 1, event.foo
+ end
end
diff --git a/activesupport/test/core_ext/name_error_test.rb b/activesupport/test/core_ext/name_error_test.rb
index 03ce09f22a..7525f80cf0 100644
--- a/activesupport/test/core_ext/name_error_test.rb
+++ b/activesupport/test/core_ext/name_error_test.rb
@@ -3,18 +3,18 @@ require 'active_support/core_ext/name_error'
class NameErrorTest < ActiveSupport::TestCase
def test_name_error_should_set_missing_name
- SomeNameThatNobodyWillUse____Really ? 1 : 0
- flunk "?!?!"
- rescue NameError => exc
+ exc = assert_raise NameError do
+ SomeNameThatNobodyWillUse____Really ? 1 : 0
+ end
assert_equal "NameErrorTest::SomeNameThatNobodyWillUse____Really", exc.missing_name
assert exc.missing_name?(:SomeNameThatNobodyWillUse____Really)
assert exc.missing_name?("NameErrorTest::SomeNameThatNobodyWillUse____Really")
end
def test_missing_method_should_ignore_missing_name
- some_method_that_does_not_exist
- flunk "?!?!"
- rescue NameError => exc
+ exc = assert_raise NameError do
+ some_method_that_does_not_exist
+ end
assert !exc.missing_name?(:Foo)
assert_nil exc.missing_name
end
diff --git a/activesupport/test/core_ext/numeric_ext_test.rb b/activesupport/test/core_ext/numeric_ext_test.rb
index 1da72eb17d..b82448458d 100644
--- a/activesupport/test/core_ext/numeric_ext_test.rb
+++ b/activesupport/test/core_ext/numeric_ext_test.rb
@@ -22,23 +22,6 @@ class NumericExtTimeAndDateTimeTest < ActiveSupport::TestCase
end
end
- def test_intervals
- @seconds.values.each do |seconds|
- assert_equal seconds.since(@now), @now + seconds
- assert_equal seconds.until(@now), @now - seconds
- end
- end
-
- # Test intervals based from Time.now
- def test_now
- @seconds.values.each do |seconds|
- now = Time.now
- assert seconds.ago >= now - seconds
- now = Time.now
- assert seconds.from_now >= now + seconds
- end
- end
-
def test_irregular_durations
assert_equal @now.advance(:days => 3000), 3000.days.since(@now)
assert_equal @now.advance(:months => 1), 1.month.since(@now)
@@ -78,54 +61,16 @@ class NumericExtTimeAndDateTimeTest < ActiveSupport::TestCase
end
def test_duration_after_conversion_is_no_longer_accurate
- assert_equal 30.days.to_i.since(@now), 1.month.to_i.since(@now)
- assert_equal 365.25.days.to_f.since(@now), 1.year.to_f.since(@now)
- assert_equal 30.days.to_i.since(@dtnow), 1.month.to_i.since(@dtnow)
- assert_equal 365.25.days.to_f.since(@dtnow), 1.year.to_f.since(@dtnow)
+ assert_equal 30.days.to_i.seconds.since(@now), 1.month.to_i.seconds.since(@now)
+ assert_equal 365.25.days.to_f.seconds.since(@now), 1.year.to_f.seconds.since(@now)
+ assert_equal 30.days.to_i.seconds.since(@dtnow), 1.month.to_i.seconds.since(@dtnow)
+ assert_equal 365.25.days.to_f.seconds.since(@dtnow), 1.year.to_f.seconds.since(@dtnow)
end
def test_add_one_year_to_leap_day
assert_equal Time.utc(2005,2,28,15,15,10), Time.utc(2004,2,29,15,15,10) + 1.year
assert_equal DateTime.civil(2005,2,28,15,15,10), DateTime.civil(2004,2,29,15,15,10) + 1.year
end
-
- def test_since_and_ago_anchored_to_time_now_when_time_zone_is_not_set
- Time.zone = nil
- with_env_tz 'US/Eastern' do
- Time.stubs(:now).returns Time.local(2000)
- # since
- assert_equal false, 5.since.is_a?(ActiveSupport::TimeWithZone)
- assert_equal Time.local(2000,1,1,0,0,5), 5.since
- # ago
- assert_equal false, 5.ago.is_a?(ActiveSupport::TimeWithZone)
- assert_equal Time.local(1999,12,31,23,59,55), 5.ago
- end
- end
-
- def test_since_and_ago_anchored_to_time_zone_now_when_time_zone_is_set
- Time.zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
- with_env_tz 'US/Eastern' do
- Time.stubs(:now).returns Time.local(2000)
- # since
- assert_equal true, 5.since.is_a?(ActiveSupport::TimeWithZone)
- assert_equal Time.utc(2000,1,1,0,0,5), 5.since.time
- assert_equal 'Eastern Time (US & Canada)', 5.since.time_zone.name
- # ago
- assert_equal true, 5.ago.is_a?(ActiveSupport::TimeWithZone)
- assert_equal Time.utc(1999,12,31,23,59,55), 5.ago.time
- assert_equal 'Eastern Time (US & Canada)', 5.ago.time_zone.name
- end
- ensure
- Time.zone = nil
- end
-
- protected
- def with_env_tz(new_tz = 'US/Eastern')
- old_tz, ENV['TZ'] = ENV['TZ'], new_tz
- yield
- ensure
- old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ')
- end
end
class NumericExtDateTest < ActiveSupport::TestCase
@@ -440,4 +385,8 @@ class NumericExtFormattingTest < ActiveSupport::TestCase
assert_equal BigDecimal, BigDecimal("1000010").class
assert_equal '1 Million', BigDecimal("1000010").to_s(:human)
end
+
+ def test_in_milliseconds
+ assert_equal 10_000, 10.seconds.in_milliseconds
+ end
end
diff --git a/activesupport/test/core_ext/object/acts_like_test.rb b/activesupport/test/core_ext/object/acts_like_test.rb
new file mode 100644
index 0000000000..e68b1d23cb
--- /dev/null
+++ b/activesupport/test/core_ext/object/acts_like_test.rb
@@ -0,0 +1,33 @@
+require 'abstract_unit'
+require 'active_support/core_ext/object'
+
+class ObjectTests < ActiveSupport::TestCase
+ class DuckTime
+ def acts_like_time?
+ true
+ end
+ end
+
+ def test_duck_typing
+ object = Object.new
+ time = Time.now
+ date = Date.today
+ dt = DateTime.new
+ duck = DuckTime.new
+
+ assert !object.acts_like?(:time)
+ assert !object.acts_like?(:date)
+
+ assert time.acts_like?(:time)
+ assert !time.acts_like?(:date)
+
+ assert !date.acts_like?(:time)
+ assert date.acts_like?(:date)
+
+ assert dt.acts_like?(:time)
+ assert dt.acts_like?(:date)
+
+ assert duck.acts_like?(:time)
+ assert !duck.acts_like?(:date)
+ end
+end
diff --git a/activesupport/test/core_ext/blank_test.rb b/activesupport/test/core_ext/object/blank_test.rb
index a68c074777..8a5e385dd7 100644
--- a/activesupport/test/core_ext/blank_test.rb
+++ b/activesupport/test/core_ext/object/blank_test.rb
@@ -1,20 +1,31 @@
-# encoding: utf-8
require 'abstract_unit'
require 'active_support/core_ext/object/blank'
class BlankTest < ActiveSupport::TestCase
- BLANK = [ EmptyTrue.new, nil, false, '', ' ', " \n\t \r ", ' ', [], {} ]
+ class EmptyTrue
+ def empty?
+ 0
+ end
+ end
+
+ class EmptyFalse
+ def empty?
+ nil
+ end
+ end
+
+ BLANK = [ EmptyTrue.new, nil, false, '', ' ', " \n\t \r ", ' ', "\u00a0", [], {} ]
NOT = [ EmptyFalse.new, Object.new, true, 0, 1, 'a', [nil], { nil => 0 } ]
def test_blank
- BLANK.each { |v| assert v.blank?, "#{v.inspect} should be blank" }
- NOT.each { |v| assert !v.blank?, "#{v.inspect} should not be blank" }
+ BLANK.each { |v| assert_equal true, v.blank?, "#{v.inspect} should be blank" }
+ NOT.each { |v| assert_equal false, v.blank?, "#{v.inspect} should not be blank" }
end
def test_present
- BLANK.each { |v| assert !v.present?, "#{v.inspect} should not be present" }
- NOT.each { |v| assert v.present?, "#{v.inspect} should be present" }
+ BLANK.each { |v| assert_equal false, v.present?, "#{v.inspect} should not be present" }
+ NOT.each { |v| assert_equal true, v.present?, "#{v.inspect} should be present" }
end
def test_presence
diff --git a/activesupport/test/core_ext/deep_dup_test.rb b/activesupport/test/core_ext/object/deep_dup_test.rb
index 91d558dbb5..91d558dbb5 100644
--- a/activesupport/test/core_ext/deep_dup_test.rb
+++ b/activesupport/test/core_ext/object/deep_dup_test.rb
diff --git a/activesupport/test/core_ext/object/duplicable_test.rb b/activesupport/test/core_ext/object/duplicable_test.rb
new file mode 100644
index 0000000000..d37f4bd0d8
--- /dev/null
+++ b/activesupport/test/core_ext/object/duplicable_test.rb
@@ -0,0 +1,22 @@
+require 'abstract_unit'
+require 'bigdecimal'
+require 'active_support/core_ext/object/duplicable'
+require 'active_support/core_ext/numeric/time'
+
+class DuplicableTest < ActiveSupport::TestCase
+ RAISE_DUP = [nil, false, true, :symbol, 1, 2.3, method(:puts)]
+ ALLOW_DUP = ['1', Object.new, /foo/, [], {}, Time.now, Class.new, Module.new]
+ ALLOW_DUP << BigDecimal.new('4.56')
+
+ def test_duplicable
+ RAISE_DUP.each do |v|
+ assert !v.duplicable?
+ assert_raises(TypeError, v.class.name) { v.dup }
+ end
+
+ ALLOW_DUP.each do |v|
+ assert v.duplicable?, "#{ v.class } should be duplicable"
+ assert_nothing_raised { v.dup }
+ end
+ end
+end
diff --git a/activesupport/test/core_ext/object/inclusion_test.rb b/activesupport/test/core_ext/object/inclusion_test.rb
index 478706eeae..32d512eca3 100644
--- a/activesupport/test/core_ext/object/inclusion_test.rb
+++ b/activesupport/test/core_ext/object/inclusion_test.rb
@@ -37,14 +37,23 @@ class InTest < ActiveSupport::TestCase
end
class C < B
end
+ class D
+ end
def test_in_module
assert A.in?(B)
assert A.in?(C)
assert !A.in?(A)
+ assert !A.in?(D)
end
def test_no_method_catching
assert_raise(ArgumentError) { 1.in?(1) }
end
+
+ def test_presence_in
+ assert_equal "stuff", "stuff".presence_in(%w( lots of stuff ))
+ assert_nil "stuff".presence_in(%w( lots of crap ))
+ assert_raise(ArgumentError) { 1.presence_in(1) }
+ end
end
diff --git a/activesupport/test/core_ext/object/instance_variables_test.rb b/activesupport/test/core_ext/object/instance_variables_test.rb
new file mode 100644
index 0000000000..9f4c5dc4f1
--- /dev/null
+++ b/activesupport/test/core_ext/object/instance_variables_test.rb
@@ -0,0 +1,31 @@
+require 'abstract_unit'
+require 'active_support/core_ext/object'
+
+class ObjectInstanceVariableTest < ActiveSupport::TestCase
+ def setup
+ @source, @dest = Object.new, Object.new
+ @source.instance_variable_set(:@bar, 'bar')
+ @source.instance_variable_set(:@baz, 'baz')
+ end
+
+ def test_instance_variable_names
+ assert_equal %w(@bar @baz), @source.instance_variable_names.sort
+ end
+
+ def test_instance_values
+ assert_equal({'bar' => 'bar', 'baz' => 'baz'}, @source.instance_values)
+ end
+
+ def test_instance_exec_passes_arguments_to_block
+ assert_equal %w(hello goodbye), 'hello'.instance_exec('goodbye') { |v| [self, v] }
+ end
+
+ def test_instance_exec_with_frozen_obj
+ assert_equal %w(olleh goodbye), 'hello'.freeze.instance_exec('goodbye') { |v| [reverse, v] }
+ end
+
+ def test_instance_exec_nested
+ assert_equal %w(goodbye olleh bar), 'hello'.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
new file mode 100644
index 0000000000..2f7ea3a497
--- /dev/null
+++ b/activesupport/test/core_ext/object/json_cherry_pick_test.rb
@@ -0,0 +1,42 @@
+require 'abstract_unit'
+
+# These test cases were added to test that cherry-picking the json extensions
+# works correctly, primarily for dependencies problems reported in #16131. They
+# need to be executed in isolation to reproduce the scenario correctly, because
+# other test cases might have already loaded additional dependencies.
+
+class JsonCherryPickTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::Isolation
+
+ def test_time_as_json
+ require_or_skip 'active_support/core_ext/object/json'
+
+ expected = Time.new(2004, 7, 25)
+ actual = Time.parse(expected.as_json)
+
+ assert_equal expected, actual
+ end
+
+ def test_date_as_json
+ require_or_skip 'active_support/core_ext/object/json'
+
+ expected = Date.new(2004, 7, 25)
+ actual = Date.parse(expected.as_json)
+
+ assert_equal expected, actual
+ end
+
+ def test_datetime_as_json
+ require_or_skip 'active_support/core_ext/object/json'
+
+ expected = DateTime.new(2004, 7, 25)
+ actual = DateTime.parse(expected.as_json)
+
+ assert_equal expected, actual
+ end
+
+ private
+ def require_or_skip(file)
+ require(file) || skip("'#{file}' was already loaded")
+ end
+end
diff --git a/activesupport/test/core_ext/object/to_param_test.rb b/activesupport/test/core_ext/object/to_param_test.rb
index bd7c6c422a..30a7557dc2 100644
--- a/activesupport/test/core_ext/object/to_param_test.rb
+++ b/activesupport/test/core_ext/object/to_param_test.rb
@@ -2,6 +2,12 @@ require 'abstract_unit'
require 'active_support/core_ext/object/to_param'
class ToParamTest < ActiveSupport::TestCase
+ class CustomString < String
+ def to_param
+ "custom-#{ self }"
+ end
+ end
+
def test_object
foo = Object.new
def foo.to_s; 'foo' end
@@ -16,4 +22,16 @@ class ToParamTest < ActiveSupport::TestCase
assert_equal true, true.to_param
assert_equal false, false.to_param
end
+
+ def test_array
+ # Empty Array
+ assert_equal '', [].to_param
+
+ array = [1, 2, 3, 4]
+ assert_equal "1/2/3/4", array.to_param
+
+ # Array of different objects
+ array = [1, '3', { a: 1, b: 2 }, nil, true, false, CustomString.new('object')]
+ assert_equal "1/3/a=1&b=2//true/false/custom-object", array.to_param
+ end
end
diff --git a/activesupport/test/core_ext/object/to_query_test.rb b/activesupport/test/core_ext/object/to_query_test.rb
index 92f996f9a4..09cab3ed35 100644
--- a/activesupport/test/core_ext/object/to_query_test.rb
+++ b/activesupport/test/core_ext/object/to_query_test.rb
@@ -46,6 +46,35 @@ class ToQueryTest < ActiveSupport::TestCase
:person => {:id => [20, 10]}
end
+ def test_empty_array
+ assert_equal "person%5B%5D=", [].to_query('person')
+ end
+
+ def test_nested_empty_hash
+ assert_equal '',
+ {}.to_query
+ assert_query_equal 'a=1&b%5Bc%5D=3',
+ { a: 1, b: { c: 3, d: {} } }
+ assert_query_equal '',
+ { a: {b: {c: {}}} }
+ assert_query_equal 'b%5Bc%5D=false&b%5Be%5D=&b%5Bf%5D=&p=12',
+ { p: 12, b: { c: false, e: nil, f: '' } }
+ assert_query_equal 'b%5Bc%5D=3&b%5Bf%5D=',
+ { b: { c: 3, k: {}, f: '' } }
+ assert_query_equal 'b=3',
+ {a: [], b: 3}
+ end
+
+ def test_hash_with_namespace
+ hash = { name: 'Nakshay', nationality: 'Indian' }
+ assert_equal "user%5Bname%5D=Nakshay&user%5Bnationality%5D=Indian", hash.to_query('user')
+ end
+
+ def test_hash_sorted_lexicographically
+ hash = { type: 'human', name: 'Nakshay' }
+ assert_equal "name=Nakshay&type=human", hash.to_query
+ end
+
private
def assert_query_equal(expected, actual)
assert_equal expected.split('&'), actual.to_query.split('&')
diff --git a/activesupport/test/core_ext/object/try_test.rb b/activesupport/test/core_ext/object/try_test.rb
new file mode 100644
index 0000000000..89438675c1
--- /dev/null
+++ b/activesupport/test/core_ext/object/try_test.rb
@@ -0,0 +1,99 @@
+require 'abstract_unit'
+require 'active_support/core_ext/object'
+
+class ObjectTryTest < ActiveSupport::TestCase
+ def setup
+ @string = "Hello"
+ end
+
+ def test_nonexisting_method
+ method = :undefined_method
+ assert !@string.respond_to?(method)
+ assert_nil @string.try(method)
+ end
+
+ def test_nonexisting_method_with_arguments
+ method = :undefined_method
+ assert !@string.respond_to?(method)
+ assert_nil @string.try(method, 'llo', 'y')
+ end
+
+ def test_nonexisting_method_bang
+ method = :undefined_method
+ assert !@string.respond_to?(method)
+ assert_raise(NoMethodError) { @string.try!(method) }
+ end
+
+ def test_nonexisting_method_with_arguments_bang
+ method = :undefined_method
+ assert !@string.respond_to?(method)
+ assert_raise(NoMethodError) { @string.try!(method, 'llo', 'y') }
+ end
+
+ def test_valid_method
+ assert_equal 5, @string.try(:size)
+ end
+
+ def test_argument_forwarding
+ assert_equal 'Hey', @string.try(:sub, 'llo', 'y')
+ end
+
+ def test_block_forwarding
+ assert_equal 'Hey', @string.try(:sub, 'llo') { |match| 'y' }
+ end
+
+ def test_nil_to_type
+ assert_nil nil.try(:to_s)
+ assert_nil nil.try(:to_i)
+ end
+
+ def test_false_try
+ assert_equal 'false', false.try(:to_s)
+ end
+
+ def test_try_only_block
+ assert_equal @string.reverse, @string.try(&:reverse)
+ end
+
+ def test_try_only_block_bang
+ assert_equal @string.reverse, @string.try!(&:reverse)
+ end
+
+ def test_try_only_block_nil
+ ran = false
+ nil.try { ran = true }
+ assert_equal false, ran
+ end
+
+ def test_try_with_instance_eval_block
+ assert_equal @string.reverse, @string.try { reverse }
+ end
+
+ def test_try_with_instance_eval_block_bang
+ assert_equal @string.reverse, @string.try! { reverse }
+ end
+
+ def test_try_with_private_method_bang
+ klass = Class.new do
+ private
+
+ def private_method
+ 'private method'
+ end
+ end
+
+ assert_raise(NoMethodError) { klass.new.try!(:private_method) }
+ end
+
+ def test_try_with_private_method
+ klass = Class.new do
+ private
+
+ def private_method
+ 'private method'
+ end
+ end
+
+ assert_nil klass.new.try(:private_method)
+ end
+end
diff --git a/activesupport/test/core_ext/object_and_class_ext_test.rb b/activesupport/test/core_ext/object_and_class_ext_test.rb
deleted file mode 100644
index 8d748791e3..0000000000
--- a/activesupport/test/core_ext/object_and_class_ext_test.rb
+++ /dev/null
@@ -1,181 +0,0 @@
-require 'abstract_unit'
-require 'active_support/time'
-require 'active_support/core_ext/object'
-require 'active_support/core_ext/class/subclasses'
-
-class ClassA; end
-class ClassB < ClassA; end
-class ClassC < ClassB; end
-class ClassD < ClassA; end
-
-class ClassI; end
-class ClassJ < ClassI; end
-
-class ClassK
-end
-module Nested
- class << self
- def on_const_missing(&callback)
- @on_const_missing = callback
- end
- private
- def const_missing(mod_id)
- @on_const_missing[mod_id] if @on_const_missing
- super
- end
- end
- class ClassL < ClassK
- end
-end
-
-class ObjectTests < ActiveSupport::TestCase
- class DuckTime
- def acts_like_time?
- true
- end
- end
-
- def test_duck_typing
- object = Object.new
- time = Time.now
- date = Date.today
- dt = DateTime.new
- duck = DuckTime.new
-
- assert !object.acts_like?(:time)
- assert !object.acts_like?(:date)
-
- assert time.acts_like?(:time)
- assert !time.acts_like?(:date)
-
- assert !date.acts_like?(:time)
- assert date.acts_like?(:date)
-
- assert dt.acts_like?(:time)
- assert dt.acts_like?(:date)
-
- assert duck.acts_like?(:time)
- assert !duck.acts_like?(:date)
- end
-end
-
-class ObjectInstanceVariableTest < ActiveSupport::TestCase
- def setup
- @source, @dest = Object.new, Object.new
- @source.instance_variable_set(:@bar, 'bar')
- @source.instance_variable_set(:@baz, 'baz')
- end
-
- def test_instance_variable_names
- assert_equal %w(@bar @baz), @source.instance_variable_names.sort
- end
-
- def test_instance_values
- object = Object.new
- object.instance_variable_set :@a, 1
- object.instance_variable_set :@b, 2
- assert_equal({'a' => 1, 'b' => 2}, object.instance_values)
- end
-
- def test_instance_exec_passes_arguments_to_block
- assert_equal %w(hello goodbye), 'hello'.instance_exec('goodbye') { |v| [self, v] }
- end
-
- def test_instance_exec_with_frozen_obj
- assert_equal %w(olleh goodbye), 'hello'.freeze.instance_exec('goodbye') { |v| [reverse, v] }
- end
-
- def test_instance_exec_nested
- assert_equal %w(goodbye olleh bar), 'hello'.instance_exec('goodbye') { |arg|
- [arg] + instance_exec('bar') { |v| [reverse, v] } }
- end
-end
-
-class ObjectTryTest < ActiveSupport::TestCase
- def setup
- @string = "Hello"
- end
-
- def test_nonexisting_method
- method = :undefined_method
- assert !@string.respond_to?(method)
- assert_nil @string.try(method)
- end
-
- def test_nonexisting_method_with_arguments
- method = :undefined_method
- assert !@string.respond_to?(method)
- assert_nil @string.try(method, 'llo', 'y')
- end
-
- def test_nonexisting_method_bang
- method = :undefined_method
- assert !@string.respond_to?(method)
- assert_raise(NoMethodError) { @string.try!(method) }
- end
-
- def test_nonexisting_method_with_arguments_bang
- method = :undefined_method
- assert !@string.respond_to?(method)
- assert_raise(NoMethodError) { @string.try!(method, 'llo', 'y') }
- end
-
- def test_try_only_block_bang
- assert_equal @string.reverse, @string.try! { |s| s.reverse }
- end
-
- def test_valid_method
- assert_equal 5, @string.try(:size)
- end
-
- def test_argument_forwarding
- assert_equal 'Hey', @string.try(:sub, 'llo', 'y')
- end
-
- def test_block_forwarding
- assert_equal 'Hey', @string.try(:sub, 'llo') { |match| 'y' }
- end
-
- def test_nil_to_type
- assert_nil nil.try(:to_s)
- assert_nil nil.try(:to_i)
- end
-
- def test_false_try
- assert_equal 'false', false.try(:to_s)
- end
-
- def test_try_only_block
- assert_equal @string.reverse, @string.try { |s| s.reverse }
- end
-
- def test_try_only_block_nil
- ran = false
- nil.try { ran = true }
- assert_equal false, ran
- end
-
- def test_try_with_private_method_bang
- klass = Class.new do
- private
-
- def private_method
- 'private method'
- end
- end
-
- assert_raise(NoMethodError) { klass.new.try!(:private_method) }
- end
-
- def test_try_with_private_method
- klass = Class.new do
- private
-
- def private_method
- 'private method'
- end
- end
-
- assert_nil klass.new.try(:private_method)
- end
-end
diff --git a/activesupport/test/core_ext/range_ext_test.rb b/activesupport/test/core_ext/range_ext_test.rb
index 2f8723a074..f096328cee 100644
--- a/activesupport/test/core_ext/range_ext_test.rb
+++ b/activesupport/test/core_ext/range_ext_test.rb
@@ -12,10 +12,11 @@ class RangeTest < ActiveSupport::TestCase
date_range = Time.utc(2005, 12, 10, 15, 30)..Time.utc(2005, 12, 10, 17, 30)
assert_equal "BETWEEN '2005-12-10 15:30:00' AND '2005-12-10 17:30:00'", date_range.to_s(:db)
end
-
+
def test_date_range
assert_instance_of Range, DateTime.new..DateTime.new
assert_instance_of Range, DateTime::Infinity.new..DateTime::Infinity.new
+ assert_instance_of Range, DateTime.new..DateTime::Infinity.new
end
def test_overlaps_last_inclusive
@@ -42,7 +43,7 @@ class RangeTest < ActiveSupport::TestCase
assert((1...10).include?(1...10))
end
- def test_should_include_other_with_exlusive_end
+ def test_should_include_other_with_exclusive_end
assert((1..10).include?(1...10))
end
@@ -90,4 +91,35 @@ class RangeTest < ActiveSupport::TestCase
time_range_2 = Time.utc(2005, 12, 10, 17, 31)..Time.utc(2005, 12, 10, 18, 00)
assert !time_range_1.overlaps?(time_range_2)
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))
+ 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))
+ 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
+ end
+
+ def test_date_time_with_each
+ datetime = DateTime.now
+ assert(((datetime - 1.hour)..datetime).each {})
+ end
+
+ def test_date_time_with_step
+ datetime = DateTime.now
+ assert(((datetime - 1.hour)..datetime).step(1) {})
+ end
end
diff --git a/activesupport/test/core_ext/securerandom.rb b/activesupport/test/core_ext/securerandom.rb
new file mode 100644
index 0000000000..dfacb7fe9f
--- /dev/null
+++ b/activesupport/test/core_ext/securerandom.rb
@@ -0,0 +1,20 @@
+require 'abstract_unit'
+require 'active_support/core_ext/securerandom'
+
+class SecureRandomTest < ActiveSupport::TestCase
+ def test_base58
+ s1 = SecureRandom.base58
+ s2 = SecureRandom.base58
+
+ assert_not_equal s1, s2
+ assert_equal 16, s1.length
+ end
+
+ def test_base58_with_length
+ s1 = SecureRandom.base58(24)
+ s2 = SecureRandom.base58(24)
+
+ assert_not_equal s1, s2
+ assert_equal 24, s1.length
+ end
+end
diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb
index 2537af0394..24037c665a 100644
--- a/activesupport/test/core_ext/string_ext_test.rb
+++ b/activesupport/test/core_ext/string_ext_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'date'
require 'abstract_unit'
require 'inflector_test_cases'
@@ -10,17 +9,12 @@ require 'active_support/time'
require 'active_support/core_ext/string/strip'
require 'active_support/core_ext/string/output_safety'
require 'active_support/core_ext/string/indent'
-
-module Ace
- module Base
- class Case
- end
- end
-end
+require 'time_zone_test_helpers'
class StringInflectionsTest < ActiveSupport::TestCase
include InflectorTestCases
include ConstantizeTestCases
+ include TimeZoneTestHelpers
def test_strip_heredoc_on_an_empty_string
assert_equal '', ''.strip_heredoc
@@ -65,6 +59,11 @@ class StringInflectionsTest < ActiveSupport::TestCase
assert_equal("blargles", "blargle".pluralize(2))
end
+ test 'pluralize with count = 1 still returns new string' do
+ name = "Kuldeep"
+ assert_not_same name.pluralize(1), name
+ end
+
def test_singularize
SingularToPlural.each do |singular, plural|
assert_equal(singular, plural.singularize)
@@ -162,59 +161,19 @@ class StringInflectionsTest < ActiveSupport::TestCase
end
end
- def test_ord
- assert_equal 97, 'a'.ord
- assert_equal 97, 'abc'.ord
+ def test_humanize_without_capitalize
+ UnderscoreToHumanWithoutCapitalize.each do |underscore, human|
+ assert_equal(human, underscore.humanize(capitalize: false))
+ end
end
- def test_access
- s = "hello"
- assert_equal "h", s.at(0)
-
- assert_equal "llo", s.from(2)
- assert_equal "hel", s.to(2)
-
- assert_equal "h", s.first
- assert_equal "he", s.first(2)
- assert_equal "", s.first(0)
-
- assert_equal "o", s.last
- assert_equal "llo", s.last(3)
- assert_equal "hello", s.last(10)
- assert_equal "", s.last(0)
-
- assert_equal 'x', 'x'.first
- assert_equal 'x', 'x'.first(4)
-
- assert_equal 'x', 'x'.last
- assert_equal 'x', 'x'.last(4)
+ def test_humanize_with_html_escape
+ assert_equal 'Hello', ERB::Util.html_escape("hello").humanize
end
- def test_access_returns_a_real_string
- hash = {}
- hash["h"] = true
- hash["hello123".at(0)] = true
- assert_equal %w(h), hash.keys
-
- hash = {}
- hash["llo"] = true
- hash["hello".from(2)] = true
- assert_equal %w(llo), hash.keys
-
- hash = {}
- hash["hel"] = true
- hash["hello".to(2)] = true
- assert_equal %w(hel), hash.keys
-
- hash = {}
- hash["hello"] = true
- hash["123hello".last(5)] = true
- assert_equal %w(hello), hash.keys
-
- hash = {}
- hash["hello"] = true
- hash["hello123".first(5)] = true
- assert_equal %w(hello), hash.keys
+ def test_ord
+ assert_equal 97, 'a'.ord
+ assert_equal 97, 'abc'.ord
end
def test_starts_ends_with_alias
@@ -229,21 +188,21 @@ class StringInflectionsTest < ActiveSupport::TestCase
end
def test_string_squish
- original = %{\u180E\u180E A string surrounded by unicode mongolian vowel separators,
- with tabs(\t\t), newlines(\n\n), unicode nextlines(\u0085\u0085) and many spaces( ). \u180E\u180E}
+ 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}
- expected = "A string surrounded by unicode mongolian vowel separators, " +
+ expected = "A string surrounded by various unicode spaces, " +
"with tabs( ), newlines( ), unicode nextlines( ) and many spaces( )."
# Make sure squish returns what we expect:
- assert_equal original.squish, expected
+ assert_equal expected, original.squish
# But doesn't modify the original string:
- assert_not_equal original, expected
+ assert_not_equal expected, original
# Make sure squish! returns what we expect:
- assert_equal original.squish!, expected
+ assert_equal expected, original.squish!
# And changes the original string:
- assert_equal original, expected
+ assert_equal expected, original
end
def test_string_inquiry
@@ -256,19 +215,40 @@ class StringInflectionsTest < ActiveSupport::TestCase
assert_equal "Hello Wor...", "Hello World!!".truncate(12)
end
- def test_truncate_with_omission_and_seperator
+ def test_truncate_with_omission_and_separator
assert_equal "Hello[...]", "Hello World!".truncate(10, :omission => "[...]")
assert_equal "Hello[...]", "Hello Big World!".truncate(13, :omission => "[...]", :separator => ' ')
assert_equal "Hello Big[...]", "Hello Big World!".truncate(14, :omission => "[...]", :separator => ' ')
assert_equal "Hello Big[...]", "Hello Big World!".truncate(15, :omission => "[...]", :separator => ' ')
end
- def test_truncate_with_omission_and_regexp_seperator
+ def test_truncate_with_omission_and_regexp_separator
assert_equal "Hello[...]", "Hello Big World!".truncate(13, :omission => "[...]", :separator => /\s/)
assert_equal "Hello Big[...]", "Hello Big World!".truncate(14, :omission => "[...]", :separator => /\s/)
assert_equal "Hello Big[...]", "Hello Big World!".truncate(15, :omission => "[...]", :separator => /\s/)
end
+ def test_truncate_words
+ assert_equal "Hello Big World!", "Hello Big World!".truncate_words(3)
+ assert_equal "Hello Big...", "Hello Big World!".truncate_words(2)
+ end
+
+ def test_truncate_words_with_omission
+ assert_equal "Hello Big World!", "Hello Big World!".truncate_words(3, :omission => "[...]")
+ assert_equal "Hello Big[...]", "Hello Big World!".truncate_words(2, :omission => "[...]")
+ end
+
+ def test_truncate_words_with_separator
+ assert_equal "Hello<br>Big<br>World!...", "Hello<br>Big<br>World!<br>".truncate_words(3, :separator => '<br>')
+ assert_equal "Hello<br>Big<br>World!", "Hello<br>Big<br>World!".truncate_words(3, :separator => '<br>')
+ assert_equal "Hello\n<br>Big...", "Hello\n<br>Big<br>Wide<br>World!".truncate_words(2, :separator => '<br>')
+ end
+
+ def test_truncate_words_with_separator_and_omission
+ assert_equal "Hello<br>Big<br>World![...]", "Hello<br>Big<br>World!<br>".truncate_words(3, :omission => "[...]", :separator => '<br>')
+ assert_equal "Hello<br>Big<br>World!", "Hello<br>Big<br>World!".truncate_words(3, :omission => "[...]", :separator => '<br>')
+ 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)
@@ -277,26 +257,139 @@ class StringInflectionsTest < ActiveSupport::TestCase
def test_truncate_should_not_be_html_safe
assert !"Hello World!".truncate(12).html_safe?
end
-
+
def test_remove
- assert_equal "Summer", "Fast Summer".remove(/Fast /)
- assert_equal "Summer", "Fast Summer".remove!(/Fast /)
+ original = "This is a good day to die"
+ assert_equal "This is a good day", original.remove(" to die")
+ assert_equal "This is a good day", original.remove(" to ", /die/)
+ assert_equal "This is a good day to die", original
+ end
+
+ def test_remove_for_multiple_occurrences
+ original = "This is a good day to die to die"
+ assert_equal "This is a good day", original.remove(" to die")
+ assert_equal "This is a good day to die to die", original
+ end
+
+ def test_remove!
+ original = "This is a very good day to die"
+ 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/)
+ assert_equal "This is a good day", original
end
def test_constantize
- run_constantize_tests_on do |string|
- string.constantize
- end
+ run_constantize_tests_on(&:constantize)
end
def test_safe_constantize
- run_safe_constantize_tests_on do |string|
- string.safe_constantize
- end
+ run_safe_constantize_tests_on(&:safe_constantize)
+ end
+end
+
+class StringAccessTest < ActiveSupport::TestCase
+ test "#at with Fixnum, returns a substring of one character at that position" do
+ assert_equal "h", "hello".at(0)
+ end
+
+ test "#at with Range, returns a substring containing characters at offsets" do
+ assert_equal "lo", "hello".at(-2..-1)
+ end
+
+ test "#at with Regex, returns the matching portion of the string" do
+ assert_equal "lo", "hello".at(/lo/)
+ assert_equal nil, "hello".at(/nonexisting/)
+ end
+
+ test "#from with positive Fixnum, returns substring from the given position to the end" do
+ assert_equal "llo", "hello".from(2)
+ end
+
+ test "#from with negative Fixnum, position is counted from the end" do
+ assert_equal "lo", "hello".from(-2)
+ end
+
+ test "#to with positive Fixnum, substring from the beginning to the given position" do
+ assert_equal "hel", "hello".to(2)
+ end
+
+ test "#to with negative Fixnum, position is counted from the end" do
+ assert_equal "hell", "hello".to(-2)
+ end
+
+ test "#from and #to can be combined" do
+ assert_equal "hello", "hello".from(0).to(-1)
+ assert_equal "ell", "hello".from(1).to(-2)
+ end
+
+ test "#first returns the first character" do
+ assert_equal "h", "hello".first
+ assert_equal 'x', 'x'.first
+ end
+
+ test "#first with Fixnum, returns a substring from the beginning to position" do
+ assert_equal "he", "hello".first(2)
+ assert_equal "", "hello".first(0)
+ assert_equal "hello", "hello".first(10)
+ assert_equal 'x', 'x'.first(4)
+ end
+
+ test "#first with Fixnum >= string length still returns a new string" do
+ string = "hello"
+ different_string = string.first(5)
+ assert_not_same different_string, string
+ end
+
+ test "#last returns the last character" do
+ assert_equal "o", "hello".last
+ assert_equal 'x', 'x'.last
+ end
+
+ test "#last with Fixnum, returns a substring from the end to position" do
+ assert_equal "llo", "hello".last(3)
+ assert_equal "hello", "hello".last(10)
+ assert_equal "", "hello".last(0)
+ assert_equal 'x', 'x'.last(4)
+ end
+
+ test "#last with Fixnum >= string length still returns a new string" do
+ string = "hello"
+ different_string = string.last(5)
+ assert_not_same different_string, string
+ end
+
+ test "access returns a real string" do
+ hash = {}
+ hash["h"] = true
+ hash["hello123".at(0)] = true
+ assert_equal %w(h), hash.keys
+
+ hash = {}
+ hash["llo"] = true
+ hash["hello".from(2)] = true
+ assert_equal %w(llo), hash.keys
+
+ hash = {}
+ hash["hel"] = true
+ hash["hello".to(2)] = true
+ assert_equal %w(hel), hash.keys
+
+ hash = {}
+ hash["hello"] = true
+ hash["123hello".last(5)] = true
+ assert_equal %w(hello), hash.keys
+
+ hash = {}
+ hash["hello"] = true
+ hash["hello123".first(5)] = true
+ assert_equal %w(hello), hash.keys
end
end
class StringConversionsTest < ActiveSupport::TestCase
+ include TimeZoneTestHelpers
+
def test_string_to_time
with_env_tz "Europe/Moscow" do
assert_equal Time.utc(2005, 2, 27, 23, 50), "2005-02-27 23:50".to_time(:utc)
@@ -322,11 +415,11 @@ class StringConversionsTest < ActiveSupport::TestCase
end
def test_partial_string_to_time
- with_env_tz "Europe/Moscow" do
+ with_env_tz "Europe/Moscow" do # use timezone which does not observe DST.
now = Time.now
assert_equal Time.local(now.year, now.month, now.day, 23, 50), "23:50".to_time
assert_equal Time.utc(now.year, now.month, now.day, 23, 50), "23:50".to_time(:utc)
- assert_equal Time.local(now.year, now.month, now.day, 18, 50), "13:50 -0100".to_time
+ assert_equal Time.local(now.year, now.month, now.day, 17, 50), "13:50 -0100".to_time
assert_equal Time.utc(now.year, now.month, now.day, 23, 50), "22:50 -0100".to_time(:utc)
end
end
@@ -466,14 +559,6 @@ class StringConversionsTest < ActiveSupport::TestCase
assert_nil "".to_date
assert_equal Date.new(Date.today.year, 2, 3), "Feb 3rd".to_date
end
-
- protected
- def with_env_tz(new_tz = 'US/Eastern')
- old_tz, ENV['TZ'] = ENV['TZ'], new_tz
- yield
- ensure
- old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ')
- end
end
class StringBehaviourTest < ActiveSupport::TestCase
@@ -568,6 +653,19 @@ class OutputSafetyTest < ActiveSupport::TestCase
assert !@other_combination.html_safe?
end
+ test "Prepending safe onto unsafe yields unsafe" do
+ @string.prepend "other".html_safe
+ assert !@string.html_safe?
+ assert_equal @string, "otherhello"
+ end
+
+ test "Prepending unsafe onto safe yields escaped safe" do
+ other = "other".html_safe
+ other.prepend "<foo>"
+ assert other.html_safe?
+ assert_equal other, "&lt;foo&gt;other"
+ end
+
test "Concatting safe onto unsafe yields unsafe" do
@other_string = "other"
@@ -678,6 +776,14 @@ class OutputSafetyTest < ActiveSupport::TestCase
string = "<b>hello</b>".html_safe
assert_equal string, ERB::Util.html_escape(string)
end
+
+ test "ERB::Util.html_escape_once only escapes once" do
+ string = '1 < 2 &amp; 3'
+ escaped_string = "1 &lt; 2 &amp; 3"
+
+ assert_equal escaped_string, ERB::Util.html_escape_once(string)
+ assert_equal escaped_string, ERB::Util.html_escape_once(escaped_string)
+ end
end
class StringExcludeTest < ActiveSupport::TestCase
diff --git a/activesupport/test/core_ext/struct_test.rb b/activesupport/test/core_ext/struct_test.rb
deleted file mode 100644
index 0dff7b32d2..0000000000
--- a/activesupport/test/core_ext/struct_test.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-require 'abstract_unit'
-require 'active_support/core_ext/struct'
-
-class StructExt < ActiveSupport::TestCase
- def test_to_h
- x = Struct.new(:foo, :bar)
- z = x.new(1, 2)
- assert_equal({ foo: 1, bar: 2 }, z.to_h)
- end
-end
diff --git a/activesupport/test/core_ext/thread_test.rb b/activesupport/test/core_ext/thread_test.rb
deleted file mode 100644
index 230c1203ad..0000000000
--- a/activesupport/test/core_ext/thread_test.rb
+++ /dev/null
@@ -1,77 +0,0 @@
-require 'abstract_unit'
-require 'active_support/core_ext/thread'
-
-class ThreadExt < ActiveSupport::TestCase
- def test_main_thread_variable_in_enumerator
- assert_equal Thread.main, Thread.current
-
- Thread.current.thread_variable_set :foo, "bar"
-
- thread, value = Fiber.new {
- Fiber.yield [Thread.current, Thread.current.thread_variable_get(:foo)]
- }.resume
-
- assert_equal Thread.current, thread
- assert_equal Thread.current.thread_variable_get(:foo), value
- end
-
- def test_thread_variable_in_enumerator
- Thread.new {
- Thread.current.thread_variable_set :foo, "bar"
-
- thread, value = Fiber.new {
- Fiber.yield [Thread.current, Thread.current.thread_variable_get(:foo)]
- }.resume
-
- assert_equal Thread.current, thread
- assert_equal Thread.current.thread_variable_get(:foo), value
- }.join
- end
-
- def test_thread_variables
- assert_equal [], Thread.new { Thread.current.thread_variables }.join.value
-
- t = Thread.new {
- Thread.current.thread_variable_set(:foo, "bar")
- Thread.current.thread_variables
- }
- assert_equal [:foo], t.join.value
- end
-
- def test_thread_variable?
- assert_not Thread.new { Thread.current.thread_variable?("foo") }.join.value
- t = Thread.new {
- Thread.current.thread_variable_set("foo", "bar")
- }.join
-
- assert t.thread_variable?("foo")
- assert t.thread_variable?(:foo)
- assert_not t.thread_variable?(:bar)
- end
-
- def test_thread_variable_strings_and_symbols_are_the_same_key
- t = Thread.new {}.join
- t.thread_variable_set("foo", "bar")
- assert_equal "bar", t.thread_variable_get(:foo)
- end
-
- def test_thread_variable_frozen
- t = Thread.new { }.join
- t.freeze
- assert_raises(RuntimeError) do
- t.thread_variable_set(:foo, "bar")
- end
- end
-
- def test_thread_variable_security
- t = Thread.new { sleep }
-
- assert_raises(SecurityError) do
- Thread.new { $SAFE = 4; t.thread_variable_get(:foo) }.join
- end
-
- assert_raises(SecurityError) do
- Thread.new { $SAFE = 4; t.thread_variable_set(:foo, :baz) }.join
- end
- end
-end
diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb
index 41a1df084e..d59775001b 100644
--- a/activesupport/test/core_ext/time_ext_test.rb
+++ b/activesupport/test/core_ext/time_ext_test.rb
@@ -1,6 +1,7 @@
require 'abstract_unit'
require 'active_support/time'
require 'core_ext/date_and_time_behavior'
+require 'time_zone_test_helpers'
class TimeExtCalculationsTest < ActiveSupport::TestCase
def date_time_init(year,month,day,hour,minute,second,usec=0)
@@ -8,6 +9,7 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
end
include DateAndTimeBehavior
+ include TimeZoneTestHelpers
def test_seconds_since_midnight
assert_equal 1,Time.local(2005,1,1,0,0,1).seconds_since_midnight
@@ -385,6 +387,8 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal Time.local(2005,1,2,11, 6, 0, 0), Time.local(2005,1,2,11,22,33,44).change(:min => 6)
assert_equal Time.local(2005,1,2,11,22, 7, 0), Time.local(2005,1,2,11,22,33,44).change(:sec => 7)
assert_equal Time.local(2005,1,2,11,22,33, 8), Time.local(2005,1,2,11,22,33,44).change(:usec => 8)
+ assert_equal Time.local(2005,1,2,11,22,33, 8), Time.local(2005,1,2,11,22,33,2).change(:nsec => 8000)
+ assert_raise(ArgumentError) { Time.local(2005,1,2,11,22,33, 8).change(:usec => 1, :nsec => 1) }
end
def test_utc_change
@@ -394,6 +398,7 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal Time.utc(2005,2,22,16), Time.utc(2005,2,22,15,15,10).change(:hour => 16)
assert_equal Time.utc(2005,2,22,16,45), Time.utc(2005,2,22,15,15,10).change(:hour => 16, :min => 45)
assert_equal Time.utc(2005,2,22,15,45), Time.utc(2005,2,22,15,15,10).change(:min => 45)
+ assert_equal Time.utc(2005,1,2,11,22,33,8), Time.utc(2005,1,2,11,22,33,2).change(:nsec => 8000)
end
def test_offset_change
@@ -403,6 +408,11 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal Time.new(2005,2,22,16,0,0,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").change(:hour => 16)
assert_equal Time.new(2005,2,22,16,45,0,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").change(:hour => 16, :min => 45)
assert_equal Time.new(2005,2,22,15,45,0,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").change(:min => 45)
+ assert_equal Time.new(2005,2,22,15,15,10,"-08:00"), Time.new(2005,2,22,15,15,0,"-08:00").change(:sec => 10)
+ assert_equal 10, Time.new(2005,2,22,15,15,0,"-08:00").change(:usec => 10).usec
+ assert_equal 10, Time.new(2005,2,22,15,15,0,"-08:00").change(:nsec => 10).nsec
+ assert_raise(ArgumentError) { Time.new(2005, 2, 22, 15, 15, 45, "-08:00").change(:usec => 1000000) }
+ assert_raise(ArgumentError) { Time.new(2005, 2, 22, 15, 15, 45, "-08:00").change(:nsec => 1000000000) }
end
def test_advance
@@ -476,6 +486,13 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal t, t.advance(:months => 0)
end
+ def test_advance_gregorian_proleptic
+ assert_equal Time.local(1582,10,14,15,15,10), Time.local(1582,10,15,15,15,10).advance(:days => -1)
+ assert_equal Time.local(1582,10,15,15,15,10), Time.local(1582,10,14,15,15,10).advance(:days => 1)
+ assert_equal Time.local(1582,10,5,15,15,10), Time.local(1582,10,4,15,15,10).advance(:days => 1)
+ assert_equal Time.local(1582,10,4,15,15,10), Time.local(1582,10,5,15,15,10).advance(:days => -1)
+ end
+
def test_last_week
with_env_tz 'US/Eastern' do
assert_equal Time.local(2005,2,21), Time.local(2005,3,1,15,15,10).last_week
@@ -840,15 +857,6 @@ 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
-
- protected
- def with_env_tz(new_tz = 'US/Eastern')
- old_tz, ENV['TZ'] = ENV['TZ'], new_tz
- yield
- ensure
- old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ')
- 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 5494824a40..92c233d567 100644
--- a/activesupport/test/core_ext/time_with_zone_test.rb
+++ b/activesupport/test/core_ext/time_with_zone_test.rb
@@ -1,8 +1,9 @@
require 'abstract_unit'
require 'active_support/time'
-require 'active_support/json'
+require 'time_zone_test_helpers'
class TimeWithZoneTest < ActiveSupport::TestCase
+ include TimeZoneTestHelpers
def setup
@utc = Time.utc(2000, 1, 1, 0)
@@ -66,25 +67,6 @@ class TimeWithZoneTest < ActiveSupport::TestCase
assert_equal 'EDT', ActiveSupport::TimeWithZone.new(Time.utc(2000, 6), @time_zone).zone #dst
end
- def test_to_json_with_use_standard_json_time_format_config_set_to_false
- old, ActiveSupport.use_standard_json_time_format = ActiveSupport.use_standard_json_time_format, false
- assert_equal "\"1999/12/31 19:00:00 -0500\"", ActiveSupport::JSON.encode(@twz)
- ensure
- ActiveSupport.use_standard_json_time_format = old
- end
-
- def test_to_json_with_use_standard_json_time_format_config_set_to_true
- old, ActiveSupport.use_standard_json_time_format = ActiveSupport.use_standard_json_time_format, true
- assert_equal "\"1999-12-31T19:00:00.000-05:00\"", ActiveSupport::JSON.encode(@twz)
- ensure
- ActiveSupport.use_standard_json_time_format = old
- end
-
- def test_to_json_when_wrapping_a_date_time
- twz = ActiveSupport::TimeWithZone.new(DateTime.civil(2000), @time_zone)
- assert_equal '"1999-12-31T19:00:00.000-05:00"', ActiveSupport::JSON.encode(twz)
- end
-
def test_nsec
local = Time.local(2011,6,7,23,59,59,Rational(999999999, 1000))
with_zone = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Hawaii"], local)
@@ -97,6 +79,11 @@ class TimeWithZoneTest < ActiveSupport::TestCase
assert_equal '1999-12-31 19:00:00 EST -0500', @twz.strftime('%Y-%m-%d %H:%M:%S %Z %z')
end
+ def test_strftime_with_escaping
+ assert_equal '%Z %z', @twz.strftime('%%Z %%z')
+ assert_equal '%EST %-0500', @twz.strftime('%%%Z %%%z')
+ end
+
def test_inspect
assert_equal 'Fri, 31 Dec 1999 19:00:00 EST -05:00', @twz.inspect
end
@@ -131,6 +118,10 @@ class TimeWithZoneTest < ActiveSupport::TestCase
assert_equal "1999-12-31T19:00:00.001234-05:00", @twz.xmlschema(12)
end
+ def test_xmlschema_with_nil_fractional_seconds
+ assert_equal "1999-12-31T19:00:00-05:00", @twz.xmlschema(nil)
+ end
+
def test_to_yaml
assert_match(/^--- 2000-01-01 00:00:00(\.0+)?\s*Z\n/, @twz.to_yaml)
end
@@ -262,16 +253,31 @@ class TimeWithZoneTest < ActiveSupport::TestCase
assert_equal 86_400.0, ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2), ActiveSupport::TimeZone['Hawaii'] ) - Time.utc(2000, 1, 1)
end
+ def test_minus_with_time_precision
+ assert_equal 86_399.999999998, ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2, 23, 59, 59, Rational(999999999, 1000)), ActiveSupport::TimeZone['UTC'] ) - Time.utc(2000, 1, 2, 0, 0, 0, Rational(1, 1000))
+ assert_equal 86_399.999999998, ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2, 23, 59, 59, Rational(999999999, 1000)), ActiveSupport::TimeZone['Hawaii'] ) - Time.utc(2000, 1, 2, 0, 0, 0, Rational(1, 1000))
+ end
+
def test_minus_with_time_with_zone
twz1 = ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1), ActiveSupport::TimeZone['UTC'] )
twz2 = ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2), ActiveSupport::TimeZone['UTC'] )
assert_equal 86_400.0, twz2 - twz1
end
+ def test_minus_with_time_with_zone_precision
+ twz1 = ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 0, 0, 0, Rational(1, 1000)), ActiveSupport::TimeZone['UTC'] )
+ twz2 = ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 23, 59, 59, Rational(999999999, 1000)), ActiveSupport::TimeZone['UTC'] )
+ assert_equal 86_399.999999998, twz2 - twz1
+ end
+
def test_minus_with_datetime
assert_equal 86_400.0, ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2), ActiveSupport::TimeZone['UTC'] ) - DateTime.civil(2000, 1, 1)
end
+ def test_minus_with_datetime_precision
+ assert_equal 86_399.999999999, ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 23, 59, 59, Rational(999999999, 1000)), ActiveSupport::TimeZone['UTC'] ) - DateTime.civil(2000, 1, 1)
+ end
+
def test_minus_with_wrapped_datetime
assert_equal 86_400.0, ActiveSupport::TimeWithZone.new( DateTime.civil(2000, 1, 2), ActiveSupport::TimeZone['UTC'] ) - Time.utc(2000, 1, 1)
assert_equal 86_400.0, ActiveSupport::TimeWithZone.new( DateTime.civil(2000, 1, 2), ActiveSupport::TimeZone['UTC'] ) - DateTime.civil(2000, 1, 1)
@@ -366,6 +372,7 @@ class TimeWithZoneTest < ActiveSupport::TestCase
end
def test_acts_like_time
+ assert @twz.acts_like_time?
assert @twz.acts_like?(:time)
assert ActiveSupport::TimeWithZone.new(DateTime.civil(2000), @time_zone).acts_like?(:time)
end
@@ -511,6 +518,16 @@ class TimeWithZoneTest < ActiveSupport::TestCase
assert_equal "Fri, 31 Dec 1999 19:00:30 EST -05:00", @twz.change(:sec => 30).inspect
end
+ def test_change_at_dst_boundary
+ twz = ActiveSupport::TimeWithZone.new(Time.at(1319936400).getutc, ActiveSupport::TimeZone['Madrid'])
+ assert_equal twz, twz.change(:min => 0)
+ end
+
+ def test_round_at_dst_boundary
+ twz = ActiveSupport::TimeWithZone.new(Time.at(1319936400).getutc, ActiveSupport::TimeZone['Madrid'])
+ assert_equal twz, twz.round
+ end
+
def test_advance
assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect
assert_equal "Mon, 31 Dec 2001 19:00:00 EST -05:00", @twz.advance(:years => 2).inspect
@@ -795,29 +812,25 @@ class TimeWithZoneTest < ActiveSupport::TestCase
end
def test_no_method_error_has_proper_context
+ rubinius_skip "Error message inconsistency"
+
e = assert_raises(NoMethodError) {
@twz.this_method_does_not_exist
}
assert_equal "undefined method `this_method_does_not_exist' for Fri, 31 Dec 1999 19:00:00 EST -05:00:Time", e.message
assert_no_match "rescue", e.backtrace.first
end
-
- protected
- def with_env_tz(new_tz = 'US/Eastern')
- old_tz, ENV['TZ'] = ENV['TZ'], new_tz
- yield
- ensure
- old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ')
- end
end
class TimeWithZoneMethodsForTimeAndDateTimeTest < ActiveSupport::TestCase
+ include TimeZoneTestHelpers
+
def setup
- @t, @dt = Time.utc(2000), DateTime.civil(2000)
+ @t, @dt, @zone = Time.utc(2000), DateTime.civil(2000), Time.zone
end
def teardown
- Time.zone = nil
+ Time.zone = @zone
end
def test_in_time_zone
@@ -873,8 +886,6 @@ class TimeWithZoneMethodsForTimeAndDateTimeTest < ActiveSupport::TestCase
def test_localtime
Time.zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
assert_equal @dt.in_time_zone.localtime, @dt.in_time_zone.utc.to_time.getlocal
- ensure
- Time.zone = nil
end
def test_use_zone
@@ -913,6 +924,7 @@ class TimeWithZoneMethodsForTimeAndDateTimeTest < ActiveSupport::TestCase
end
def test_time_zone_getter_and_setter_with_zone_default_set
+ old_zone_default = Time.zone_default
Time.zone_default = ActiveSupport::TimeZone['Alaska']
assert_equal ActiveSupport::TimeZone['Alaska'], Time.zone
Time.zone = ActiveSupport::TimeZone['Hawaii']
@@ -920,8 +932,7 @@ class TimeWithZoneMethodsForTimeAndDateTimeTest < ActiveSupport::TestCase
Time.zone = nil
assert_equal ActiveSupport::TimeZone['Alaska'], Time.zone
ensure
- Time.zone = nil
- Time.zone_default = nil
+ Time.zone_default = old_zone_default
end
def test_time_zone_setter_is_thread_safe
@@ -993,8 +1004,6 @@ class TimeWithZoneMethodsForTimeAndDateTimeTest < ActiveSupport::TestCase
assert_equal 'Eastern Time (US & Canada)', Time.current.time_zone.name
assert_equal Time.utc(2000), Time.current.time
end
- ensure
- Time.zone = nil
end
def test_time_in_time_zone_doesnt_affect_receiver
@@ -1005,25 +1014,15 @@ class TimeWithZoneMethodsForTimeAndDateTimeTest < ActiveSupport::TestCase
assert_not time.utc?, 'time expected to be local, but is UTC'
end
end
-
- protected
- def with_env_tz(new_tz = 'US/Eastern')
- old_tz, ENV['TZ'] = ENV['TZ'], new_tz
- yield
- ensure
- old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ')
- end
end
class TimeWithZoneMethodsForDate < ActiveSupport::TestCase
+ include TimeZoneTestHelpers
+
def setup
@d = Date.civil(2000)
end
- def teardown
- Time.zone = nil
- end
-
def test_in_time_zone
with_tz_default 'Alaska' do
assert_equal 'Sat, 01 Jan 2000 00:00:00 AKST -09:00', @d.in_time_zone.inspect
@@ -1056,28 +1055,17 @@ class TimeWithZoneMethodsForDate < ActiveSupport::TestCase
assert_raise(ArgumentError) { @d.in_time_zone(-15.hours) }
assert_raise(ArgumentError) { @d.in_time_zone(Object.new) }
end
-
- protected
- def with_tz_default(tz = nil)
- old_tz = Time.zone
- Time.zone = tz
- yield
- ensure
- Time.zone = old_tz
- end
end
class TimeWithZoneMethodsForString < ActiveSupport::TestCase
+ include TimeZoneTestHelpers
+
def setup
@s = "Sat, 01 Jan 2000 00:00:00"
@u = "Sat, 01 Jan 2000 00:00:00 UTC +00:00"
@z = "Fri, 31 Dec 1999 19:00:00 EST -05:00"
end
- def teardown
- Time.zone = nil
- end
-
def test_in_time_zone
with_tz_default 'Alaska' do
assert_equal 'Sat, 01 Jan 2000 00:00:00 AKST -09:00', @s.in_time_zone.inspect
@@ -1132,13 +1120,4 @@ class TimeWithZoneMethodsForString < ActiveSupport::TestCase
assert_raise(ArgumentError) { @u.in_time_zone(Object.new) }
assert_raise(ArgumentError) { @z.in_time_zone(Object.new) }
end
-
- protected
- def with_tz_default(tz = nil)
- old_tz = Time.zone
- Time.zone = tz
- yield
- ensure
- Time.zone = old_tz
- end
end
diff --git a/activesupport/test/core_ext/uri_ext_test.rb b/activesupport/test/core_ext/uri_ext_test.rb
index 03e388dd7a..1694fe7e72 100644
--- a/activesupport/test/core_ext/uri_ext_test.rb
+++ b/activesupport/test/core_ext/uri_ext_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'abstract_unit'
require 'uri'
require 'active_support/core_ext/uri'
@@ -7,7 +6,7 @@ class URIExtTest < ActiveSupport::TestCase
def test_uri_decode_handle_multibyte
str = "\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E" # Ni-ho-nn-go in UTF-8, means Japanese.
- parser = URI::Parser.new
+ parser = URI.parser
assert_equal str, parser.unescape(parser.escape(str))
end
end
diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb
index 68b6cc6e8c..702e26859a 100644
--- a/activesupport/test/dependencies_test.rb
+++ b/activesupport/test/dependencies_test.rb
@@ -16,15 +16,18 @@ module ModuleWithConstant
end
class DependenciesTest < ActiveSupport::TestCase
- def teardown
- ActiveSupport::Dependencies.clear
+ include DependenciesTestHelpers
+
+ setup do
+ @loaded_features_copy = $LOADED_FEATURES.dup
end
- include DependenciesTestHelpers
+ teardown do
+ ActiveSupport::Dependencies.clear
+ $LOADED_FEATURES.replace(@loaded_features_copy)
+ end
def test_depend_on_path
- skip "LoadError#path does not exist" if RUBY_VERSION < '2.0.0'
-
expected = assert_raises(LoadError) do
Kernel.require 'omgwtfbbq'
end
@@ -35,25 +38,39 @@ class DependenciesTest < ActiveSupport::TestCase
assert_equal expected.path, e.path
end
+ def test_require_dependency_accepts_an_object_which_implements_to_path
+ o = Object.new
+ def o.to_path; 'dependencies/service_one'; end
+ assert_nothing_raised {
+ require_dependency o
+ }
+ assert defined?(ServiceOne)
+ ensure
+ remove_constants(:ServiceOne)
+ end
+
def test_tracking_loaded_files
- require_dependency 'dependencies/service_one'
- require_dependency 'dependencies/service_two'
- assert_equal 2, ActiveSupport::Dependencies.loaded.size
+ with_loading do
+ require_dependency 'dependencies/service_one'
+ require_dependency 'dependencies/service_two'
+ assert_equal 2, ActiveSupport::Dependencies.loaded.size
+ end
ensure
- Object.send(:remove_const, :ServiceOne) if Object.const_defined?(:ServiceOne)
- Object.send(:remove_const, :ServiceTwo) if Object.const_defined?(:ServiceTwo)
+ remove_constants(:ServiceOne, :ServiceTwo)
end
def test_tracking_identical_loaded_files
- require_dependency 'dependencies/service_one'
- require_dependency 'dependencies/service_one'
- assert_equal 1, ActiveSupport::Dependencies.loaded.size
+ with_loading do
+ require_dependency 'dependencies/service_one'
+ require_dependency 'dependencies/service_one'
+ assert_equal 1, ActiveSupport::Dependencies.loaded.size
+ end
ensure
- Object.send(:remove_const, :ServiceOne) if Object.const_defined?(:ServiceOne)
+ remove_constants(:ServiceOne)
end
def test_missing_dependency_raises_missing_source_file
- assert_raise(MissingSourceFile) { require_dependency("missing_service") }
+ assert_raise(LoadError) { require_dependency("missing_service") }
end
def test_dependency_which_raises_exception_isnt_added_to_loaded_set
@@ -62,16 +79,15 @@ class DependenciesTest < ActiveSupport::TestCase
$raises_exception_load_count = 0
5.times do |count|
- begin
+ e = assert_raise Exception, 'should have loaded dependencies/raises_exception which raises an exception' do
require_dependency filename
- flunk 'should have loaded dependencies/raises_exception which raises an exception'
- rescue Exception => e
- assert_equal 'Loading me failed, so do not add to loaded or history.', e.message
end
+
+ assert_equal 'Loading me failed, so do not add to loaded or history.', e.message
assert_equal count + 1, $raises_exception_load_count
- assert !ActiveSupport::Dependencies.loaded.include?(filename)
- assert !ActiveSupport::Dependencies.history.include?(filename)
+ assert_not ActiveSupport::Dependencies.loaded.include?(filename)
+ assert_not ActiveSupport::Dependencies.history.include?(filename)
end
end
end
@@ -79,7 +95,6 @@ class DependenciesTest < ActiveSupport::TestCase
def test_dependency_which_raises_doesnt_blindly_call_blame_file!
with_loading do
filename = 'dependencies/raises_exception_without_blame_file'
-
assert_raises(Exception) { require_dependency filename }
end
end
@@ -87,13 +102,12 @@ class DependenciesTest < ActiveSupport::TestCase
def test_warnings_should_be_enabled_on_first_load
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}")
$check_warnings_load_count = 0
- assert !ActiveSupport::Dependencies.loaded.include?(expanded)
- assert !ActiveSupport::Dependencies.history.include?(expanded)
+ assert_not ActiveSupport::Dependencies.loaded.include?(expanded)
+ assert_not ActiveSupport::Dependencies.history.include?(expanded)
silence_warnings { require_dependency filename }
assert_equal 1, $check_warnings_load_count
@@ -101,7 +115,7 @@ class DependenciesTest < ActiveSupport::TestCase
assert ActiveSupport::Dependencies.loaded.include?(expanded)
ActiveSupport::Dependencies.clear
- assert !ActiveSupport::Dependencies.loaded.include?(expanded)
+ assert_not ActiveSupport::Dependencies.loaded.include?(expanded)
assert ActiveSupport::Dependencies.history.include?(expanded)
silence_warnings { require_dependency filename }
@@ -110,7 +124,7 @@ class DependenciesTest < ActiveSupport::TestCase
assert ActiveSupport::Dependencies.loaded.include?(expanded)
ActiveSupport::Dependencies.clear
- assert !ActiveSupport::Dependencies.loaded.include?(expanded)
+ assert_not ActiveSupport::Dependencies.loaded.include?(expanded)
assert ActiveSupport::Dependencies.history.include?(expanded)
enable_warnings { require_dependency filename }
@@ -143,6 +157,31 @@ class DependenciesTest < ActiveSupport::TestCase
end
end
+ def test_ensures_the_expected_constant_is_defined
+ with_autoloading_fixtures do
+ e = assert_raise(LoadError) { Typo }
+ assert_match %r{Unable to autoload constant Typo, expected .*activesupport/test/autoloading_fixtures/typo.rb to define it}, e.message
+ end
+ end
+
+ def test_require_dependency_does_not_assume_any_particular_constant_is_defined
+ with_autoloading_fixtures do
+ require_dependency 'typo'
+ assert_equal 1, TypO
+ end
+ end
+
+ # Regression, see https://github.com/rails/rails/issues/16468.
+ def test_require_dependency_interaction_with_autoloading
+ with_autoloading_fixtures do
+ require_dependency 'typo'
+ assert_equal 1, TypO
+
+ e = assert_raise(LoadError) { Typo }
+ assert_match %r{Unable to autoload constant Typo, expected .*activesupport/test/autoloading_fixtures/typo.rb to define it}, e.message
+ end
+ end
+
def test_module_loading
with_autoloading_fixtures do
assert_kind_of Module, A
@@ -164,43 +203,49 @@ class DependenciesTest < ActiveSupport::TestCase
def test_directories_manifest_as_modules_unless_const_defined
with_autoloading_fixtures do
assert_kind_of Module, ModuleFolder
- Object.__send__ :remove_const, :ModuleFolder
end
+ ensure
+ remove_constants(:ModuleFolder)
end
def test_module_with_nested_class
with_autoloading_fixtures do
assert_kind_of Class, ModuleFolder::NestedClass
- Object.__send__ :remove_const, :ModuleFolder
end
+ ensure
+ remove_constants(:ModuleFolder)
end
def test_module_with_nested_inline_class
with_autoloading_fixtures do
assert_kind_of Class, ModuleFolder::InlineClass
- Object.__send__ :remove_const, :ModuleFolder
end
+ ensure
+ remove_constants(:ModuleFolder)
end
def test_directories_may_manifest_as_nested_classes
with_autoloading_fixtures do
assert_kind_of Class, ClassFolder
- Object.__send__ :remove_const, :ClassFolder
end
+ ensure
+ remove_constants(:ClassFolder)
end
def test_class_with_nested_class
with_autoloading_fixtures do
assert_kind_of Class, ClassFolder::NestedClass
- Object.__send__ :remove_const, :ClassFolder
end
+ ensure
+ remove_constants(:ClassFolder)
end
def test_class_with_nested_inline_class
with_autoloading_fixtures do
assert_kind_of Class, ClassFolder::InlineClass
- Object.__send__ :remove_const, :ClassFolder
end
+ ensure
+ remove_constants(:ClassFolder)
end
def test_class_with_nested_inline_subclass_of_parent
@@ -208,8 +253,9 @@ class DependenciesTest < ActiveSupport::TestCase
assert_kind_of Class, ClassFolder::ClassFolderSubclass
assert_kind_of Class, ClassFolder
assert_equal 'indeed', ClassFolder::ClassFolderSubclass::ConstantInClassFolder
- Object.__send__ :remove_const, :ClassFolder
end
+ ensure
+ remove_constants(:ClassFolder)
end
def test_nested_class_can_access_sibling
@@ -217,16 +263,15 @@ class DependenciesTest < ActiveSupport::TestCase
sibling = ModuleFolder::NestedClass.class_eval "NestedSibling"
assert defined?(ModuleFolder::NestedSibling)
assert_equal ModuleFolder::NestedSibling, sibling
- Object.__send__ :remove_const, :ModuleFolder
end
+ ensure
+ remove_constants(:ModuleFolder)
end
def test_doesnt_break_normal_require
path = File.expand_path("../autoloading_fixtures/load_path", __FILE__)
original_path = $:.dup
- original_features = $".dup
$:.push(path)
-
with_autoloading_fixtures do
# The _ = assignments are to prevent warnings
_ = RequiresConstant
@@ -238,15 +283,13 @@ class DependenciesTest < ActiveSupport::TestCase
assert defined?(LoadedConstant)
end
ensure
- remove_constants(:RequiresConstant, :LoadedConstant, :LoadsConstant)
- $".replace(original_features)
+ remove_constants(:RequiresConstant, :LoadedConstant)
$:.replace(original_path)
end
def test_doesnt_break_normal_require_nested
path = File.expand_path("../autoloading_fixtures/load_path", __FILE__)
original_path = $:.dup
- original_features = $".dup
$:.push(path)
with_autoloading_fixtures do
@@ -261,14 +304,12 @@ class DependenciesTest < ActiveSupport::TestCase
end
ensure
remove_constants(:RequiresConstant, :LoadedConstant, :LoadsConstant)
- $".replace(original_features)
$:.replace(original_path)
end
def test_require_returns_true_when_file_not_yet_required
path = File.expand_path("../autoloading_fixtures/load_path", __FILE__)
original_path = $:.dup
- original_features = $".dup
$:.push(path)
with_loading do
@@ -276,14 +317,12 @@ class DependenciesTest < ActiveSupport::TestCase
end
ensure
remove_constants(:LoadedConstant)
- $".replace(original_features)
$:.replace(original_path)
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__)
original_path = $:.dup
- original_features = $".dup
$:.push(path)
with_loading do
@@ -292,14 +331,12 @@ class DependenciesTest < ActiveSupport::TestCase
end
ensure
remove_constants(:LoadedConstant)
- $".replace(original_features)
$:.replace(original_path)
end
def test_require_returns_false_when_file_already_required
path = File.expand_path("../autoloading_fixtures/load_path", __FILE__)
original_path = $:.dup
- original_features = $".dup
$:.push(path)
with_loading do
@@ -308,7 +345,6 @@ class DependenciesTest < ActiveSupport::TestCase
end
ensure
remove_constants(:LoadedConstant)
- $".replace(original_features)
$:.replace(original_path)
end
@@ -316,14 +352,11 @@ class DependenciesTest < ActiveSupport::TestCase
with_loading do
assert_raise(LoadError) { require 'this_file_dont_exist_dude' }
end
- ensure
- remove_constants(:LoadedConstant)
end
def test_load_returns_true_when_file_found
path = File.expand_path("../autoloading_fixtures/load_path", __FILE__)
original_path = $:.dup
- original_features = $".dup
$:.push(path)
with_loading do
@@ -332,7 +365,6 @@ class DependenciesTest < ActiveSupport::TestCase
end
ensure
remove_constants(:LoadedConstant)
- $".replace(original_features)
$:.replace(original_path)
end
@@ -340,41 +372,37 @@ class DependenciesTest < ActiveSupport::TestCase
with_loading do
assert_raise(LoadError) { load 'this_file_dont_exist_dude.rb' }
end
- ensure
- remove_constants(:LoadedConstant)
end
def failing_test_access_thru_and_upwards_fails
with_autoloading_fixtures do
- assert ! defined?(ModuleFolder)
+ assert_not defined?(ModuleFolder)
assert_raise(NameError) { ModuleFolder::Object }
assert_raise(NameError) { ModuleFolder::NestedClass::Object }
- Object.__send__ :remove_const, :ModuleFolder
end
+ ensure
+ remove_constants(:ModuleFolder)
end
def test_non_existing_const_raises_name_error_with_fully_qualified_name
with_autoloading_fixtures do
- begin
- A::DoesNotExist.nil?
- flunk "No raise!!"
- rescue NameError => e
- assert_equal "uninitialized constant A::DoesNotExist", e.message
- end
- begin
- A::B::DoesNotExist.nil?
- flunk "No raise!!"
- rescue NameError => e
- assert_equal "uninitialized constant A::B::DoesNotExist", e.message
- end
+ e = assert_raise(NameError) { A::DoesNotExist.nil? }
+ assert_equal "uninitialized constant A::DoesNotExist", e.message
+ assert_equal :DoesNotExist, e.name
+
+ e = assert_raise(NameError) { A::B::DoesNotExist.nil? }
+ assert_equal "uninitialized constant A::B::DoesNotExist", e.message
+ assert_equal :DoesNotExist, e.name
end
+ ensure
+ remove_constants(:A)
end
def test_smart_name_error_strings
- Object.module_eval "ImaginaryObject"
- flunk "No raise!!"
- rescue NameError => e
- assert e.message.include?("uninitialized constant ImaginaryObject")
+ e = assert_raise NameError do
+ Object.module_eval "ImaginaryObject"
+ end
+ assert_includes "uninitialized constant ImaginaryObject", e.message
end
def test_loadable_constants_for_path_should_handle_empty_autoloads
@@ -457,9 +485,9 @@ class DependenciesTest < ActiveSupport::TestCase
nil_name = Module.new
def nil_name.name() nil end
assert !ActiveSupport::Dependencies.autoloaded?(nil_name)
-
- Object.class_eval { remove_const :ModuleFolder }
end
+ ensure
+ remove_constants(:ModuleFolder)
end
def test_qualified_name_for
@@ -517,46 +545,45 @@ class DependenciesTest < ActiveSupport::TestCase
assert_kind_of Module, ::ModuleWithCustomConstMissing::A
assert_kind_of String, ::ModuleWithCustomConstMissing::A::B
end
+ ensure
+ remove_constants(:ModuleWithCustomConstMissing)
end
- def test_const_missing_should_not_double_load
- $counting_loaded_times = 0
+ def test_const_missing_in_anonymous_modules_loads_top_level_constants
with_autoloading_fixtures do
- require_dependency '././counting_loader'
- assert_equal 1, $counting_loaded_times
- assert_raise(NameError) { ActiveSupport::Dependencies.load_missing_constant Object, :CountingLoader }
- assert_equal 1, $counting_loaded_times
+ # class_eval STRING pushes the class to the nesting of the eval'ed code.
+ klass = Class.new.class_eval "E"
+ assert_equal E, klass
end
+ ensure
+ remove_constants(:E)
end
- def test_const_missing_within_anonymous_module
- $counting_loaded_times = 0
- m = Module.new
- m.module_eval "def a() CountingLoader; end"
- extend m
+ def test_const_missing_in_anonymous_modules_raises_if_the_constant_belongs_to_Object
with_autoloading_fixtures do
- kls = nil
- assert_nothing_raised { kls = a }
- assert_equal "CountingLoader", kls.name
- assert_equal 1, $counting_loaded_times
+ require_dependency 'e'
- assert_nothing_raised { kls = a }
- assert_equal 1, $counting_loaded_times
+ mod = Module.new
+ e = assert_raise(NameError) { mod::E }
+ assert_equal 'E cannot be autoloaded from an anonymous class or module', e.message
+ assert_equal :E, e.name
end
+ ensure
+ remove_constants(:E)
end
def test_removal_from_tree_should_be_detected
with_loading 'dependencies' do
c = ServiceOne
ActiveSupport::Dependencies.clear
- assert ! defined?(ServiceOne)
- begin
+ assert_not defined?(ServiceOne)
+ e = assert_raise ArgumentError do
ActiveSupport::Dependencies.load_missing_constant(c, :FakeMissing)
- flunk "Expected exception"
- rescue ArgumentError => e
- assert_match %r{ServiceOne has been removed from the module tree}i, e.message
end
+ assert_match %r{ServiceOne has been removed from the module tree}i, e.message
end
+ ensure
+ remove_constants(:ServiceOne)
end
def test_references_should_work
@@ -565,42 +592,46 @@ class DependenciesTest < ActiveSupport::TestCase
service_one_first = ServiceOne
assert_equal service_one_first, c.get("ServiceOne")
ActiveSupport::Dependencies.clear
- assert ! defined?(ServiceOne)
-
+ assert_not defined?(ServiceOne)
service_one_second = ServiceOne
assert_not_equal service_one_first, c.get("ServiceOne")
assert_equal service_one_second, c.get("ServiceOne")
end
+ ensure
+ remove_constants(:ServiceOne)
end
def test_constantize_shortcut_for_cached_constant_lookups
with_loading 'dependencies' do
assert_equal ServiceOne, ActiveSupport::Dependencies.constantize("ServiceOne")
end
+ ensure
+ remove_constants(:ServiceOne)
end
def test_nested_load_error_isnt_rescued
with_loading 'dependencies' do
- assert_raise(MissingSourceFile) do
+ assert_raise(LoadError) do
RequiresNonexistent1
end
end
end
def test_autoload_once_paths_do_not_add_to_autoloaded_constants
+ old_path = ActiveSupport::Dependencies.autoload_once_paths
with_autoloading_fixtures do
ActiveSupport::Dependencies.autoload_once_paths = ActiveSupport::Dependencies.autoload_paths.dup
- assert ! ActiveSupport::Dependencies.autoloaded?("ModuleFolder")
- assert ! ActiveSupport::Dependencies.autoloaded?("ModuleFolder::NestedClass")
- assert ! ActiveSupport::Dependencies.autoloaded?(ModuleFolder)
+ assert_not ActiveSupport::Dependencies.autoloaded?("ModuleFolder")
+ assert_not ActiveSupport::Dependencies.autoloaded?("ModuleFolder::NestedClass")
+ assert_not ActiveSupport::Dependencies.autoloaded?(ModuleFolder)
1 if ModuleFolder::NestedClass # 1 if to avoid warning
- assert ! ActiveSupport::Dependencies.autoloaded?(ModuleFolder::NestedClass)
+ assert_not ActiveSupport::Dependencies.autoloaded?(ModuleFolder::NestedClass)
end
ensure
- Object.class_eval { remove_const :ModuleFolder }
- ActiveSupport::Dependencies.autoload_once_paths = []
+ remove_constants(:ModuleFolder)
+ ActiveSupport::Dependencies.autoload_once_paths = old_path
end
def test_autoload_once_pathnames_do_not_add_to_autoloaded_constants
@@ -609,15 +640,15 @@ class DependenciesTest < ActiveSupport::TestCase
ActiveSupport::Dependencies.autoload_paths = pathnames
ActiveSupport::Dependencies.autoload_once_paths = pathnames
- assert ! ActiveSupport::Dependencies.autoloaded?("ModuleFolder")
- assert ! ActiveSupport::Dependencies.autoloaded?("ModuleFolder::NestedClass")
- assert ! ActiveSupport::Dependencies.autoloaded?(ModuleFolder)
+ assert_not ActiveSupport::Dependencies.autoloaded?("ModuleFolder")
+ assert_not ActiveSupport::Dependencies.autoloaded?("ModuleFolder::NestedClass")
+ assert_not ActiveSupport::Dependencies.autoloaded?(ModuleFolder)
1 if ModuleFolder::NestedClass # 1 if to avoid warning
- assert ! ActiveSupport::Dependencies.autoloaded?(ModuleFolder::NestedClass)
+ assert_not ActiveSupport::Dependencies.autoloaded?(ModuleFolder::NestedClass)
end
ensure
- Object.class_eval { remove_const :ModuleFolder }
+ remove_constants(:ModuleFolder)
ActiveSupport::Dependencies.autoload_once_paths = []
end
@@ -627,6 +658,8 @@ class DependenciesTest < ActiveSupport::TestCase
assert_equal 10, ApplicationController
assert ActiveSupport::Dependencies.autoloaded?(:ApplicationController)
end
+ ensure
+ remove_constants(:ApplicationController)
end
def test_preexisting_constants_are_not_marked_as_autoloaded
@@ -642,9 +675,18 @@ class DependenciesTest < ActiveSupport::TestCase
assert ! ActiveSupport::Dependencies.autoloaded?(:E), "E shouldn't be marked autoloaded!"
ActiveSupport::Dependencies.clear
end
+ ensure
+ remove_constants(:E)
+ end
+ def test_constants_in_capitalized_nesting_marked_as_autoloaded
+ with_autoloading_fixtures do
+ ActiveSupport::Dependencies.load_missing_constant(HTML, "SomeClass")
+
+ assert ActiveSupport::Dependencies.autoloaded?("HTML::SomeClass")
+ end
ensure
- Object.class_eval { remove_const :E }
+ remove_constants(:HTML)
end
def test_unloadable
@@ -675,7 +717,7 @@ class DependenciesTest < ActiveSupport::TestCase
assert_equal false, M.unloadable
end
ensure
- Object.class_eval { remove_const :M }
+ remove_constants(:M)
end
def test_unloadable_constants_should_receive_callback
@@ -686,7 +728,7 @@ class DependenciesTest < ActiveSupport::TestCase
ActiveSupport::Dependencies.clear
assert !defined?(C)
ensure
- Object.class_eval { remove_const :C } if defined?(C)
+ remove_constants(:C)
end
def test_new_contants_in_without_constants
@@ -700,7 +742,7 @@ class DependenciesTest < ActiveSupport::TestCase
}.map(&:to_s)
assert ActiveSupport::Dependencies.constant_watch_stack.all? {|k,v| v.empty? }
ensure
- Object.class_eval { remove_const :Hello }
+ remove_constants(:Hello)
end
def test_new_constants_in_with_nesting
@@ -717,9 +759,7 @@ class DependenciesTest < ActiveSupport::TestCase
assert_equal ["OuterAfter", "OuterBefore"], outer.sort.map(&:to_s)
assert ActiveSupport::Dependencies.constant_watch_stack.all? {|k,v| v.empty? }
ensure
- %w(OuterBefore Inner OuterAfter).each do |name|
- Object.class_eval { remove_const name if const_defined?(name) }
- end
+ remove_constants(:OuterBefore, :Inner, :OuterAfter)
end
def test_new_constants_in_module
@@ -738,7 +778,7 @@ class DependenciesTest < ActiveSupport::TestCase
assert_equal ["M::OuterAfter", "M::OuterBefore"], outer.sort
assert ActiveSupport::Dependencies.constant_watch_stack.all? {|k,v| v.empty? }
ensure
- Object.class_eval { remove_const :M }
+ remove_constants(:M)
end
def test_new_constants_in_module_using_name
@@ -756,7 +796,7 @@ class DependenciesTest < ActiveSupport::TestCase
assert_equal ["M::OuterAfter", "M::OuterBefore"], outer.sort
assert ActiveSupport::Dependencies.constant_watch_stack.all? {|k,v| v.empty? }
ensure
- Object.class_eval { remove_const :M }
+ remove_constants(:M)
end
def test_new_constants_in_with_inherited_constants
@@ -774,26 +814,27 @@ class DependenciesTest < ActiveSupport::TestCase
def test_file_with_multiple_constants_and_require_dependency
with_autoloading_fixtures do
- assert ! defined?(MultipleConstantFile)
- assert ! defined?(SiblingConstant)
+ assert_not defined?(MultipleConstantFile)
+ assert_not defined?(SiblingConstant)
require_dependency 'multiple_constant_file'
assert defined?(MultipleConstantFile)
assert defined?(SiblingConstant)
assert ActiveSupport::Dependencies.autoloaded?(:MultipleConstantFile)
assert ActiveSupport::Dependencies.autoloaded?(:SiblingConstant)
-
ActiveSupport::Dependencies.clear
- assert ! defined?(MultipleConstantFile)
- assert ! defined?(SiblingConstant)
+ assert_not defined?(MultipleConstantFile)
+ assert_not defined?(SiblingConstant)
end
+ ensure
+ remove_constants(:MultipleConstantFile, :SiblingConstant)
end
def test_file_with_multiple_constants_and_auto_loading
with_autoloading_fixtures do
- assert ! defined?(MultipleConstantFile)
- assert ! defined?(SiblingConstant)
+ assert_not defined?(MultipleConstantFile)
+ assert_not defined?(SiblingConstant)
assert_equal 10, MultipleConstantFile
@@ -804,15 +845,17 @@ class DependenciesTest < ActiveSupport::TestCase
ActiveSupport::Dependencies.clear
- assert ! defined?(MultipleConstantFile)
- assert ! defined?(SiblingConstant)
+ assert_not defined?(MultipleConstantFile)
+ assert_not defined?(SiblingConstant)
end
+ ensure
+ remove_constants(:MultipleConstantFile, :SiblingConstant)
end
def test_nested_file_with_multiple_constants_and_require_dependency
with_autoloading_fixtures do
- assert ! defined?(ClassFolder::NestedClass)
- assert ! defined?(ClassFolder::SiblingClass)
+ assert_not defined?(ClassFolder::NestedClass)
+ assert_not defined?(ClassFolder::SiblingClass)
require_dependency 'class_folder/nested_class'
@@ -823,15 +866,17 @@ class DependenciesTest < ActiveSupport::TestCase
ActiveSupport::Dependencies.clear
- assert ! defined?(ClassFolder::NestedClass)
- assert ! defined?(ClassFolder::SiblingClass)
+ assert_not defined?(ClassFolder::NestedClass)
+ assert_not defined?(ClassFolder::SiblingClass)
end
+ ensure
+ remove_constants(:ClassFolder)
end
def test_nested_file_with_multiple_constants_and_auto_loading
with_autoloading_fixtures do
- assert ! defined?(ClassFolder::NestedClass)
- assert ! defined?(ClassFolder::SiblingClass)
+ assert_not defined?(ClassFolder::NestedClass)
+ assert_not defined?(ClassFolder::SiblingClass)
assert_kind_of Class, ClassFolder::NestedClass
@@ -842,9 +887,11 @@ class DependenciesTest < ActiveSupport::TestCase
ActiveSupport::Dependencies.clear
- assert ! defined?(ClassFolder::NestedClass)
- assert ! defined?(ClassFolder::SiblingClass)
+ assert_not defined?(ClassFolder::NestedClass)
+ assert_not defined?(ClassFolder::SiblingClass)
end
+ ensure
+ remove_constants(:ClassFolder)
end
def test_autoload_doesnt_shadow_no_method_error_with_relative_constant
@@ -855,9 +902,8 @@ class DependenciesTest < ActiveSupport::TestCase
assert !defined?(::RaisesNoMethodError), "::RaisesNoMethodError is defined but it should have failed!"
end
end
-
ensure
- Object.class_eval { remove_const :RaisesNoMethodError if const_defined?(:RaisesNoMethodError) }
+ remove_constants(:RaisesNoMethodError)
end
def test_autoload_doesnt_shadow_no_method_error_with_absolute_constant
@@ -868,9 +914,8 @@ class DependenciesTest < ActiveSupport::TestCase
assert !defined?(::RaisesNoMethodError), "::RaisesNoMethodError is defined but it should have failed!"
end
end
-
ensure
- Object.class_eval { remove_const :RaisesNoMethodError if const_defined?(:RaisesNoMethodError) }
+ remove_constants(:RaisesNoMethodError)
end
def test_autoload_doesnt_shadow_error_when_mechanism_not_set_to_load
@@ -880,18 +925,17 @@ class DependenciesTest < ActiveSupport::TestCase
assert_raise(NameError) { assert_equal 123, ::RaisesNameError::FooBarBaz }
end
end
+ ensure
+ remove_constants(:RaisesNameError)
end
def test_autoload_doesnt_shadow_name_error
with_autoloading_fixtures do
- Object.send(:remove_const, :RaisesNameError) if defined?(::RaisesNameError)
2.times do
- begin
+ e = assert_raise NameError do
::RaisesNameError::FooBarBaz.object_id
- flunk 'should have raised NameError when autoloaded file referenced FooBarBaz'
- rescue NameError => e
- assert_equal 'uninitialized constant RaisesNameError::FooBarBaz', e.message
end
+ assert_equal 'uninitialized constant RaisesNameError::FooBarBaz', e.message
assert !defined?(::RaisesNameError), "::RaisesNameError is defined but it should have failed!"
end
@@ -901,19 +945,20 @@ class DependenciesTest < ActiveSupport::TestCase
assert !defined?(::RaisesNameError), "::RaisesNameError is defined but it should have failed!"
end
end
-
ensure
- Object.class_eval { remove_const :RaisesNoMethodError if const_defined?(:RaisesNoMethodError) }
+ remove_constants(:RaisesNameError)
end
def test_remove_constant_handles_double_colon_at_start
Object.const_set 'DeleteMe', Module.new
DeleteMe.const_set 'OrMe', Module.new
ActiveSupport::Dependencies.remove_constant "::DeleteMe::OrMe"
- assert ! defined?(DeleteMe::OrMe)
+ assert_not defined?(DeleteMe::OrMe)
assert defined?(DeleteMe)
ActiveSupport::Dependencies.remove_constant "::DeleteMe"
- assert ! defined?(DeleteMe)
+ assert_not defined?(DeleteMe)
+ ensure
+ remove_constants(:DeleteMe)
end
def test_remove_constant_does_not_trigger_loading_autoloads
@@ -923,7 +968,9 @@ class DependenciesTest < ActiveSupport::TestCase
end
assert_nil ActiveSupport::Dependencies.remove_constant(constant), "Kernel#autoload has been triggered by remove_constant"
- assert !defined?(ShouldNotBeAutoloaded)
+ assert_not defined?(ShouldNotBeAutoloaded)
+ ensure
+ remove_constants(constant)
end
def test_remove_constant_does_not_autoload_already_removed_parents_as_a_side_effect
@@ -932,11 +979,14 @@ class DependenciesTest < ActiveSupport::TestCase
_ = ::A::B # assignment to silence parse-time warning "possibly useless use of :: in void context"
ActiveSupport::Dependencies.remove_constant('A')
ActiveSupport::Dependencies.remove_constant('A::B')
- assert !defined?(A)
+ assert_not defined?(A)
end
+ ensure
+ remove_constants(:A)
end
def test_load_once_constants_should_not_be_unloaded
+ old_path = ActiveSupport::Dependencies.autoload_once_paths
with_autoloading_fixtures do
ActiveSupport::Dependencies.autoload_once_paths = ActiveSupport::Dependencies.autoload_paths
_ = ::A # assignment to silence parse-time warning "possibly useless use of :: in void context"
@@ -945,30 +995,58 @@ class DependenciesTest < ActiveSupport::TestCase
assert defined?(A)
end
ensure
- ActiveSupport::Dependencies.autoload_once_paths = []
- Object.class_eval { remove_const :A if const_defined?(:A) }
+ ActiveSupport::Dependencies.autoload_once_paths = old_path
+ remove_constants(:A)
+ end
+
+ def test_access_unloaded_constants_for_reload
+ with_autoloading_fixtures do
+ assert_kind_of Module, A
+ assert_kind_of Class, A::B # Necessary to load A::B for the test
+ ActiveSupport::Dependencies.mark_for_unload(A::B)
+ ActiveSupport::Dependencies.remove_unloadable_constants!
+
+ A::B # Make sure no circular dependency error
+ end
+ ensure
+ remove_constants(:A)
end
+
def test_autoload_once_paths_should_behave_when_recursively_loading
+ old_path = ActiveSupport::Dependencies.autoload_once_paths
with_loading 'dependencies', 'autoloading_fixtures' do
ActiveSupport::Dependencies.autoload_once_paths = [ActiveSupport::Dependencies.autoload_paths.last]
- assert !defined?(CrossSiteDependency)
+ assert_not defined?(CrossSiteDependency)
assert_nothing_raised { CrossSiteDepender.nil? }
assert defined?(CrossSiteDependency)
- assert !ActiveSupport::Dependencies.autoloaded?(CrossSiteDependency),
+ assert_not ActiveSupport::Dependencies.autoloaded?(CrossSiteDependency),
"CrossSiteDependency shouldn't be marked as autoloaded!"
ActiveSupport::Dependencies.clear
assert defined?(CrossSiteDependency),
"CrossSiteDependency shouldn't have been unloaded!"
end
ensure
- ActiveSupport::Dependencies.autoload_once_paths = []
+ ActiveSupport::Dependencies.autoload_once_paths = old_path
+ remove_constants(:CrossSiteDependency)
end
def test_hook_called_multiple_times
assert_nothing_raised { ActiveSupport::Dependencies.hook! }
end
+ def test_load_and_require_stay_private
+ assert Object.private_methods.include?(:load)
+ assert Object.private_methods.include?(:require)
+
+ ActiveSupport::Dependencies.unhook!
+
+ assert Object.private_methods.include?(:load)
+ assert Object.private_methods.include?(:require)
+ ensure
+ ActiveSupport::Dependencies.hook!
+ end
+
def test_unhook
ActiveSupport::Dependencies.unhook!
assert !Module.new.respond_to?(:const_missing_without_dependencies)
@@ -976,11 +1054,4 @@ class DependenciesTest < ActiveSupport::TestCase
ensure
ActiveSupport::Dependencies.hook!
end
-
-private
- def remove_constants(*constants)
- constants.each do |constant|
- Object.send(:remove_const, constant) if Object.const_defined?(constant)
- end
- end
end
diff --git a/activesupport/test/dependencies_test_helpers.rb b/activesupport/test/dependencies_test_helpers.rb
index 9268512a97..e4d5197112 100644
--- a/activesupport/test/dependencies_test_helpers.rb
+++ b/activesupport/test/dependencies_test_helpers.rb
@@ -13,6 +13,7 @@ module DependenciesTestHelpers
ActiveSupport::Dependencies.autoload_paths = prior_autoload_paths
ActiveSupport::Dependencies.mechanism = old_mechanism
ActiveSupport::Dependencies.explicitly_unloadable_constants = []
+ ActiveSupport::Dependencies.clear
end
def with_autoloading_fixtures(&block)
diff --git a/activesupport/test/deprecation_test.rb b/activesupport/test/deprecation_test.rb
index 9674851b9d..20bd8ee5dd 100644
--- a/activesupport/test/deprecation_test.rb
+++ b/activesupport/test/deprecation_test.rb
@@ -1,4 +1,5 @@
require 'abstract_unit'
+require 'active_support/testing/stream'
class Deprecatee
def initialize
@@ -36,6 +37,8 @@ end
class DeprecationTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::Stream
+
def setup
# Track the last warning.
@old_behavior = ActiveSupport::Deprecation.behavior
@@ -104,14 +107,11 @@ class DeprecationTest < ActiveSupport::TestCase
message = 'Revise this deprecated stuff now!'
callstack = %w(foo bar baz)
- begin
+ e = assert_raise ActiveSupport::DeprecationException do
ActiveSupport::Deprecation.behavior.first.call(message, callstack)
- rescue ActiveSupport::DeprecationException => e
- assert_equal message, e.message
- assert_equal callstack, e.backtrace
- else
- flunk 'the :raise deprecation behaviour should raise the expected exception'
end
+ assert_equal message, e.message
+ assert_equal callstack, e.backtrace
end
def test_default_stderr_behavior
@@ -174,7 +174,7 @@ class DeprecationTest < ActiveSupport::TestCase
ActiveSupport::Deprecation.warn 'abc'
ActiveSupport::Deprecation.warn 'def'
end
- rescue MiniTest::Assertion
+ rescue Minitest::Assertion
flunk 'assert_deprecated should match any warning in block, not just the last one'
end
@@ -358,4 +358,5 @@ class DeprecationTest < ActiveSupport::TestCase
end
deprecator
end
+
end
diff --git a/activesupport/test/descendants_tracker_without_autoloading_test.rb b/activesupport/test/descendants_tracker_without_autoloading_test.rb
index 74669aaca1..00b449af51 100644
--- a/activesupport/test/descendants_tracker_without_autoloading_test.rb
+++ b/activesupport/test/descendants_tracker_without_autoloading_test.rb
@@ -4,4 +4,14 @@ require 'descendants_tracker_test_cases'
class DescendantsTrackerWithoutAutoloadingTest < ActiveSupport::TestCase
include DescendantsTrackerTestCases
+
+ # Regression test for #8422. https://github.com/rails/rails/issues/8442
+ def test_clear_without_autoloaded_singleton_parent
+ mark_as_autoloaded do
+ parent_instance = Parent.new
+ parent_instance.singleton_class.descendants
+ ActiveSupport::DescendantsTracker.clear
+ assert !ActiveSupport::DescendantsTracker.class_variable_get(:@@direct_descendants).key?(parent_instance.singleton_class)
+ end
+ end
end
diff --git a/activesupport/test/empty_bool.rb b/activesupport/test/empty_bool.rb
deleted file mode 100644
index 005b3523ef..0000000000
--- a/activesupport/test/empty_bool.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-class EmptyTrue
- def empty?() true; end
-end
-
-class EmptyFalse
- def empty?() false; end
-end
diff --git a/activesupport/test/file_fixtures/sample.txt b/activesupport/test/file_fixtures/sample.txt
new file mode 100644
index 0000000000..0fa80e7383
--- /dev/null
+++ b/activesupport/test/file_fixtures/sample.txt
@@ -0,0 +1 @@
+sample file fixture
diff --git a/activesupport/test/fixtures/custom.rb b/activesupport/test/fixtures/custom.rb
deleted file mode 100644
index 0eefce0c25..0000000000
--- a/activesupport/test/fixtures/custom.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-class Custom
-end \ No newline at end of file
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..1facd691fa
--- /dev/null
+++ b/activesupport/test/hash_with_indifferent_access_test.rb
@@ -0,0 +1,11 @@
+require 'abstract_unit'
+require 'active_support/hash_with_indifferent_access'
+
+class HashWithIndifferentAccessTest < ActiveSupport::TestCase
+ def test_reverse_merge
+ hash = HashWithIndifferentAccess.new key: :old_value
+ hash.reverse_merge! key: :new_value
+ assert_equal :old_value, hash[:key]
+ end
+
+end
diff --git a/activesupport/test/i18n_test.rb b/activesupport/test/i18n_test.rb
index 5ef59b6e6b..3faa15e7fd 100644
--- a/activesupport/test/i18n_test.rb
+++ b/activesupport/test/i18n_test.rb
@@ -99,7 +99,6 @@ class I18nTest < ActiveSupport::TestCase
end
def test_to_sentence_with_empty_i18n_store
- I18n.backend.store_translations 'empty', {}
assert_equal 'a, b, and c', %w[a b c].to_sentence(locale: 'empty')
end
end
diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb
index 1bb2d7e920..be68bb2e2e 100644
--- a/activesupport/test/inflector_test.rb
+++ b/activesupport/test/inflector_test.rb
@@ -70,10 +70,11 @@ class InflectorTest < ActiveSupport::TestCase
def test_overwrite_previous_inflectors
- assert_equal("series", ActiveSupport::Inflector.singularize("series"))
- ActiveSupport::Inflector.inflections.singular "series", "serie"
- assert_equal("serie", ActiveSupport::Inflector.singularize("series"))
- ActiveSupport::Inflector.inflections.uncountable "series" # Return to normal
+ with_dup do
+ assert_equal("series", ActiveSupport::Inflector.singularize("series"))
+ ActiveSupport::Inflector.inflections.singular "series", "serie"
+ assert_equal("serie", ActiveSupport::Inflector.singularize("series"))
+ end
end
MixtureToTitleCase.each_with_index do |(before, titleized), index|
@@ -119,10 +120,14 @@ class InflectorTest < ActiveSupport::TestCase
["SSLError", "ssl_error", "SSL error", "SSL Error"],
["RESTful", "restful", "RESTful", "RESTful"],
["RESTfulController", "restful_controller", "RESTful controller", "RESTful Controller"],
+ ["Nested::RESTful", "nested/restful", "Nested/RESTful", "Nested/RESTful"],
["IHeartW3C", "i_heart_w3c", "I heart W3C", "I Heart W3C"],
["PhDRequired", "phd_required", "PhD required", "PhD Required"],
["IRoRU", "i_ror_u", "I RoR u", "I RoR U"],
["RESTfulHTTPAPI", "restful_http_api", "RESTful HTTP API", "RESTful HTTP API"],
+ ["HTTP::RESTful", "http/restful", "HTTP/RESTful", "HTTP/RESTful"],
+ ["HTTP::RESTfulAPI", "http/restful_api", "HTTP/RESTful API", "HTTP/RESTful API"],
+ ["APIRESTful", "api_restful", "API RESTful", "API RESTful"],
# misdirection
["Capistrano", "capistrano", "Capistrano", "Capistrano"],
@@ -199,6 +204,7 @@ class InflectorTest < ActiveSupport::TestCase
def test_demodulize
assert_equal "Account", ActiveSupport::Inflector.demodulize("MyApplication::Billing::Account")
assert_equal "Account", ActiveSupport::Inflector.demodulize("Account")
+ assert_equal "Account", ActiveSupport::Inflector.demodulize("::Account")
assert_equal "", ActiveSupport::Inflector.demodulize("")
end
@@ -230,25 +236,35 @@ class InflectorTest < ActiveSupport::TestCase
end
end
+# FIXME: get following tests to pass on jruby, currently skipped
+#
+# Currently this fails because ActiveSupport::Multibyte::Unicode#tidy_bytes
+# required a specific Encoding::Converter(UTF-8 to UTF8-MAC) which unavailable on JRuby
+# causing our tests to error out.
+# related bug http://jira.codehaus.org/browse/JRUBY-7194
def test_parameterize
+ jruby_skip "UTF-8 to UTF8-MAC Converter is unavailable"
StringToParameterized.each do |some_string, parameterized_string|
assert_equal(parameterized_string, ActiveSupport::Inflector.parameterize(some_string))
end
end
def test_parameterize_and_normalize
+ jruby_skip "UTF-8 to UTF8-MAC Converter is unavailable"
StringToParameterizedAndNormalized.each do |some_string, parameterized_string|
assert_equal(parameterized_string, ActiveSupport::Inflector.parameterize(some_string))
end
end
def test_parameterize_with_custom_separator
+ jruby_skip "UTF-8 to UTF8-MAC Converter is unavailable"
StringToParameterizeWithUnderscore.each do |some_string, parameterized_string|
assert_equal(parameterized_string, ActiveSupport::Inflector.parameterize(some_string, '_'))
end
end
def test_parameterize_with_multi_character_separator
+ jruby_skip "UTF-8 to UTF8-MAC Converter is unavailable"
StringToParameterized.each do |some_string, parameterized_string|
assert_equal(parameterized_string.gsub('-', '__sep__'), ActiveSupport::Inflector.parameterize(some_string, '__sep__'))
end
@@ -277,6 +293,12 @@ class InflectorTest < ActiveSupport::TestCase
end
end
+ def test_humanize_without_capitalize
+ UnderscoreToHumanWithoutCapitalize.each do |underscore, human|
+ assert_equal(human, ActiveSupport::Inflector.humanize(underscore, capitalize: false))
+ end
+ end
+
def test_humanize_by_rule
ActiveSupport::Inflector.inflections do |inflect|
inflect.human(/_cnt$/i, '\1_count')
@@ -468,8 +490,8 @@ class InflectorTest < ActiveSupport::TestCase
assert_equal [], inflect.uncountables
# restore all the inflections
- singulars.reverse.each { |singular| inflect.singular(*singular) }
- plurals.reverse.each { |plural| inflect.plural(*plural) }
+ singulars.reverse_each { |singular| inflect.singular(*singular) }
+ plurals.reverse_each { |plural| inflect.plural(*plural) }
inflect.uncountable(uncountables)
assert_equal singulars, inflect.singulars
@@ -480,10 +502,10 @@ class InflectorTest < ActiveSupport::TestCase
end
%w(plurals singulars uncountables humans acronyms).each do |scope|
- ActiveSupport::Inflector.inflections do |inflect|
- define_method("test_clear_inflections_with_#{scope}") do
- with_dup do
- # clear the inflections
+ define_method("test_clear_inflections_with_#{scope}") do
+ with_dup do
+ # clear the inflections
+ ActiveSupport::Inflector.inflections do |inflect|
inflect.clear(scope)
assert_equal [], inflect.send(scope)
end
@@ -491,6 +513,14 @@ class InflectorTest < ActiveSupport::TestCase
end
end
+ def test_inflections_with_uncountable_words
+ ActiveSupport::Inflector.inflections do |inflect|
+ inflect.uncountable "HTTP"
+ end
+
+ assert_equal "HTTP", ActiveSupport::Inflector.pluralize("HTTP")
+ end
+
# Dups the singleton and yields, restoring the original inflections later.
# Use this in tests what modify the state of the singleton.
#
@@ -498,9 +528,10 @@ class InflectorTest < ActiveSupport::TestCase
# there are module functions that access ActiveSupport::Inflector.inflections,
# so we need to replace the singleton itself.
def with_dup
- original = ActiveSupport::Inflector::Inflections.instance_variable_get(:@__instance__)
- ActiveSupport::Inflector::Inflections.instance_variable_set(:@__instance__, original.dup)
+ original = ActiveSupport::Inflector::Inflections.instance_variable_get(:@__instance__)[:en]
+ ActiveSupport::Inflector::Inflections.instance_variable_set(:@__instance__, en: original.dup)
+ yield
ensure
- ActiveSupport::Inflector::Inflections.instance_variable_set(:@__instance__, original)
+ ActiveSupport::Inflector::Inflections.instance_variable_set(:@__instance__, en: original)
end
end
diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb
index cc36d62138..18a8b92eb9 100644
--- a/activesupport/test/inflector_test_cases.rb
+++ b/activesupport/test/inflector_test_cases.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
module InflectorTestCases
SingularToPlural = {
@@ -63,6 +62,7 @@ module InflectorTestCases
"news" => "news",
"series" => "series",
+ "miniseries" => "miniseries",
"species" => "species",
"quiz" => "quizzes",
@@ -140,6 +140,7 @@ module InflectorTestCases
"HTMLTidyGenerator" => "html_tidy_generator",
"FreeBSD" => "free_bsd",
"HTML" => "html",
+ "ForceXMLController" => "force_xml_controller",
}
CamelWithModuleToUnderscoreWithSlash = {
@@ -207,9 +208,17 @@ module InflectorTestCases
}
UnderscoreToHuman = {
- "employee_salary" => "Employee salary",
- "employee_id" => "Employee",
- "underground" => "Underground"
+ 'employee_salary' => 'Employee salary',
+ 'employee_id' => 'Employee',
+ 'underground' => 'Underground',
+ '_id' => 'Id',
+ '_external_id' => 'External'
+ }
+
+ UnderscoreToHumanWithoutCapitalize = {
+ "employee_salary" => "employee salary",
+ "employee_id" => "employee",
+ "underground" => "underground"
}
MixtureToTitleCase = {
@@ -307,7 +316,7 @@ module InflectorTestCases
'child' => 'children',
'sex' => 'sexes',
'move' => 'moves',
- 'cow' => 'kine',
+ 'cow' => 'kine', # Test inflections with different starting letters
'zombie' => 'zombies',
'genus' => 'genera'
}
diff --git a/activesupport/test/json/decoding_test.rb b/activesupport/test/json/decoding_test.rb
index 99c5f2d1ec..f2fc456f4b 100644
--- a/activesupport/test/json/decoding_test.rb
+++ b/activesupport/test/json/decoding_test.rb
@@ -1,9 +1,14 @@
-# encoding: utf-8
require 'abstract_unit'
require 'active_support/json'
require 'active_support/time'
class TestJSONDecoding < ActiveSupport::TestCase
+ class Foo
+ def self.json_create(object)
+ "Foo"
+ end
+ end
+
TESTS = {
%q({"returnTo":{"\/categories":"\/"}}) => {"returnTo" => {"/categories" => "/"}},
%q({"return\\"To\\":":{"\/categories":"\/"}}) => {"return\"To\":" => {"/categories" => "/"}},
@@ -52,31 +57,56 @@ class TestJSONDecoding < ActiveSupport::TestCase
# tests escaping of "\n" char with Yaml backend
%q({"a":"\n"}) => {"a"=>"\n"},
%q({"a":"\u000a"}) => {"a"=>"\n"},
- %q({"a":"Line1\u000aLine2"}) => {"a"=>"Line1\nLine2"}
+ %q({"a":"Line1\u000aLine2"}) => {"a"=>"Line1\nLine2"},
+ # prevent json unmarshalling
+ %q({"json_class":"TestJSONDecoding::Foo"}) => {"json_class"=>"TestJSONDecoding::Foo"},
+ # json "fragments" - these are invalid JSON, but ActionPack relies on this
+ %q("a string") => "a string",
+ %q(1.1) => 1.1,
+ %q(1) => 1,
+ %q(-1) => -1,
+ %q(true) => true,
+ %q(false) => false,
+ %q(null) => nil
}
TESTS.each_with_index do |(json, expected), index|
test "json decodes #{index}" do
- prev = ActiveSupport.parse_json_times
- ActiveSupport.parse_json_times = true
- silence_warnings do
- assert_equal expected, ActiveSupport::JSON.decode(json), "JSON decoding \
- failed for #{json}"
+ with_parse_json_times(true) do
+ silence_warnings do
+ assert_equal expected, ActiveSupport::JSON.decode(json), "JSON decoding \
+ failed for #{json}"
+ end
end
- ActiveSupport.parse_json_times = prev
end
end
test "json decodes time json with time parsing disabled" do
- prev = ActiveSupport.parse_json_times
- ActiveSupport.parse_json_times = false
- expected = {"a" => "2007-01-01 01:12:34 Z"}
- assert_equal expected, ActiveSupport::JSON.decode(%({"a": "2007-01-01 01:12:34 Z"}))
- ActiveSupport.parse_json_times = prev
+ with_parse_json_times(false) do
+ expected = {"a" => "2007-01-01 01:12:34 Z"}
+ assert_equal expected, ActiveSupport::JSON.decode(%({"a": "2007-01-01 01:12:34 Z"}))
+ end
end
def test_failed_json_decoding
+ assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%(undefined)) }
+ assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%({a: 1})) }
assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%({: 1})) }
+ assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%()) }
+ end
+
+ def test_cannot_pass_unsupported_options
+ assert_raise(ArgumentError) { ActiveSupport::JSON.decode("", create_additions: true) }
+ end
+
+ private
+
+ def with_parse_json_times(value)
+ old_value = ActiveSupport.parse_json_times
+ ActiveSupport.parse_json_times = value
+ yield
+ ensure
+ ActiveSupport.parse_json_times = old_value
end
end
diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb
index ed1326705c..63d921e3b4 100644
--- a/activesupport/test/json/encoding_test.rb
+++ b/activesupport/test/json/encoding_test.rb
@@ -1,9 +1,13 @@
-# encoding: utf-8
+require 'securerandom'
require 'abstract_unit'
require 'active_support/core_ext/string/inflections'
require 'active_support/json'
+require 'active_support/time'
+require 'time_zone_test_helpers'
class TestJSONEncoding < ActiveSupport::TestCase
+ include TimeZoneTestHelpers
+
class Foo
def initialize(a, b)
@a, @b = a, b
@@ -12,13 +16,17 @@ class TestJSONEncoding < ActiveSupport::TestCase
class Hashlike
def to_hash
- { :a => 1 }
+ { :foo => "hello", :bar => "world" }
end
end
class Custom
- def as_json(options)
- 'custom'
+ def initialize(serialized)
+ @serialized = serialized
+ end
+
+ def as_json(options = nil)
+ @serialized
end
end
@@ -31,6 +39,25 @@ class TestJSONEncoding < ActiveSupport::TestCase
end
end
+ class OptionsTest
+ def as_json(options = :default)
+ options
+ end
+ end
+
+ class HashWithAsJson < Hash
+ attr_accessor :as_json_called
+
+ def initialize(*)
+ super
+ end
+
+ def as_json(options={})
+ @as_json_called = true
+ super
+ end
+ end
+
TrueTests = [[ true, %(true) ]]
FalseTests = [[ false, %(false) ]]
NilTests = [[ nil, %(null) ]]
@@ -40,13 +67,13 @@ class TestJSONEncoding < ActiveSupport::TestCase
[ 1.0/0.0, %(null) ],
[ -1.0/0.0, %(null) ],
[ BigDecimal('0.0')/BigDecimal('0.0'), %(null) ],
- [ BigDecimal('2.5'), %("#{BigDecimal('2.5').to_s}") ]]
+ [ BigDecimal('2.5'), %("#{BigDecimal('2.5')}") ]]
- StringTests = [[ 'this is the <string>', %("this is the \\u003Cstring\\u003E")],
+ StringTests = [[ 'this is the <string>', %("this is the \\u003cstring\\u003e")],
[ 'a "string" with quotes & an ampersand', %("a \\"string\\" with quotes \\u0026 an ampersand") ],
[ 'http://test.host/posts/1', %("http://test.host/posts/1")],
- [ "Control characters: \x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\342\200\250\342\200\251",
- %("Control characters: \\u0000\\u0001\\u0002\\u0003\\u0004\\u0005\\u0006\\u0007\\b\\t\\n\\u000B\\f\\r\\u000E\\u000F\\u0010\\u0011\\u0012\\u0013\\u0014\\u0015\\u0016\\u0017\\u0018\\u0019\\u001A\\u001B\\u001C\\u001D\\u001E\\u001F\\u2028\\u2029") ]]
+ [ "Control characters: \x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\u2028\u2029",
+ %("Control characters: \\u0000\\u0001\\u0002\\u0003\\u0004\\u0005\\u0006\\u0007\\b\\t\\n\\u000b\\f\\r\\u000e\\u000f\\u0010\\u0011\\u0012\\u0013\\u0014\\u0015\\u0016\\u0017\\u0018\\u0019\\u001a\\u001b\\u001c\\u001d\\u001e\\u001f\\u2028\\u2029") ]]
ArrayTests = [[ ['a', 'b', 'c'], %([\"a\",\"b\",\"c\"]) ],
[ [1, 'a', :b, nil, false], %([1,\"a\",\"b\",null,false]) ]]
@@ -60,8 +87,14 @@ class TestJSONEncoding < ActiveSupport::TestCase
[ :"a b", %("a b") ]]
ObjectTests = [[ Foo.new(1, 2), %({\"a\":1,\"b\":2}) ]]
- HashlikeTests = [[ Hashlike.new, %({\"a\":1}) ]]
- CustomTests = [[ Custom.new, '"custom"' ]]
+ HashlikeTests = [[ Hashlike.new, %({\"bar\":\"world\",\"foo\":\"hello\"}) ]]
+ CustomTests = [[ Custom.new("custom"), '"custom"' ],
+ [ Custom.new(nil), 'null' ],
+ [ Custom.new(:a), '"a"' ],
+ [ Custom.new([ :foo, "bar" ]), '["foo","bar"]' ],
+ [ Custom.new({ :foo => "hello", :bar => "world" }), '{"bar":"world","foo":"hello"}' ],
+ [ Custom.new(Hashlike.new), '{"bar":"world","foo":"hello"}' ],
+ [ Custom.new(Custom.new(Custom.new(:a))), '"a"' ]]
RegexpTests = [[ /^a/, '"(?-mix:^a)"' ], [/^\w{1,2}[a-z]+/ix, '"(?ix-m:^\\\\w{1,2}[a-z]+)"']]
@@ -70,8 +103,8 @@ class TestJSONEncoding < ActiveSupport::TestCase
DateTimeTests = [[ DateTime.civil(2005,2,1,15,15,10), %("2005/02/01 15:15:10 +0000") ]]
StandardDateTests = [[ Date.new(2005,2,1), %("2005-02-01") ]]
- StandardTimeTests = [[ Time.utc(2005,2,1,15,15,10), %("2005-02-01T15:15:10Z") ]]
- StandardDateTimeTests = [[ DateTime.civil(2005,2,1,15,15,10), %("2005-02-01T15:15:10+00:00") ]]
+ StandardTimeTests = [[ Time.utc(2005,2,1,15,15,10), %("2005-02-01T15:15:10.000Z") ]]
+ StandardDateTimeTests = [[ DateTime.civil(2005,2,1,15,15,10), %("2005-02-01T15:15:10.000+00:00") ]]
StandardStringTests = [[ 'this is the <string>', %("this is the <string>")]]
def sorted_json(json)
@@ -96,6 +129,13 @@ class TestJSONEncoding < ActiveSupport::TestCase
end
end
+ def test_process_status
+ # There doesn't seem to be a good way to get a handle on a Process::Status object without actually
+ # creating a child process, hence this to populate $?
+ system("not_a_real_program_#{SecureRandom.hex}")
+ assert_equal %({"exitstatus":#{$?.exitstatus},"pid":#{$?.pid}}), ActiveSupport::JSON.encode($?)
+ end
+
def test_hash_encoding
assert_equal %({\"a\":\"b\"}), ActiveSupport::JSON.encode(:a => :b)
assert_equal %({\"a\":1}), ActiveSupport::JSON.encode('a' => 1)
@@ -135,24 +175,6 @@ class TestJSONEncoding < ActiveSupport::TestCase
assert_equal "𐒑", decoded_hash['string']
end
- def test_exception_raised_when_encoding_circular_reference_in_array
- a = [1]
- a << a
- assert_raise(ActiveSupport::JSON::Encoding::CircularReferenceError) { ActiveSupport::JSON.encode(a) }
- end
-
- def test_exception_raised_when_encoding_circular_reference_in_hash
- a = { :name => 'foo' }
- a[:next] = a
- assert_raise(ActiveSupport::JSON::Encoding::CircularReferenceError) { ActiveSupport::JSON.encode(a) }
- end
-
- def test_exception_raised_when_encoding_circular_reference_in_hash_inside_array
- a = { :name => 'foo', :sub => [] }
- a[:sub] << a
- assert_raise(ActiveSupport::JSON::Encoding::CircularReferenceError) { ActiveSupport::JSON.encode(a) }
- end
-
def test_hash_key_identifiers_are_always_quoted
values = {0 => 0, 1 => 1, :_ => :_, "$" => "$", "a" => "a", :A => :A, :A0 => :A0, "A0B" => "A0B"}
assert_equal %w( "$" "A" "A0" "A0B" "_" "a" "0" "1" ).sort, object_keys(ActiveSupport::JSON.encode(values))
@@ -167,21 +189,17 @@ class TestJSONEncoding < ActiveSupport::TestCase
end
def test_time_to_json_includes_local_offset
- prev = ActiveSupport.use_standard_json_time_format
- ActiveSupport.use_standard_json_time_format = true
- with_env_tz 'US/Eastern' do
- assert_equal %("2005-02-01T15:15:10-05:00"), ActiveSupport::JSON.encode(Time.local(2005,2,1,15,15,10))
+ with_standard_json_time_format(true) do
+ with_env_tz 'US/Eastern' do
+ assert_equal %("2005-02-01T15:15:10.000-05:00"), ActiveSupport::JSON.encode(Time.local(2005,2,1,15,15,10))
+ end
end
- ensure
- ActiveSupport.use_standard_json_time_format = prev
end
def test_hash_with_time_to_json
- prev = ActiveSupport.use_standard_json_time_format
- ActiveSupport.use_standard_json_time_format = false
- assert_equal '{"time":"2009/01/01 00:00:00 +0000"}', { :time => Time.utc(2009) }.to_json
- ensure
- ActiveSupport.use_standard_json_time_format = prev
+ with_standard_json_time_format(false) do
+ assert_equal '{"time":"2009/01/01 00:00:00 +0000"}', { :time => Time.utc(2009) }.to_json
+ end
end
def test_nested_hash_with_float
@@ -196,6 +214,31 @@ class TestJSONEncoding < ActiveSupport::TestCase
end
end
+ def test_hash_like_with_options
+ h = Hashlike.new
+ json = h.to_json :only => [:foo]
+
+ assert_equal({"foo"=>"hello"}, JSON.parse(json))
+ end
+
+ def test_object_to_json_with_options
+ obj = Object.new
+ obj.instance_variable_set :@foo, "hello"
+ obj.instance_variable_set :@bar, "world"
+ json = obj.to_json :only => ["foo"]
+
+ assert_equal({"foo"=>"hello"}, JSON.parse(json))
+ end
+
+ def test_struct_to_json_with_options
+ struct = Struct.new(:foo, :bar).new
+ struct.foo = "hello"
+ struct.bar = "world"
+ json = struct.to_json :only => [:foo]
+
+ assert_equal({"foo"=>"hello"}, JSON.parse(json))
+ end
+
def test_hash_should_pass_encoding_options_to_children_in_as_json
person = {
:name => 'John',
@@ -246,12 +289,39 @@ class TestJSONEncoding < ActiveSupport::TestCase
assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json)
end
- def test_enumerable_should_pass_encoding_options_to_children_in_as_json
- people = [
- { :name => 'John', :address => { :city => 'London', :country => 'UK' }},
- { :name => 'Jean', :address => { :city => 'Paris' , :country => 'France' }}
+ People = Class.new(BasicObject) do
+ include Enumerable
+ def initialize()
+ @people = [
+ { :name => 'John', :address => { :city => 'London', :country => 'UK' }},
+ { :name => 'Jean', :address => { :city => 'Paris' , :country => 'France' }}
+ ]
+ end
+ def each(*, &blk)
+ @people.each do |p|
+ yield p if blk
+ p
+ end.each
+ end
+ end
+
+ def test_enumerable_should_generate_json_with_as_json
+ json = People.new.as_json :only => [:address, :city]
+ expected = [
+ { 'address' => { 'city' => 'London' }},
+ { 'address' => { 'city' => 'Paris' }}
]
- json = people.each.as_json :only => [:address, :city]
+
+ assert_equal(expected, json)
+ end
+
+ def test_enumerable_should_generate_json_with_to_json
+ json = People.new.to_json :only => [:address, :city]
+ assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json)
+ end
+
+ def test_enumerable_should_pass_encoding_options_to_children_in_as_json
+ json = People.new.each.as_json :only => [:address, :city]
expected = [
{ 'address' => { 'city' => 'London' }},
{ 'address' => { 'city' => 'Paris' }}
@@ -261,23 +331,39 @@ class TestJSONEncoding < ActiveSupport::TestCase
end
def test_enumerable_should_pass_encoding_options_to_children_in_to_json
- people = [
- { :name => 'John', :address => { :city => 'London', :country => 'UK' }},
- { :name => 'Jean', :address => { :city => 'Paris' , :country => 'France' }}
- ]
- json = people.each.to_json :only => [:address, :city]
+ json = People.new.each.to_json :only => [:address, :city]
assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json)
end
- def test_to_json_should_not_keep_options_around
+ def test_hash_to_json_should_not_keep_options_around
f = CustomWithOptions.new
f.foo = "hello"
f.bar = "world"
hash = {"foo" => f, "other_hash" => {"foo" => "other_foo", "test" => "other_test"}}
assert_equal({"foo"=>{"foo"=>"hello","bar"=>"world"},
- "other_hash" => {"foo"=>"other_foo","test"=>"other_test"}}, JSON.parse(hash.to_json))
+ "other_hash" => {"foo"=>"other_foo","test"=>"other_test"}}, ActiveSupport::JSON.decode(hash.to_json))
+ end
+
+ def test_array_to_json_should_not_keep_options_around
+ f = CustomWithOptions.new
+ f.foo = "hello"
+ f.bar = "world"
+
+ array = [f, {"foo" => "other_foo", "test" => "other_test"}]
+ assert_equal([{"foo"=>"hello","bar"=>"world"},
+ {"foo"=>"other_foo","test"=>"other_test"}], ActiveSupport::JSON.decode(array.to_json))
+ end
+
+ def test_hash_as_json_without_options
+ json = { foo: OptionsTest.new }.as_json
+ assert_equal({"foo" => :default}, json)
+ end
+
+ def test_array_as_json_without_options
+ json = [ OptionsTest.new ].as_json
+ assert_equal([:default], json)
end
def test_struct_encoding
@@ -302,24 +388,13 @@ class TestJSONEncoding < ActiveSupport::TestCase
assert_equal({"name" => "David",
"sub" => {
"name" => "David",
- "date" => "2010-01-01" }}, JSON.parse(json_custom))
+ "date" => "2010-01-01" }}, ActiveSupport::JSON.decode(json_custom))
assert_equal({"name" => "David", "email" => "sample@example.com"},
- JSON.parse(json_strings))
+ ActiveSupport::JSON.decode(json_strings))
assert_equal({"name" => "David", "date" => "2010-01-01"},
- JSON.parse(json_string_and_date))
- end
-
- def test_opt_out_big_decimal_string_serialization
- big_decimal = BigDecimal('2.5')
-
- begin
- ActiveSupport.encode_big_decimal_as_string = false
- assert_equal big_decimal.to_s, big_decimal.to_json
- ensure
- ActiveSupport.encode_big_decimal_as_string = true
- end
+ ActiveSupport::JSON.decode(json_string_and_date))
end
def test_nil_true_and_false_represented_as_themselves
@@ -328,16 +403,104 @@ class TestJSONEncoding < ActiveSupport::TestCase
assert_equal false, false.as_json
end
+ def test_json_gem_dump_by_passing_active_support_encoder
+ h = HashWithAsJson.new
+ h[:foo] = "hello"
+ h[:bar] = "world"
+
+ assert_equal %({"foo":"hello","bar":"world"}), JSON.dump(h)
+ assert_nil h.as_json_called
+ end
+
+ def test_json_gem_generate_by_passing_active_support_encoder
+ h = HashWithAsJson.new
+ h[:foo] = "hello"
+ h[:bar] = "world"
+
+ assert_equal %({"foo":"hello","bar":"world"}), JSON.generate(h)
+ assert_nil h.as_json_called
+ end
+
+ def test_json_gem_pretty_generate_by_passing_active_support_encoder
+ h = HashWithAsJson.new
+ h[:foo] = "hello"
+ h[:bar] = "world"
+
+ assert_equal <<EXPECTED.chomp, JSON.pretty_generate(h)
+{
+ "foo": "hello",
+ "bar": "world"
+}
+EXPECTED
+ assert_nil h.as_json_called
+ end
+
+ def test_twz_to_json_with_use_standard_json_time_format_config_set_to_false
+ with_standard_json_time_format(false) do
+ zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
+ time = ActiveSupport::TimeWithZone.new(Time.utc(2000), zone)
+ assert_equal "\"1999/12/31 19:00:00 -0500\"", ActiveSupport::JSON.encode(time)
+ end
+ end
+
+ def test_twz_to_json_with_use_standard_json_time_format_config_set_to_true
+ with_standard_json_time_format(true) do
+ zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
+ time = ActiveSupport::TimeWithZone.new(Time.utc(2000), zone)
+ assert_equal "\"1999-12-31T19:00:00.000-05:00\"", ActiveSupport::JSON.encode(time)
+ end
+ end
+
+ def test_twz_to_json_with_custom_time_precision
+ with_standard_json_time_format(true) do
+ with_time_precision(0) do
+ zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
+ time = ActiveSupport::TimeWithZone.new(Time.utc(2000), zone)
+ assert_equal "\"1999-12-31T19:00:00-05:00\"", ActiveSupport::JSON.encode(time)
+ end
+ end
+ end
+
+ def test_time_to_json_with_custom_time_precision
+ with_standard_json_time_format(true) do
+ with_time_precision(0) do
+ assert_equal "\"2000-01-01T00:00:00Z\"", ActiveSupport::JSON.encode(Time.utc(2000))
+ end
+ end
+ end
+
+ def test_datetime_to_json_with_custom_time_precision
+ with_standard_json_time_format(true) do
+ with_time_precision(0) do
+ assert_equal "\"2000-01-01T00:00:00+00:00\"", ActiveSupport::JSON.encode(DateTime.new(2000))
+ end
+ end
+ end
+
+ def test_twz_to_json_when_wrapping_a_date_time
+ zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
+ time = ActiveSupport::TimeWithZone.new(DateTime.new(2000), zone)
+ assert_equal '"1999-12-31T19:00:00.000-05:00"', ActiveSupport::JSON.encode(time)
+ end
+
protected
def object_keys(json_object)
json_object[1..-2].scan(/([^{}:,\s]+):/).flatten.sort
end
- def with_env_tz(new_tz = 'US/Eastern')
- old_tz, ENV['TZ'] = ENV['TZ'], new_tz
+ def with_standard_json_time_format(boolean = true)
+ old, ActiveSupport.use_standard_json_time_format = ActiveSupport.use_standard_json_time_format, boolean
+ yield
+ ensure
+ ActiveSupport.use_standard_json_time_format = old
+ end
+
+ def with_time_precision(value)
+ old_value = ActiveSupport::JSON::Encoding.time_precision
+ ActiveSupport::JSON::Encoding.time_precision = value
yield
ensure
- old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ')
+ ActiveSupport::JSON::Encoding.time_precision = old_value
end
end
diff --git a/activesupport/test/key_generator_test.rb b/activesupport/test/key_generator_test.rb
index 525082d313..f7e8e9a795 100644
--- a/activesupport/test/key_generator_test.rb
+++ b/activesupport/test/key_generator_test.rb
@@ -29,4 +29,34 @@ class KeyGeneratorTest < ActiveSupport::TestCase
end
end
+class CachingKeyGeneratorTest < ActiveSupport::TestCase
+ def setup
+ @secret = SecureRandom.hex(64)
+ @generator = ActiveSupport::KeyGenerator.new(@secret, :iterations=>2)
+ @caching_generator = ActiveSupport::CachingKeyGenerator.new(@generator)
+ end
+
+ test "Generating a cached key for same salt and key size" do
+ derived_key = @caching_generator.generate_key("some_salt", 32)
+ cached_key = @caching_generator.generate_key("some_salt", 32)
+
+ assert_equal derived_key, cached_key
+ assert_equal derived_key.object_id, cached_key.object_id
+ end
+
+ test "Does not cache key for different salt" do
+ derived_key = @caching_generator.generate_key("some_salt", 32)
+ different_salt_key = @caching_generator.generate_key("other_salt", 32)
+
+ assert_not_equal derived_key, different_salt_key
+ end
+
+ test "Does not cache key for different length" do
+ derived_key = @caching_generator.generate_key("some_salt", 32)
+ different_length_key = @caching_generator.generate_key("some_salt", 64)
+
+ assert_not_equal derived_key, different_length_key
+ end
+end
+
end
diff --git a/activesupport/test/message_encryptor_test.rb b/activesupport/test/message_encryptor_test.rb
index 509c453b5c..eb71369397 100644
--- a/activesupport/test/message_encryptor_test.rb
+++ b/activesupport/test/message_encryptor_test.rb
@@ -1,12 +1,5 @@
require 'abstract_unit'
-
-begin
- require 'openssl'
- OpenSSL::Digest::SHA1
-rescue LoadError, NameError
- $stderr.puts "Skipping MessageEncryptor test: broken OpenSSL install"
-else
-
+require 'openssl'
require 'active_support/time'
require 'active_support/json'
@@ -60,12 +53,23 @@ class MessageEncryptorTest < ActiveSupport::TestCase
ActiveSupport.use_standard_json_time_format = true
encryptor = ActiveSupport::MessageEncryptor.new(SecureRandom.hex(64), SecureRandom.hex(64), :serializer => JSONSerializer.new)
message = encryptor.encrypt_and_sign({ :foo => 123, 'bar' => Time.utc(2010) })
- exp = { "foo" => 123, "bar" => "2010-01-01T00:00:00Z" }
+ exp = { "foo" => 123, "bar" => "2010-01-01T00:00:00.000Z" }
assert_equal exp, encryptor.decrypt_and_verify(message)
ensure
ActiveSupport.use_standard_json_time_format = prev
end
+ def test_message_obeys_strict_encoding
+ bad_encoding_characters = "\n!@#"
+ message, iv = @encryptor.encrypt_and_sign("This is a very \n\nhumble string"+bad_encoding_characters)
+
+ assert_not_decrypted("#{::Base64.encode64 message.to_s}--#{::Base64.encode64 iv.to_s}")
+ assert_not_verified("#{::Base64.encode64 message.to_s}--#{::Base64.encode64 iv.to_s}")
+
+ assert_not_decrypted([iv, message] * bad_encoding_characters)
+ assert_not_verified([iv, message] * bad_encoding_characters)
+ end
+
private
def assert_not_decrypted(value)
@@ -81,10 +85,8 @@ class MessageEncryptorTest < ActiveSupport::TestCase
end
def munge(base64_string)
- bits = ::Base64.decode64(base64_string)
+ bits = ::Base64.strict_decode64(base64_string)
bits.reverse!
::Base64.strict_encode64(bits)
end
end
-
-end
diff --git a/activesupport/test/message_verifier_test.rb b/activesupport/test/message_verifier_test.rb
index a8633f7299..6c3519df9a 100644
--- a/activesupport/test/message_verifier_test.rb
+++ b/activesupport/test/message_verifier_test.rb
@@ -1,17 +1,10 @@
require 'abstract_unit'
-
-begin
- require 'openssl'
- OpenSSL::Digest::SHA1
-rescue LoadError, NameError
- $stderr.puts "Skipping MessageVerifier test: broken OpenSSL install"
-else
-
+require 'openssl'
require 'active_support/time'
require 'active_support/json'
class MessageVerifierTest < ActiveSupport::TestCase
-
+
class JSONSerializer
def dump(value)
ActiveSupport::JSON.encode(value)
@@ -21,45 +14,72 @@ class MessageVerifierTest < ActiveSupport::TestCase
ActiveSupport::JSON.decode(value)
end
end
-
+
def setup
@verifier = ActiveSupport::MessageVerifier.new("Hey, I'm a secret!")
@data = { :some => "data", :now => Time.local(2010) }
end
+ def test_valid_message
+ data, hash = @verifier.generate(@data).split("--")
+ assert !@verifier.valid_message?(nil)
+ assert !@verifier.valid_message?("")
+ assert !@verifier.valid_message?("#{data.reverse}--#{hash}")
+ assert !@verifier.valid_message?("#{data}--#{hash.reverse}")
+ assert !@verifier.valid_message?("purejunk")
+ end
+
def test_simple_round_tripping
message = @verifier.generate(@data)
+ assert_equal @data, @verifier.verified(message)
assert_equal @data, @verifier.verify(message)
end
- def test_missing_signature_raises
- assert_not_verified(nil)
- assert_not_verified("")
+ def test_verified_returns_false_on_invalid_message
+ assert !@verifier.verified("purejunk")
end
- def test_tampered_data_raises
- data, hash = @verifier.generate(@data).split("--")
- assert_not_verified("#{data.reverse}--#{hash}")
- assert_not_verified("#{data}--#{hash.reverse}")
- assert_not_verified("purejunk")
+ def test_verify_exception_on_invalid_message
+ assert_raise(ActiveSupport::MessageVerifier::InvalidSignature) do
+ @verifier.verify("purejunk")
+ end
end
-
+
def test_alternative_serialization_method
prev = ActiveSupport.use_standard_json_time_format
ActiveSupport.use_standard_json_time_format = true
verifier = ActiveSupport::MessageVerifier.new("Hey, I'm a secret!", :serializer => JSONSerializer.new)
message = verifier.generate({ :foo => 123, 'bar' => Time.utc(2010) })
- exp = { "foo" => 123, "bar" => "2010-01-01T00:00:00Z" }
+ exp = { "foo" => 123, "bar" => "2010-01-01T00:00:00.000Z" }
+ assert_equal exp, verifier.verified(message)
assert_equal exp, verifier.verify(message)
ensure
ActiveSupport.use_standard_json_time_format = prev
end
-
- def assert_not_verified(message)
- assert_raise(ActiveSupport::MessageVerifier::InvalidSignature) do
- @verifier.verify(message)
+
+ def test_raise_error_when_argument_class_is_not_loaded
+ # To generate the valid message below:
+ #
+ # AutoloadClass = Struct.new(:foo)
+ # valid_message = @verifier.generate(foo: AutoloadClass.new('foo'))
+ #
+ valid_message = "BAh7BjoIZm9vbzonTWVzc2FnZVZlcmlmaWVyVGVzdDo6QXV0b2xvYWRDbGFzcwY6CUBmb29JIghmb28GOgZFVA==--f3ef39a5241c365083770566dc7a9eb5d6ace914"
+ exception = assert_raise(ArgumentError, NameError) do
+ @verifier.verified(valid_message)
end
+ assert_includes ["uninitialized constant MessageVerifierTest::AutoloadClass",
+ "undefined class/module MessageVerifierTest::AutoloadClass"], exception.message
+ exception = assert_raise(ArgumentError, NameError) do
+ @verifier.verify(valid_message)
+ end
+ assert_includes ["uninitialized constant MessageVerifierTest::AutoloadClass",
+ "undefined class/module MessageVerifierTest::AutoloadClass"], exception.message
end
-end
+ def test_raise_error_when_secret_is_nil
+ exception = assert_raise(ArgumentError) do
+ ActiveSupport::MessageVerifier.new(nil)
+ end
+ assert_equal exception.message, 'Secret should not be nil.'
+ end
end
diff --git a/activesupport/test/multibyte_chars_test.rb b/activesupport/test/multibyte_chars_test.rb
index 2bf73291a2..e1c4b705f8 100644
--- a/activesupport/test/multibyte_chars_test.rb
+++ b/activesupport/test/multibyte_chars_test.rb
@@ -1,21 +1,13 @@
-# encoding: utf-8
require 'abstract_unit'
require 'multibyte_test_helpers'
require 'active_support/core_ext/string/multibyte'
-class String
- def __method_for_multibyte_testing_with_integer_result; 1; end
- def __method_for_multibyte_testing; 'result'; end
- def __method_for_multibyte_testing!; 'result'; end
- def __method_for_multibyte_testing_that_returns_nil!; end
-end
-
class MultibyteCharsTest < ActiveSupport::TestCase
include MultibyteTestHelpers
def setup
@proxy_class = ActiveSupport::Multibyte::Chars
- @chars = @proxy_class.new UNICODE_STRING
+ @chars = @proxy_class.new UNICODE_STRING.dup
end
def test_wraps_the_original_string
@@ -24,6 +16,8 @@ class MultibyteCharsTest < ActiveSupport::TestCase
end
def test_should_allow_method_calls_to_string
+ @chars.wrapped_string.singleton_class.class_eval { def __method_for_multibyte_testing; 'result'; end }
+
assert_nothing_raised do
@chars.__method_for_multibyte_testing
end
@@ -33,28 +27,35 @@ class MultibyteCharsTest < ActiveSupport::TestCase
end
def test_forwarded_method_calls_should_return_new_chars_instance
+ @chars.wrapped_string.singleton_class.class_eval { def __method_for_multibyte_testing; 'result'; end }
+
assert_kind_of @proxy_class, @chars.__method_for_multibyte_testing
assert_not_equal @chars.object_id, @chars.__method_for_multibyte_testing.object_id
end
def test_forwarded_bang_method_calls_should_return_the_original_chars_instance_when_result_is_not_nil
+ @chars.wrapped_string.singleton_class.class_eval { def __method_for_multibyte_testing!; 'result'; end }
+
assert_kind_of @proxy_class, @chars.__method_for_multibyte_testing!
assert_equal @chars.object_id, @chars.__method_for_multibyte_testing!.object_id
end
def test_forwarded_bang_method_calls_should_return_nil_when_result_is_nil
+ @chars.wrapped_string.singleton_class.class_eval { def __method_for_multibyte_testing_that_returns_nil!; end }
+
assert_nil @chars.__method_for_multibyte_testing_that_returns_nil!
end
def test_methods_are_forwarded_to_wrapped_string_for_byte_strings
- original_encoding = BYTE_STRING.encoding
assert_equal BYTE_STRING.length, BYTE_STRING.mb_chars.length
- ensure
- BYTE_STRING.force_encoding(original_encoding)
end
def test_forwarded_method_with_non_string_result_should_be_returned_vertabim
- assert_equal ''.__method_for_multibyte_testing_with_integer_result, @chars.__method_for_multibyte_testing_with_integer_result
+ str = ''
+ 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 }
+
+ assert_equal str.__method_for_multibyte_testing_with_integer_result, @chars.__method_for_multibyte_testing_with_integer_result
end
def test_should_concatenate
@@ -103,7 +104,6 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase
@chars = UNICODE_STRING.dup.mb_chars
# Ruby 1.9 only supports basic whitespace
@whitespace = "\n\t "
- @byte_order_mark = [65279].pack('U')
end
def test_split_should_return_an_array_of_chars_instances
@@ -181,7 +181,7 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase
end
def test_sortability
- words = %w(builder armor zebra).sort_by { |s| s.mb_chars }
+ words = %w(builder armor zebra).sort_by(&:mb_chars)
assert_equal %w(armor builder zebra), words
end
@@ -221,9 +221,9 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase
end
def test_include_raises_when_nil_is_passed
- @chars.include?(nil)
- flunk "Expected chars.include?(nil) to raise TypeError or NoMethodError"
- rescue Exception
+ assert_raises TypeError, NoMethodError, "Expected chars.include?(nil) to raise TypeError or NoMethodError" do
+ @chars.include?(nil)
+ end
end
def test_index_should_return_character_offset
diff --git a/activesupport/test/multibyte_conformance.rb b/activesupport/test/multibyte_conformance_test.rb
index 2baf724da4..d8704716e7 100644
--- a/activesupport/test/multibyte_conformance.rb
+++ b/activesupport/test/multibyte_conformance_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'abstract_unit'
require 'multibyte_test_helpers'
@@ -10,7 +9,6 @@ require 'tmpdir'
class Downloader
def self.download(from, to)
unless File.exist?(to)
- $stderr.puts "Downloading #{from} to #{to}"
unless File.exist?(File.dirname(to))
system "mkdir -p #{File.dirname(to)}"
end
@@ -20,8 +18,9 @@ class Downloader
target.write l
end
end
- end
- end
+ end
+ end
+ true
end
end
@@ -31,11 +30,15 @@ class MultibyteConformanceTest < ActiveSupport::TestCase
UNIDATA_URL = "http://www.unicode.org/Public/#{ActiveSupport::Multibyte::Unicode::UNICODE_VERSION}/ucd"
UNIDATA_FILE = '/NormalizationTest.txt'
CACHE_DIR = File.join(Dir.tmpdir, 'cache')
+ FileUtils.mkdir_p(CACHE_DIR)
+ RUN_P = begin
+ Downloader.download(UNIDATA_URL + UNIDATA_FILE, CACHE_DIR + UNIDATA_FILE)
+ rescue
+ end
def setup
- FileUtils.mkdir_p(CACHE_DIR)
- Downloader.download(UNIDATA_URL + UNIDATA_FILE, CACHE_DIR + UNIDATA_FILE)
@proxy = ActiveSupport::Multibyte::Chars
+ skip "Unable to download test data" unless RUN_P
end
def test_normalizations_C
@@ -111,7 +114,7 @@ class MultibyteConformanceTest < ActiveSupport::TestCase
next if (line.empty? || line =~ /^\#/)
cols, comment = line.split("#")
- cols = cols.split(";").map{|e| e.strip}.reject{|e| e.empty? }
+ cols = cols.split(";").map(&:strip).reject(&:empty?)
next unless cols.length == 5
# codepoints are in hex in the test suite, pack wants them as integers
@@ -126,4 +129,4 @@ class MultibyteConformanceTest < ActiveSupport::TestCase
def inspect_codepoints(str)
str.to_s.unpack("U*").map{|cp| cp.to_s(16) }.join(' ')
end
-end \ No newline at end of file
+end
diff --git a/activesupport/test/multibyte_proxy_test.rb b/activesupport/test/multibyte_proxy_test.rb
new file mode 100644
index 0000000000..11f5374017
--- /dev/null
+++ b/activesupport/test/multibyte_proxy_test.rb
@@ -0,0 +1,33 @@
+
+require 'abstract_unit'
+
+class MultibyteProxyText < ActiveSupport::TestCase
+ class AsciiOnlyEncoder
+ attr_reader :wrapped_string
+ alias to_s wrapped_string
+
+ def initialize(string)
+ @wrapped_string = string.gsub(/[^\u0000-\u007F]/, '?')
+ end
+ end
+
+ def with_custom_encoder(encoder)
+ original_proxy_class = ActiveSupport::Multibyte.proxy_class
+
+ begin
+ ActiveSupport::Multibyte.proxy_class = encoder
+
+ yield
+ ensure
+ ActiveSupport::Multibyte.proxy_class = original_proxy_class
+ end
+ end
+
+ test "custom multibyte encoder" do
+ with_custom_encoder(AsciiOnlyEncoder) do
+ assert_equal "s?me string 123", "søme string 123".mb_chars.to_s
+ end
+
+ assert_equal "søme string 123", "søme string 123".mb_chars.to_s
+ end
+end
diff --git a/activesupport/test/multibyte_test_helpers.rb b/activesupport/test/multibyte_test_helpers.rb
index fdbe2f4350..2e4b5cc873 100644
--- a/activesupport/test/multibyte_test_helpers.rb
+++ b/activesupport/test/multibyte_test_helpers.rb
@@ -1,9 +1,8 @@
-# encoding: utf-8
module MultibyteTestHelpers
- UNICODE_STRING = 'こにちわ'
- ASCII_STRING = 'ohayo'
- BYTE_STRING = "\270\236\010\210\245".force_encoding("ASCII-8BIT")
+ UNICODE_STRING = 'こにちわ'.freeze
+ ASCII_STRING = 'ohayo'.freeze
+ BYTE_STRING = "\270\236\010\210\245".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 bec65daf50..52ae266f01 100644
--- a/activesupport/test/multibyte_unicode_database_test.rb
+++ b/activesupport/test/multibyte_unicode_database_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'abstract_unit'
diff --git a/activesupport/test/number_helper_i18n_test.rb b/activesupport/test/number_helper_i18n_test.rb
index 65aecece71..e6925e9083 100644
--- a/activesupport/test/number_helper_i18n_test.rb
+++ b/activesupport/test/number_helper_i18n_test.rb
@@ -43,6 +43,10 @@ module ActiveSupport
:custom_units_for_number_to_human => {:mili => "mm", :centi => "cm", :deci => "dm", :unit => "m", :ten => "dam", :hundred => "hm", :thousand => "km"}
end
+ def teardown
+ I18n.backend.reload!
+ end
+
def test_number_to_i18n_currency
assert_equal("&$ - 10.00", number_to_currency(10, :locale => 'ts'))
assert_equal("(&$ - 10.00)", number_to_currency(-10, :locale => 'ts'))
@@ -50,8 +54,6 @@ module ActiveSupport
end
def test_number_to_currency_with_empty_i18n_store
- I18n.backend.store_translations 'empty', {}
-
assert_equal("$10.00", number_to_currency(10, :locale => 'empty'))
assert_equal("-$10.00", number_to_currency(-10, :locale => 'empty'))
end
@@ -80,8 +82,6 @@ module ActiveSupport
end
def test_number_with_i18n_precision_and_empty_i18n_store
- I18n.backend.store_translations 'empty', {}
-
assert_equal("123456789.123", number_to_rounded(123456789.123456789, :locale => 'empty'))
assert_equal("1.000", number_to_rounded(1.0000, :locale => 'empty'))
end
@@ -92,8 +92,6 @@ module ActiveSupport
end
def test_number_with_i18n_delimiter_and_empty_i18n_store
- I18n.backend.store_translations 'empty', {}
-
assert_equal("1,000,000.234", number_to_delimited(1000000.234, :locale => 'empty'))
end
@@ -107,8 +105,6 @@ module ActiveSupport
end
def test_number_to_i18n_percentage_and_empty_i18n_store
- I18n.backend.store_translations 'empty', {}
-
assert_equal("1.000%", number_to_percentage(1, :locale => 'empty'))
assert_equal("1.243%", number_to_percentage(1.2434, :locale => 'empty'))
assert_equal("12434.000%", number_to_percentage(12434, :locale => 'empty'))
@@ -121,8 +117,6 @@ module ActiveSupport
end
def test_number_to_i18n_human_size_with_empty_i18n_store
- I18n.backend.store_translations 'empty', {}
-
assert_equal("2 KB", number_to_human_size(2048, :locale => 'empty'))
assert_equal("42 Bytes", number_to_human_size(42, :locale => 'empty'))
end
@@ -142,8 +136,6 @@ module ActiveSupport
end
def test_number_to_human_with_empty_i18n_store
- I18n.backend.store_translations 'empty', {}
-
assert_equal "2 Thousand", number_to_human(2000, :locale => 'empty')
assert_equal "1.23 Billion", number_to_human(1234567890, :locale => 'empty')
end
diff --git a/activesupport/test/number_helper_test.rb b/activesupport/test/number_helper_test.rb
index a30e8178c7..23996ef381 100644
--- a/activesupport/test/number_helper_test.rb
+++ b/activesupport/test/number_helper_test.rb
@@ -1,5 +1,6 @@
require 'abstract_unit'
require 'active_support/number_helper'
+require 'active_support/core_ext/string/output_safety'
module ActiveSupport
module NumberHelper
@@ -79,10 +80,13 @@ module ActiveSupport
assert_equal("123.4%", number_helper.number_to_percentage(123.400, :precision => 3, :strip_insignificant_zeros => true))
assert_equal("1.000,000%", number_helper.number_to_percentage(1000, :delimiter => '.', :separator => ','))
assert_equal("1000.000 %", number_helper.number_to_percentage(1000, :format => "%n %"))
+ assert_equal("98a%", number_helper.number_to_percentage("98a"))
+ assert_equal("NaN%", number_helper.number_to_percentage(Float::NAN))
+ assert_equal("Inf%", number_helper.number_to_percentage(Float::INFINITY))
assert_equal("1000%", number_helper.number_to_percentage(1000, precision: nil))
assert_equal("1000%", number_helper.number_to_percentage(1000, precision: nil))
assert_equal("1000.1%", number_helper.number_to_percentage(1000.1, precision: nil))
- assert_equal("-0.13 %", number_helper.number_to_percentage("-0.13", precision: nil, :format => "%n %"))
+ assert_equal("-0.13 %", number_helper.number_to_percentage("-0.13", precision: nil, format: "%n %"))
end
end
@@ -98,6 +102,7 @@ module ActiveSupport
assert_equal("123,456,789.78901", number_helper.number_to_delimited(123456789.78901))
assert_equal("0.78901", number_helper.number_to_delimited(0.78901))
assert_equal("123,456.78", number_helper.number_to_delimited("123456.78"))
+ assert_equal("123,456.78", number_helper.number_to_delimited("123456.78".html_safe))
end
end
@@ -106,7 +111,6 @@ module ActiveSupport
assert_equal '12 345 678', number_helper.number_to_delimited(12345678, :delimiter => ' ')
assert_equal '12,345,678-05', number_helper.number_to_delimited(12345678.05, :separator => '-')
assert_equal '12.345.678,05', number_helper.number_to_delimited(12345678.05, :separator => ',', :delimiter => '.')
- assert_equal '12.345.678,05', number_helper.number_to_delimited(12345678.05, :delimiter => '.', :separator => ',')
end
end
@@ -128,6 +132,14 @@ module ActiveSupport
assert_equal("10.00", number_helper.number_to_rounded(9.995, :precision => 2))
assert_equal("11.00", number_helper.number_to_rounded(10.995, :precision => 2))
assert_equal("0.00", number_helper.number_to_rounded(-0.001, :precision => 2))
+
+ assert_equal("111.23460000000000000000", number_helper.number_to_rounded(111.2346, :precision => 20))
+ assert_equal("111.23460000000000000000", number_helper.number_to_rounded(Rational(1112346, 10000), :precision => 20))
+ assert_equal("111.23460000000000000000", number_helper.number_to_rounded('111.2346', :precision => 20))
+ assert_equal("111.23460000000000000000", number_helper.number_to_rounded(BigDecimal(111.2346, Float::DIG), :precision => 20))
+ assert_equal("111.2346" + "0"*96, number_helper.number_to_rounded('111.2346', :precision => 100))
+ assert_equal("111.2346", number_helper.number_to_rounded(Rational(1112346, 10000), :precision => 4))
+ assert_equal('0.00', number_helper.number_to_rounded(Rational(0, 1), :precision => 2))
end
end
@@ -160,6 +172,15 @@ module ActiveSupport
assert_equal "10.0", number_helper.number_to_rounded(9.995, :precision => 3, :significant => true)
assert_equal "9.99", number_helper.number_to_rounded(9.994, :precision => 3, :significant => true)
assert_equal "11.0", number_helper.number_to_rounded(10.995, :precision => 3, :significant => true)
+
+ assert_equal "9775.0000000000000000", number_helper.number_to_rounded(9775, :precision => 20, :significant => true )
+ assert_equal "9775.0000000000000000", number_helper.number_to_rounded(9775.0, :precision => 20, :significant => true )
+ assert_equal "9775.0000000000000000", number_helper.number_to_rounded(Rational(9775, 1), :precision => 20, :significant => true )
+ assert_equal "97.750000000000000000", number_helper.number_to_rounded(Rational(9775, 100), :precision => 20, :significant => true )
+ assert_equal "9775.0000000000000000", number_helper.number_to_rounded(BigDecimal(9775), :precision => 20, :significant => true )
+ assert_equal "9775.0000000000000000", number_helper.number_to_rounded("9775", :precision => 20, :significant => true )
+ assert_equal "9775." + "0"*96, number_helper.number_to_rounded("9775", :precision => 100, :significant => true )
+ assert_equal("97.7", number_helper.number_to_rounded(Rational(9772, 100), :precision => 3, :significant => true))
end
end
@@ -374,12 +395,6 @@ module ActiveSupport
end
end
- def test_extending_or_including_number_helper_correctly_hides_private_methods
- [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
- assert !number_helper.respond_to?(:valid_float?)
- assert number_helper.respond_to?(:valid_float?, true)
- end
- end
end
end
end
diff --git a/activesupport/test/option_merger_test.rb b/activesupport/test/option_merger_test.rb
index 9d139b61b8..4c0364e68b 100644
--- a/activesupport/test/option_merger_test.rb
+++ b/activesupport/test/option_merger_test.rb
@@ -79,6 +79,15 @@ class OptionMergerTest < ActiveSupport::TestCase
assert_equal ActiveSupport::OptionMerger, ActiveSupport::OptionMerger.new('', '').class
end
+ def test_option_merger_implicit_receiver
+ @options.with_options foo: "bar" do
+ merge! fizz: "buzz"
+ end
+
+ expected = { hello: "world", foo: "bar", fizz: "buzz" }
+ assert_equal expected, @options
+ end
+
private
def method_with_options(options = {})
options
diff --git a/activesupport/test/ordered_hash_test.rb b/activesupport/test/ordered_hash_test.rb
index c3fe89de4b..460a61613e 100644
--- a/activesupport/test/ordered_hash_test.rb
+++ b/activesupport/test/ordered_hash_test.rb
@@ -1,6 +1,6 @@
require 'abstract_unit'
require 'active_support/json'
-require 'active_support/core_ext/object/to_json'
+require 'active_support/core_ext/object/json'
require 'active_support/core_ext/hash/indifferent_access'
require 'active_support/core_ext/array/extract_options'
@@ -120,7 +120,9 @@ class OrderedHashTest < ActiveSupport::TestCase
end
def test_select
- assert_equal @keys, @ordered_hash.select { true }.map(&:first)
+ new_ordered_hash = @ordered_hash.select { true }
+ assert_equal @keys, new_ordered_hash.map(&:first)
+ assert_instance_of ActiveSupport::OrderedHash, new_ordered_hash
end
def test_delete_if
@@ -143,6 +145,7 @@ class OrderedHashTest < ActiveSupport::TestCase
assert_equal copy, @ordered_hash
assert !new_ordered_hash.keys.include?('pink')
assert @ordered_hash.keys.include?('pink')
+ assert_instance_of ActiveSupport::OrderedHash, new_ordered_hash
end
def test_clear
diff --git a/activesupport/test/rescuable_test.rb b/activesupport/test/rescuable_test.rb
index ec9d231125..bd43ad0797 100644
--- a/activesupport/test/rescuable_test.rb
+++ b/activesupport/test/rescuable_test.rb
@@ -12,6 +12,12 @@ end
class CoolError < StandardError
end
+module WeirdError
+ def self.===(other)
+ Exception === other && other.respond_to?(:weird?)
+ end
+end
+
class Stargate
attr_accessor :result
@@ -29,6 +35,10 @@ class Stargate
@result = e.message
end
+ rescue_from WeirdError do
+ @result = 'weird'
+ end
+
def dispatch(method)
send(method)
rescue Exception => e
@@ -47,6 +57,16 @@ class Stargate
raise MadRonon.new("dex")
end
+ def weird
+ StandardError.new.tap do |exc|
+ def exc.weird?
+ true
+ end
+
+ raise exc
+ end
+ end
+
def sos
@result = 'killed'
end
@@ -91,15 +111,20 @@ class RescuableTest < ActiveSupport::TestCase
assert_equal 'dex', @stargate.result
end
+ def test_rescue_from_error_dispatchers_with_case_operator
+ @stargate.dispatch :weird
+ assert_equal 'weird', @stargate.result
+ end
+
def test_rescues_defined_later_are_added_at_end_of_the_rescue_handlers_array
- expected = ["WraithAttack", "WraithAttack", "NuclearExplosion", "MadRonon"]
- result = @stargate.send(:rescue_handlers).collect {|e| e.first}
+ expected = ["WraithAttack", "WraithAttack", "NuclearExplosion", "MadRonon", "WeirdError"]
+ result = @stargate.send(:rescue_handlers).collect(&:first)
assert_equal expected, result
end
def test_children_should_inherit_rescue_definitions_from_parents_and_child_rescue_should_be_appended
- expected = ["WraithAttack", "WraithAttack", "NuclearExplosion", "MadRonon", "CoolError"]
- result = @cool_stargate.send(:rescue_handlers).collect {|e| e.first}
+ expected = ["WraithAttack", "WraithAttack", "NuclearExplosion", "MadRonon", "WeirdError", "CoolError"]
+ result = @cool_stargate.send(:rescue_handlers).collect(&:first)
assert_equal expected, result
end
end
diff --git a/activesupport/test/safe_buffer_test.rb b/activesupport/test/safe_buffer_test.rb
index 047b89be2a..4532152996 100644
--- a/activesupport/test/safe_buffer_test.rb
+++ b/activesupport/test/safe_buffer_test.rb
@@ -140,4 +140,34 @@ class SafeBufferTest < ActiveSupport::TestCase
# should still be unsafe
assert !y.html_safe?, "should not be safe"
end
+
+ test 'Should work with interpolation (array argument)' do
+ x = 'foo %s bar'.html_safe % ['qux']
+ assert_equal 'foo qux bar', x
+ end
+
+ test 'Should work with interpolation (hash argument)' do
+ x = 'foo %{x} bar'.html_safe % { x: 'qux' }
+ assert_equal 'foo qux bar', x
+ end
+
+ test 'Should escape unsafe interpolated args' do
+ x = 'foo %{x} bar'.html_safe % { x: '<br/>' }
+ assert_equal 'foo &lt;br/&gt; bar', x
+ end
+
+ test 'Should not escape safe interpolated args' do
+ x = 'foo %{x} bar'.html_safe % { x: '<br/>'.html_safe }
+ assert_equal 'foo <br/> bar', x
+ end
+
+ test 'Should interpolate to a safe string' do
+ x = 'foo %{x} bar'.html_safe % { x: 'qux' }
+ assert x.html_safe?, 'should be safe'
+ end
+
+ test 'Should not affect frozen objects when accessing characters' do
+ x = 'Hello'.html_safe
+ assert_equal x[/a/, 1], nil
+ end
end
diff --git a/activesupport/test/security_utils_test.rb b/activesupport/test/security_utils_test.rb
new file mode 100644
index 0000000000..08d2e3baa6
--- /dev/null
+++ b/activesupport/test/security_utils_test.rb
@@ -0,0 +1,9 @@
+require 'abstract_unit'
+require 'active_support/security_utils'
+
+class SecurityUtilsTest < ActiveSupport::TestCase
+ def test_secure_compare_should_perform_string_comparison
+ assert ActiveSupport::SecurityUtils.secure_compare('a', 'a')
+ assert !ActiveSupport::SecurityUtils.secure_compare('a', 'b')
+ end
+end
diff --git a/activesupport/test/subscriber_test.rb b/activesupport/test/subscriber_test.rb
new file mode 100644
index 0000000000..a88d8d9eba
--- /dev/null
+++ b/activesupport/test/subscriber_test.rb
@@ -0,0 +1,54 @@
+require 'abstract_unit'
+require 'active_support/subscriber'
+
+class TestSubscriber < ActiveSupport::Subscriber
+ attach_to :doodle
+
+ cattr_reader :events
+
+ def self.clear
+ @@events = []
+ end
+
+ def open_party(event)
+ events << event
+ end
+
+ private
+
+ def private_party(event)
+ events << event
+ end
+end
+
+# Monkey patch subscriber to test that only one subscriber per method is added.
+class TestSubscriber
+ remove_method :open_party
+ def open_party(event)
+ events << event
+ end
+end
+
+class SubscriberTest < ActiveSupport::TestCase
+ def setup
+ TestSubscriber.clear
+ end
+
+ def test_attaches_subscribers
+ ActiveSupport::Notifications.instrument("open_party.doodle")
+
+ assert_equal "open_party.doodle", TestSubscriber.events.first.name
+ end
+
+ def test_attaches_only_one_subscriber
+ ActiveSupport::Notifications.instrument("open_party.doodle")
+
+ assert_equal 1, TestSubscriber.events.size
+ end
+
+ def test_does_not_attach_private_methods
+ ActiveSupport::Notifications.instrument("private_party.doodle")
+
+ assert_equal [], TestSubscriber.events
+ end
+end
diff --git a/activesupport/test/tagged_logging_test.rb b/activesupport/test/tagged_logging_test.rb
index 27f629474e..03a63e94e8 100644
--- a/activesupport/test/tagged_logging_test.rb
+++ b/activesupport/test/tagged_logging_test.rb
@@ -79,6 +79,19 @@ class TaggedLoggingTest < ActiveSupport::TestCase
assert_equal "[OMG] Cool story bro\n[BCX] Funky time\n", @output.string
end
+ test "keeps each tag in their own instance" do
+ @other_output = StringIO.new
+ @other_logger = ActiveSupport::TaggedLogging.new(MyLogger.new(@other_output))
+ @logger.tagged("OMG") do
+ @other_logger.tagged("BCX") do
+ @logger.info "Cool story bro"
+ @other_logger.info "Funky time"
+ end
+ end
+ assert_equal "[OMG] Cool story bro\n", @output.string
+ assert_equal "[BCX] Funky time\n", @other_output.string
+ end
+
test "cleans up the taggings on flush" do
@logger.tagged("BCX") do
Thread.new do
diff --git a/activesupport/test/test_test.rb b/activesupport/test/test_case_test.rb
index 0c8cc1f883..151b623171 100644
--- a/activesupport/test/test_test.rb
+++ b/activesupport/test/test_case_test.rb
@@ -19,19 +19,37 @@ class AssertDifferenceTest < ActiveSupport::TestCase
assert_equal true, assert_not(nil)
assert_equal true, assert_not(false)
- e = assert_raises(MiniTest::Assertion) { assert_not true }
+ e = assert_raises(Minitest::Assertion) { assert_not true }
assert_equal 'Expected true to be nil or false', e.message
- e = assert_raises(MiniTest::Assertion) { assert_not true, 'custom' }
+ e = assert_raises(Minitest::Assertion) { assert_not true, 'custom' }
assert_equal 'custom', e.message
end
- def test_assert_no_difference
+ def test_assert_no_difference_pass
assert_no_difference '@object.num' do
# ...
end
end
+ def test_assert_no_difference_fail
+ error = assert_raises(Minitest::Assertion) do
+ assert_no_difference '@object.num' do
+ @object.increment
+ end
+ end
+ assert_equal "\"@object.num\" didn't change by 0.\nExpected: 0\n Actual: 1", error.message
+ end
+
+ def test_assert_no_difference_with_message_fail
+ error = assert_raises(Minitest::Assertion) do
+ assert_no_difference '@object.num', 'Object Changed' do
+ @object.increment
+ end
+ end
+ assert_equal "Object Changed.\n\"@object.num\" didn't change by 0.\nExpected: 0\n Actual: 1", error.message
+ end
+
def test_assert_difference
assert_difference '@object.num', +1 do
@object.increment
@@ -71,7 +89,7 @@ class AssertDifferenceTest < ActiveSupport::TestCase
end
def test_array_of_expressions_identify_failure
- assert_raises(MiniTest::Assertion) do
+ assert_raises(Minitest::Assertion) do
assert_difference ['@object.num', '1 + 1'] do
@object.increment
end
@@ -79,7 +97,7 @@ class AssertDifferenceTest < ActiveSupport::TestCase
end
def test_array_of_expressions_identify_failure_when_message_provided
- assert_raises(MiniTest::Assertion) do
+ assert_raises(Minitest::Assertion) do
assert_difference ['@object.num', '1 + 1'], 1, 'something went wrong' do
@object.increment
end
@@ -122,7 +140,6 @@ class SetupAndTeardownTest < ActiveSupport::TestCase
end
end
-
class SubclassSetupAndTeardownTest < SetupAndTeardownTest
setup :bar
teardown :bar
@@ -143,7 +160,6 @@ class SubclassSetupAndTeardownTest < SetupAndTeardownTest
end
end
-
class TestCaseTaggedLoggingTest < ActiveSupport::TestCase
def before_setup
require 'stringio'
@@ -156,3 +172,48 @@ class TestCaseTaggedLoggingTest < ActiveSupport::TestCase
assert_match "#{self.class}: #{name}\n", @out.string
end
end
+
+class TestOrderTest < ActiveSupport::TestCase
+ def setup
+ @original_test_order = ActiveSupport::TestCase.test_order
+ end
+
+ def teardown
+ ActiveSupport::TestCase.test_order = @original_test_order
+ end
+
+ def test_defaults_to_random
+ ActiveSupport::TestCase.test_order = nil
+
+ assert_equal :random, ActiveSupport::TestCase.test_order
+
+ assert_equal :random, ActiveSupport.test_order
+ end
+
+ def test_test_order_is_global
+ ActiveSupport::TestCase.test_order = :sorted
+
+ assert_equal :sorted, ActiveSupport.test_order
+ assert_equal :sorted, ActiveSupport::TestCase.test_order
+ assert_equal :sorted, self.class.test_order
+ assert_equal :sorted, Class.new(ActiveSupport::TestCase).test_order
+
+ ActiveSupport.test_order = :random
+
+ assert_equal :random, ActiveSupport.test_order
+ assert_equal :random, ActiveSupport::TestCase.test_order
+ assert_equal :random, self.class.test_order
+ assert_equal :random, Class.new(ActiveSupport::TestCase).test_order
+ end
+
+ def test_i_suck_and_my_tests_are_order_dependent!
+ ActiveSupport::TestCase.test_order = :random
+
+ klass = Class.new(ActiveSupport::TestCase) do
+ i_suck_and_my_tests_are_order_dependent!
+ end
+
+ assert_equal :alpha, klass.test_order
+ assert_equal :random, ActiveSupport::TestCase.test_order
+ end
+end
diff --git a/activesupport/test/testing/constant_lookup_test.rb b/activesupport/test/testing/constant_lookup_test.rb
index aca2951450..0f16419c8b 100644
--- a/activesupport/test/testing/constant_lookup_test.rb
+++ b/activesupport/test/testing/constant_lookup_test.rb
@@ -6,7 +6,6 @@ class Bar < Foo
def index; end
def self.index; end
end
-class Baz < Bar; end
module FooBar; end
class ConstantLookupTest < ActiveSupport::TestCase
@@ -66,4 +65,12 @@ class ConstantLookupTest < ActiveSupport::TestCase
}
}
end
+
+ def test_does_not_swallow_exception_on_no_name_error_within_constant
+ assert_raises(NameError) do
+ with_autoloading_fixtures do
+ self.class.determine_constant_from_test_name('RaisesNameError')
+ end
+ end
+ end
end
diff --git a/activesupport/test/testing/file_fixtures_test.rb b/activesupport/test/testing/file_fixtures_test.rb
new file mode 100644
index 0000000000..91b8a9071c
--- /dev/null
+++ b/activesupport/test/testing/file_fixtures_test.rb
@@ -0,0 +1,28 @@
+require 'abstract_unit'
+
+class FileFixturesTest < ActiveSupport::TestCase
+ self.file_fixture_path = File.expand_path("../../file_fixtures", __FILE__)
+
+ test "#file_fixture returns Pathname to file fixture" do
+ path = file_fixture("sample.txt")
+ assert_kind_of Pathname, path
+ assert_match %r{activesupport/test/file_fixtures/sample.txt$}, path.to_s
+ end
+
+ test "raises an exception when the fixture file does not exist" do
+ e = assert_raises(ArgumentError) do
+ file_fixture("nope")
+ end
+ assert_match(/^the directory '[^']+test\/file_fixtures' does not contain a file named 'nope'$/, e.message)
+ end
+end
+
+class FileFixturesPathnameDirectoryTest < ActiveSupport::TestCase
+ self.file_fixture_path = Pathname.new(File.expand_path("../../file_fixtures", __FILE__))
+
+ test "#file_fixture_path returns Pathname to file fixture" do
+ path = file_fixture("sample.txt")
+ assert_kind_of Pathname, path
+ assert_match %r{activesupport/test/file_fixtures/sample.txt$}, path.to_s
+ end
+end
diff --git a/activesupport/test/time_travel_test.rb b/activesupport/test/time_travel_test.rb
new file mode 100644
index 0000000000..676a143692
--- /dev/null
+++ b/activesupport/test/time_travel_test.rb
@@ -0,0 +1,83 @@
+require 'abstract_unit'
+require 'active_support/core_ext/date'
+require 'active_support/core_ext/date_time'
+require 'active_support/core_ext/numeric/time'
+
+class TimeTravelTest < ActiveSupport::TestCase
+ setup do
+ Time.stubs now: Time.now
+ end
+
+ teardown do
+ travel_back
+ end
+
+ def test_time_helper_travel
+ expected_time = Time.now + 1.day
+ travel 1.day
+
+ assert_equal expected_time.to_s(:db), Time.now.to_s(:db)
+ assert_equal expected_time.to_date, Date.today
+ assert_equal expected_time.to_datetime.to_s(:db), DateTime.now.to_s(:db)
+ end
+
+ def test_time_helper_travel_with_block
+ expected_time = Time.now + 1.day
+
+ travel 1.day do
+ assert_equal expected_time.to_s(:db), Time.now.to_s(:db)
+ assert_equal expected_time.to_date, Date.today
+ assert_equal expected_time.to_datetime.to_s(:db), DateTime.now.to_s(:db)
+ end
+
+ assert_not_equal expected_time.to_s(:db), Time.now.to_s(:db)
+ assert_not_equal expected_time.to_date, Date.today
+ assert_not_equal expected_time.to_datetime.to_s(:db), DateTime.now.to_s(:db)
+ end
+
+ def test_time_helper_travel_to
+ expected_time = Time.new(2004, 11, 24, 01, 04, 44)
+ travel_to expected_time
+
+ assert_equal expected_time, Time.now
+ assert_equal Date.new(2004, 11, 24), Date.today
+ assert_equal expected_time.to_datetime, DateTime.now
+ end
+
+ def test_time_helper_travel_to_with_block
+ expected_time = Time.new(2004, 11, 24, 01, 04, 44)
+
+ travel_to expected_time do
+ assert_equal expected_time, Time.now
+ assert_equal Date.new(2004, 11, 24), Date.today
+ assert_equal expected_time.to_datetime, DateTime.now
+ end
+
+ assert_not_equal expected_time, Time.now
+ assert_not_equal Date.new(2004, 11, 24), Date.today
+ assert_not_equal expected_time.to_datetime, DateTime.now
+ end
+
+ def test_time_helper_travel_back
+ expected_time = Time.new(2004, 11, 24, 01, 04, 44)
+
+ travel_to expected_time
+ assert_equal expected_time, Time.now
+ assert_equal Date.new(2004, 11, 24), Date.today
+ assert_equal expected_time.to_datetime, DateTime.now
+ travel_back
+
+ assert_not_equal expected_time, Time.now
+ assert_not_equal Date.new(2004, 11, 24), Date.today
+ assert_not_equal expected_time.to_datetime, DateTime.now
+ end
+
+ def test_travel_to_will_reset_the_usec_to_avoid_mysql_rouding
+ travel_to Time.utc(2014, 10, 10, 10, 10, 50, 999999) do
+ assert_equal 50, Time.now.sec
+ assert_equal 0, Time.now.usec
+ assert_equal 50, DateTime.now.sec
+ assert_equal 0, DateTime.now.usec
+ end
+ end
+end
diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb
index 84c3154e53..cd7e184cda 100644
--- a/activesupport/test/time_zone_test.rb
+++ b/activesupport/test/time_zone_test.rb
@@ -1,7 +1,10 @@
require 'abstract_unit'
require 'active_support/time'
+require 'time_zone_test_helpers'
class TimeZoneTest < ActiveSupport::TestCase
+ include TimeZoneTestHelpers
+
def test_utc_to_local
zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
assert_equal Time.utc(1999, 12, 31, 19), zone.utc_to_local(Time.utc(2000, 1)) # standard offset -0500
@@ -19,7 +22,7 @@ class TimeZoneTest < ActiveSupport::TestCase
assert_instance_of TZInfo::TimezonePeriod, zone.period_for_local(Time.utc(2000))
end
- ActiveSupport::TimeZone::MAPPING.keys.each do |name|
+ ActiveSupport::TimeZone::MAPPING.each_key do |name|
define_method("test_map_#{name.downcase.gsub(/[^a-z]/, '_')}_to_tzinfo") do
zone = ActiveSupport::TimeZone[name]
assert_respond_to zone.tzinfo, :period_for_local
@@ -89,14 +92,65 @@ class TimeZoneTest < ActiveSupport::TestCase
end
def test_today
- Time.stubs(:now).returns(Time.utc(2000, 1, 1, 4, 59, 59)) # 1 sec before midnight Jan 1 EST
+ travel_to(Time.utc(2000, 1, 1, 4, 59, 59)) # 1 sec before midnight Jan 1 EST
assert_equal Date.new(1999, 12, 31), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].today
- Time.stubs(:now).returns(Time.utc(2000, 1, 1, 5)) # midnight Jan 1 EST
+ travel_to(Time.utc(2000, 1, 1, 5)) # midnight Jan 1 EST
assert_equal Date.new(2000, 1, 1), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].today
- Time.stubs(:now).returns(Time.utc(2000, 1, 2, 4, 59, 59)) # 1 sec before midnight Jan 2 EST
+ travel_to(Time.utc(2000, 1, 2, 4, 59, 59)) # 1 sec before midnight Jan 2 EST
assert_equal Date.new(2000, 1, 1), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].today
- Time.stubs(:now).returns(Time.utc(2000, 1, 2, 5)) # midnight Jan 2 EST
+ 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
+ travel_to(Time.utc(2000, 1, 1, 4, 59, 59)) # 1 sec before midnight Jan 1 EST
+ assert_equal Date.new(2000, 1, 1), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].tomorrow
+ travel_to(Time.utc(2000, 1, 1, 5)) # midnight Jan 1 EST
+ assert_equal Date.new(2000, 1, 2), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].tomorrow
+ travel_to(Time.utc(2000, 1, 2, 4, 59, 59)) # 1 sec before midnight Jan 2 EST
+ 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
+ travel_to(Time.utc(2000, 1, 1, 4, 59, 59)) # 1 sec before midnight Jan 1 EST
+ assert_equal Date.new(1999, 12, 30), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].yesterday
+ travel_to(Time.utc(2000, 1, 1, 5)) # midnight Jan 1 EST
+ assert_equal Date.new(1999, 12, 31), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].yesterday
+ travel_to(Time.utc(2000, 1, 2, 4, 59, 59)) # 1 sec before midnight Jan 2 EST
+ 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
+ with_env_tz do
+ Time.use_zone('Hawaii') do
+ date = Date.new(2014, 2, 18)
+ time = date.midnight
+
+ travel_to date do
+ assert_equal date, Date.current
+ assert_equal time, Time.current
+ end
+ end
+ end
+ end
+
+ def test_travel_to_travels_back_and_reraises_if_the_block_raises
+ ts = Time.current - 1.second
+
+ travel_to ts do
+ raise
+ end
+
+ flunk # ensure travel_to re-raises
+ rescue
+ assert_not_equal ts, Time.current
end
def test_local
@@ -203,6 +257,15 @@ class TimeZoneTest < ActiveSupport::TestCase
assert_equal Time.utc(1999,12,31,19), twz.time
end
+ def test_parse_with_day_omitted
+ with_env_tz 'US/Eastern' do
+ zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
+ assert_equal Time.local(2000, 2, 1), zone.parse('Feb', Time.local(2000, 1, 1))
+ assert_equal Time.local(2005, 2, 1), zone.parse('Feb 2005', Time.local(2000, 1, 1))
+ assert_equal Time.local(2005, 2, 2), zone.parse('2 Feb 2005', Time.local(2000, 1, 1))
+ end
+ end
+
def test_parse_should_not_black_out_system_timezone_dst_jump
with_env_tz('EET') do
zone = ActiveSupport::TimeZone['Pacific Time (US & Canada)']
@@ -332,15 +395,10 @@ class TimeZoneTest < ActiveSupport::TestCase
assert_raise(ArgumentError) { ActiveSupport::TimeZone[false] }
end
- def test_unknown_zone_should_have_tzinfo_but_exception_on_utc_offset
- zone = ActiveSupport::TimeZone.create("bogus")
- assert_instance_of TZInfo::TimezoneProxy, zone.tzinfo
- assert_raise(TZInfo::InvalidTimezoneIdentifier) { zone.utc_offset }
- end
-
- def test_unknown_zone_with_utc_offset
- zone = ActiveSupport::TimeZone.create("bogus", -21_600)
- assert_equal(-21_600, zone.utc_offset)
+ def test_unknown_zone_raises_exception
+ assert_raise TZInfo::InvalidTimezoneIdentifier do
+ ActiveSupport::TimeZone.create("bogus")
+ end
end
def test_unknown_zones_dont_store_mapping_keys
@@ -356,12 +414,4 @@ class TimeZoneTest < ActiveSupport::TestCase
assert ActiveSupport::TimeZone.us_zones.include?(ActiveSupport::TimeZone["Hawaii"])
assert !ActiveSupport::TimeZone.us_zones.include?(ActiveSupport::TimeZone["Kuala Lumpur"])
end
-
- protected
- def with_env_tz(new_tz = 'US/Eastern')
- old_tz, ENV['TZ'] = ENV['TZ'], new_tz
- yield
- ensure
- old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ')
- end
end
diff --git a/activesupport/test/time_zone_test_helpers.rb b/activesupport/test/time_zone_test_helpers.rb
new file mode 100644
index 0000000000..9632b89d09
--- /dev/null
+++ b/activesupport/test/time_zone_test_helpers.rb
@@ -0,0 +1,16 @@
+module TimeZoneTestHelpers
+ def with_tz_default(tz = nil)
+ old_tz = Time.zone
+ Time.zone = tz
+ yield
+ ensure
+ Time.zone = old_tz
+ end
+
+ def with_env_tz(new_tz = 'US/Eastern')
+ old_tz, ENV['TZ'] = ENV['TZ'], new_tz
+ yield
+ ensure
+ old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ')
+ end
+end
diff --git a/activesupport/test/transliterate_test.rb b/activesupport/test/transliterate_test.rb
index e0f85f4e7c..378421fedd 100644
--- a/activesupport/test/transliterate_test.rb
+++ b/activesupport/test/transliterate_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'abstract_unit'
require 'active_support/inflector/transliterate'
@@ -11,7 +10,7 @@ class TransliterateTest < ActiveSupport::TestCase
end
def test_transliterate_should_approximate_ascii
- # create string with range of Unicode"s western characters with
+ # create string with range of Unicode's western characters with
# diacritics, excluding the division and multiplication signs which for
# some reason or other are floating in the middle of all the letters.
string = (0xC0..0x17E).to_a.reject {|c| [0xD7, 0xF7].include?(c)}.pack("U*")
diff --git a/activesupport/test/xml_mini/nokogiri_engine_test.rb b/activesupport/test/xml_mini/nokogiri_engine_test.rb
index 2e962576b5..1314c9065a 100644
--- a/activesupport/test/xml_mini/nokogiri_engine_test.rb
+++ b/activesupport/test/xml_mini/nokogiri_engine_test.rb
@@ -8,15 +8,13 @@ require 'active_support/xml_mini'
require 'active_support/core_ext/hash/conversions'
class NokogiriEngineTest < ActiveSupport::TestCase
- include ActiveSupport
-
def setup
- @default_backend = XmlMini.backend
- XmlMini.backend = 'Nokogiri'
+ @default_backend = ActiveSupport::XmlMini.backend
+ ActiveSupport::XmlMini.backend = 'Nokogiri'
end
def teardown
- XmlMini.backend = @default_backend
+ ActiveSupport::XmlMini.backend = @default_backend
end
def test_file_from_xml
@@ -56,13 +54,13 @@ class NokogiriEngineTest < ActiveSupport::TestCase
end
def test_setting_nokogiri_as_backend
- XmlMini.backend = 'Nokogiri'
- assert_equal XmlMini_Nokogiri, XmlMini.backend
+ ActiveSupport::XmlMini.backend = 'Nokogiri'
+ assert_equal ActiveSupport::XmlMini_Nokogiri, ActiveSupport::XmlMini.backend
end
def test_blank_returns_empty_hash
- assert_equal({}, XmlMini.parse(nil))
- assert_equal({}, XmlMini.parse(''))
+ assert_equal({}, ActiveSupport::XmlMini.parse(nil))
+ assert_equal({}, ActiveSupport::XmlMini.parse(''))
end
def test_array_type_makes_an_array
@@ -207,9 +205,9 @@ class NokogiriEngineTest < ActiveSupport::TestCase
private
def assert_equal_rexml(xml)
- parsed_xml = XmlMini.parse(xml)
+ parsed_xml = ActiveSupport::XmlMini.parse(xml)
xml.rewind if xml.respond_to?(:rewind)
- hash = XmlMini.with_backend('REXML') { XmlMini.parse(xml) }
+ hash = ActiveSupport::XmlMini.with_backend('REXML') { ActiveSupport::XmlMini.parse(xml) }
assert_equal(hash, parsed_xml)
end
end
diff --git a/activesupport/test/xml_mini/nokogirisax_engine_test.rb b/activesupport/test/xml_mini/nokogirisax_engine_test.rb
index 4f078f31e0..7978a50921 100644
--- a/activesupport/test/xml_mini/nokogirisax_engine_test.rb
+++ b/activesupport/test/xml_mini/nokogirisax_engine_test.rb
@@ -8,15 +8,13 @@ require 'active_support/xml_mini'
require 'active_support/core_ext/hash/conversions'
class NokogiriSAXEngineTest < ActiveSupport::TestCase
- include ActiveSupport
-
def setup
- @default_backend = XmlMini.backend
- XmlMini.backend = 'NokogiriSAX'
+ @default_backend = ActiveSupport::XmlMini.backend
+ ActiveSupport::XmlMini.backend = 'NokogiriSAX'
end
def teardown
- XmlMini.backend = @default_backend
+ ActiveSupport::XmlMini.backend = @default_backend
end
def test_file_from_xml
@@ -57,13 +55,13 @@ class NokogiriSAXEngineTest < ActiveSupport::TestCase
end
def test_setting_nokogirisax_as_backend
- XmlMini.backend = 'NokogiriSAX'
- assert_equal XmlMini_NokogiriSAX, XmlMini.backend
+ ActiveSupport::XmlMini.backend = 'NokogiriSAX'
+ assert_equal ActiveSupport::XmlMini_NokogiriSAX, ActiveSupport::XmlMini.backend
end
def test_blank_returns_empty_hash
- assert_equal({}, XmlMini.parse(nil))
- assert_equal({}, XmlMini.parse(''))
+ assert_equal({}, ActiveSupport::XmlMini.parse(nil))
+ assert_equal({}, ActiveSupport::XmlMini.parse(''))
end
def test_array_type_makes_an_array
@@ -208,9 +206,9 @@ class NokogiriSAXEngineTest < ActiveSupport::TestCase
private
def assert_equal_rexml(xml)
- parsed_xml = XmlMini.parse(xml)
+ parsed_xml = ActiveSupport::XmlMini.parse(xml)
xml.rewind if xml.respond_to?(:rewind)
- hash = XmlMini.with_backend('REXML') { XmlMini.parse(xml) }
+ hash = ActiveSupport::XmlMini.with_backend('REXML') { ActiveSupport::XmlMini.parse(xml) }
assert_equal(hash, parsed_xml)
end
end
diff --git a/activesupport/test/xml_mini/rexml_engine_test.rb b/activesupport/test/xml_mini/rexml_engine_test.rb
index 0c1f11803c..f0067ca656 100644
--- a/activesupport/test/xml_mini/rexml_engine_test.rb
+++ b/activesupport/test/xml_mini/rexml_engine_test.rb
@@ -2,19 +2,17 @@ require 'abstract_unit'
require 'active_support/xml_mini'
class REXMLEngineTest < ActiveSupport::TestCase
- include ActiveSupport
-
def test_default_is_rexml
- assert_equal XmlMini_REXML, XmlMini.backend
+ assert_equal ActiveSupport::XmlMini_REXML, ActiveSupport::XmlMini.backend
end
def test_set_rexml_as_backend
- XmlMini.backend = 'REXML'
- assert_equal XmlMini_REXML, XmlMini.backend
+ ActiveSupport::XmlMini.backend = 'REXML'
+ assert_equal ActiveSupport::XmlMini_REXML, ActiveSupport::XmlMini.backend
end
def test_parse_from_io
- XmlMini.backend = 'REXML'
+ ActiveSupport::XmlMini.backend = 'REXML'
io = StringIO.new(<<-eoxml)
<root>
good
@@ -29,9 +27,9 @@ class REXMLEngineTest < ActiveSupport::TestCase
private
def assert_equal_rexml(xml)
- parsed_xml = XmlMini.parse(xml)
+ parsed_xml = ActiveSupport::XmlMini.parse(xml)
xml.rewind if xml.respond_to?(:rewind)
- hash = XmlMini.with_backend('REXML') { XmlMini.parse(xml) }
+ hash = ActiveSupport::XmlMini.with_backend('REXML') { ActiveSupport::XmlMini.parse(xml) }
assert_equal(hash, parsed_xml)
end
end
diff --git a/activesupport/test/xml_mini_test.rb b/activesupport/test/xml_mini_test.rb
index d992028323..f49431cbbf 100644
--- a/activesupport/test/xml_mini_test.rb
+++ b/activesupport/test/xml_mini_test.rb
@@ -1,6 +1,9 @@
require 'abstract_unit'
require 'active_support/xml_mini'
require 'active_support/builder'
+require 'active_support/core_ext/array'
+require 'active_support/core_ext/hash'
+require 'active_support/core_ext/big_decimal'
module XmlMiniTest
class RenameKeyTest < ActiveSupport::TestCase
@@ -88,6 +91,61 @@ module XmlMiniTest
assert_xml "<b>Howdy</b>"
end
+ test "#to_tag should use the type value in the options hash" do
+ @xml.to_tag(:b, "blue", @options.merge(type: 'color'))
+ assert_xml( "<b type=\"color\">blue</b>" )
+ end
+
+ test "#to_tag accepts symbol types" do
+ @xml.to_tag(:b, :name, @options)
+ assert_xml( "<b type=\"symbol\">name</b>" )
+ end
+
+ test "#to_tag accepts boolean types" do
+ @xml.to_tag(:b, true, @options)
+ assert_xml( "<b type=\"boolean\">true</b>")
+ end
+
+ test "#to_tag accepts float types" do
+ @xml.to_tag(:b, 3.14, @options)
+ assert_xml( "<b type=\"float\">3.14</b>")
+ end
+
+ test "#to_tag accepts decimal types" do
+ @xml.to_tag(:b, ::BigDecimal.new("1.2"), @options)
+ assert_xml( "<b type=\"decimal\">1.2</b>")
+ end
+
+ test "#to_tag accepts date types" do
+ @xml.to_tag(:b, Date.new(2001,2,3), @options)
+ assert_xml( "<b type=\"date\">2001-02-03</b>")
+ end
+
+ test "#to_tag accepts datetime types" do
+ @xml.to_tag(:b, DateTime.new(2001,2,3,4,5,6,'+7'), @options)
+ assert_xml( "<b type=\"dateTime\">2001-02-03T04:05:06+07:00</b>")
+ end
+
+ test "#to_tag accepts time types" do
+ @xml.to_tag(:b, Time.new(1993, 02, 24, 12, 0, 0, "+09:00"), @options)
+ assert_xml( "<b type=\"dateTime\">1993-02-24T12:00:00+09:00</b>")
+ end
+
+ test "#to_tag accepts array types" do
+ @xml.to_tag(:b, ["first_name", "last_name"], @options)
+ assert_xml( "<b type=\"array\"><b>first_name</b><b>last_name</b></b>" )
+ end
+
+ test "#to_tag accepts hash types" do
+ @xml.to_tag(:b, { first_name: "Bob", last_name: "Marley" }, @options)
+ assert_xml( "<b><first-name>Bob</first-name><last-name>Marley</last-name></b>" )
+ end
+
+ test "#to_tag should not add type when skip types option is set" do
+ @xml.to_tag(:b, "Bob", @options.merge(skip_types: 1))
+ assert_xml( "<b>Bob</b>" )
+ end
+
test "#to_tag should dasherize the space when passed a string with spaces as a key" do
@xml.to_tag("New York", 33, @options)
assert_xml "<New---York type=\"integer\">33</New---York>"
@@ -97,7 +155,6 @@ module XmlMiniTest
@xml.to_tag(:"New York", 33, @options)
assert_xml "<New---York type=\"integer\">33</New---York>"
end
- # TODO: test the remaining functions hidden in #to_tag.
end
class WithBackendTest < ActiveSupport::TestCase
@@ -169,4 +226,128 @@ module XmlMiniTest
end
end
end
+
+ class ParsingTest < ActiveSupport::TestCase
+ def setup
+ @parsing = ActiveSupport::XmlMini::PARSING
+ end
+
+ def test_symbol
+ parser = @parsing['symbol']
+ assert_equal :symbol, parser.call('symbol')
+ assert_equal :symbol, parser.call(:symbol)
+ assert_equal :'123', parser.call(123)
+ assert_raises(ArgumentError) { parser.call(Date.new(2013,11,12,02,11)) }
+ end
+
+ def test_date
+ parser = @parsing['date']
+ assert_equal Date.new(2013,11,12), parser.call("2013-11-12T0211Z")
+ assert_raises(TypeError) { parser.call(1384190018) }
+ assert_raises(ArgumentError) { parser.call("not really a date") }
+ end
+
+ def test_datetime
+ parser = @parsing['datetime']
+ assert_equal Time.new(2013,11,12,02,11,00,0), parser.call("2013-11-12T02:11:00Z")
+ assert_equal DateTime.new(2013,11,12), parser.call("2013-11-12T0211Z")
+ assert_equal DateTime.new(2013,11,12,02,11), parser.call("2013-11-12T02:11Z")
+ assert_equal DateTime.new(2013,11,12,02,11), parser.call("2013-11-12T11:11+9")
+ assert_raises(ArgumentError) { parser.call("1384190018") }
+ end
+
+ def test_integer
+ parser = @parsing['integer']
+ assert_equal 123, parser.call(123)
+ assert_equal 123, parser.call(123.003)
+ assert_equal 123, parser.call("123")
+ assert_equal 0, parser.call("")
+ assert_raises(ArgumentError) { parser.call(Date.new(2013,11,12,02,11)) }
+ end
+
+ def test_float
+ parser = @parsing['float']
+ assert_equal 123, parser.call("123")
+ assert_equal 123.003, parser.call("123.003")
+ assert_equal 123.0, parser.call("123,003")
+ assert_equal 0.0, parser.call("")
+ assert_equal 123, parser.call(123)
+ assert_equal 123.05, parser.call(123.05)
+ assert_raises(ArgumentError) { parser.call(Date.new(2013,11,12,02,11)) }
+ end
+
+ def test_decimal
+ parser = @parsing['decimal']
+ assert_equal 123, parser.call("123")
+ assert_equal 123.003, parser.call("123.003")
+ assert_equal 123.0, parser.call("123,003")
+ assert_equal 0.0, parser.call("")
+ assert_equal 123, parser.call(123)
+ assert_raises(ArgumentError) { parser.call(123.04) }
+ assert_raises(ArgumentError) { parser.call(Date.new(2013,11,12,02,11)) }
+ end
+
+ def test_boolean
+ parser = @parsing['boolean']
+ [1, true, "1"].each do |value|
+ assert parser.call(value)
+ end
+
+ [0, false, "0"].each do |value|
+ assert_not parser.call(value)
+ end
+ end
+
+ def test_string
+ parser = @parsing['string']
+ assert_equal "123", parser.call(123)
+ assert_equal "123", parser.call("123")
+ assert_equal "[]", parser.call("[]")
+ assert_equal "[]", parser.call([])
+ assert_equal "{}", parser.call({})
+ assert_raises(ArgumentError) { parser.call(Date.new(2013,11,12,02,11)) }
+ end
+
+ def test_yaml
+ yaml = <<YAML
+product:
+ - sku : BL394D
+ quantity : 4
+ description : Basketball
+YAML
+ expected = {
+ "product"=> [
+ {"sku"=>"BL394D", "quantity"=>4, "description"=>"Basketball"}
+ ]
+ }
+ parser = @parsing['yaml']
+ assert_equal(expected, parser.call(yaml))
+ assert_equal({1 => 'test'}, parser.call({1 => 'test'}))
+ assert_equal({"1 => 'test'"=>nil}, parser.call("{1 => 'test'}"))
+ end
+
+ def test_base64Binary_and_binary
+ base64 = <<BASE64
+TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlz
+IHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2Yg
+dGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGlu
+dWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRo
+ZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=
+BASE64
+ expected_base64 = <<EXPECTED
+Man is distinguished, not only by his reason, but by this singular passion from
+other animals, which is a lust of the mind, that by a perseverance of delight
+in the continued and indefatigable generation of knowledge, exceeds the short
+vehemence of any carnal pleasure.
+EXPECTED
+
+ parser = @parsing['base64Binary']
+ assert_equal expected_base64.gsub(/\n/," ").strip, parser.call(base64)
+ parser.call("NON BASE64 INPUT")
+
+ parser = @parsing['binary']
+ assert_equal expected_base64.gsub(/\n/," ").strip, parser.call(base64, 'encoding' => 'base64')
+ assert_equal "IGNORED INPUT", parser.call("IGNORED INPUT", {})
+ end
+ end
end