aboutsummaryrefslogtreecommitdiffstats
path: root/activesupport
diff options
context:
space:
mode:
Diffstat (limited to 'activesupport')
-rw-r--r--activesupport/CHANGELOG.md444
-rw-r--r--activesupport/MIT-LICENSE2
-rw-r--r--activesupport/activesupport.gemspec8
-rwxr-xr-xactivesupport/bin/generate_tables6
-rw-r--r--activesupport/lib/active_support.rb9
-rw-r--r--activesupport/lib/active_support/all.rb1
-rw-r--r--activesupport/lib/active_support/backtrace_cleaner.rb2
-rw-r--r--activesupport/lib/active_support/cache.rb62
-rw-r--r--activesupport/lib/active_support/cache/file_store.rb50
-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.rb13
-rw-r--r--activesupport/lib/active_support/callbacks.rb12
-rw-r--r--activesupport/lib/active_support/configurable.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/array/access.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/array/grouping.rb12
-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.rb15
-rw-r--r--activesupport/lib/active_support/core_ext/big_decimal/yaml_conversions.rb14
-rw-r--r--activesupport/lib/active_support/core_ext/class.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/class/attribute.rb7
-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.rb17
-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.rb21
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/calculations.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/file/atomic.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/hash.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/hash/compact.rb20
-rw-r--r--activesupport/lib/active_support/core_ext/hash/conversions.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/hash/deep_merge.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/hash/except.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/hash/indifferent_access.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/hash/keys.rb32
-rw-r--r--activesupport/lib/active_support/core_ext/hash/slice.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/integer/multiple.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/kernel/reporting.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/module.rb1
-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.rb45
-rw-r--r--activesupport/lib/active_support/core_ext/module/deprecation.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/time.rb8
-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.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/object/json.rb197
-rw-r--r--activesupport/lib/active_support/core_ext/object/to_json.rb30
-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/string/access.rb62
-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.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/string/inflections.rb20
-rw-r--r--activesupport/lib/active_support/core_ext/string/output_safety.rb83
-rw-r--r--activesupport/lib/active_support/core_ext/thread.rb31
-rw-r--r--activesupport/lib/active_support/core_ext/time/calculations.rb31
-rw-r--r--activesupport/lib/active_support/dependencies.rb35
-rw-r--r--activesupport/lib/active_support/deprecation.rb2
-rw-r--r--activesupport/lib/active_support/deprecation/behaviors.rb23
-rw-r--r--activesupport/lib/active_support/duration.rb20
-rw-r--r--activesupport/lib/active_support/file_update_checker.rb2
-rw-r--r--activesupport/lib/active_support/hash_with_indifferent_access.rb12
-rw-r--r--activesupport/lib/active_support/i18n_railtie.rb11
-rw-r--r--activesupport/lib/active_support/inflector/inflections.rb34
-rw-r--r--activesupport/lib/active_support/inflector/methods.rb27
-rw-r--r--activesupport/lib/active_support/json/decoding.rb14
-rw-r--r--activesupport/lib/active_support/json/encoding.rb410
-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.rb8
-rw-r--r--activesupport/lib/active_support/message_verifier.rb7
-rw-r--r--activesupport/lib/active_support/multibyte/chars.rb3
-rw-r--r--activesupport/lib/active_support/multibyte/unicode.rb79
-rw-r--r--activesupport/lib/active_support/notifications.rb2
-rw-r--r--activesupport/lib/active_support/notifications/fanout.rb11
-rw-r--r--activesupport/lib/active_support/notifications/instrumenter.rb3
-rw-r--r--activesupport/lib/active_support/number_helper.rb333
-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.rb21
-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.rb92
-rw-r--r--activesupport/lib/active_support/ordered_hash.rb4
-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/subscriber.rb29
-rw-r--r--activesupport/lib/active_support/tagged_logging.rb1
-rw-r--r--activesupport/lib/active_support/test_case.rb25
-rw-r--r--activesupport/lib/active_support/testing/declarative.rb17
-rw-r--r--activesupport/lib/active_support/testing/deprecation.rb21
-rw-r--r--activesupport/lib/active_support/testing/isolation.rb11
-rw-r--r--activesupport/lib/active_support/testing/time_helpers.rb109
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb23
-rw-r--r--activesupport/lib/active_support/values/time_zone.rb56
-rw-r--r--activesupport/lib/active_support/values/unicode_tables.datbin904483 -> 904640 bytes
-rw-r--r--activesupport/lib/active_support/version.rb2
-rw-r--r--activesupport/lib/active_support/xml_mini.rb6
-rw-r--r--activesupport/test/abstract_unit.rb14
-rw-r--r--activesupport/test/autoloading_fixtures/html/some_class.rb4
-rw-r--r--activesupport/test/caching_test.rb64
-rw-r--r--activesupport/test/callbacks_test.rb21
-rw-r--r--activesupport/test/configurable_test.rb14
-rw-r--r--activesupport/test/core_ext/array_ext_test.rb18
-rw-r--r--activesupport/test/core_ext/big_decimal/yaml_conversions_test.rb11
-rw-r--r--activesupport/test/core_ext/bigdecimal_test.rb13
-rw-r--r--activesupport/test/core_ext/blank_test.rb22
-rw-r--r--activesupport/test/core_ext/class/attribute_accessor_test.rb61
-rw-r--r--activesupport/test/core_ext/class/delegating_attributes_test.rb5
-rw-r--r--activesupport/test/core_ext/date_ext_test.rb17
-rw-r--r--activesupport/test/core_ext/duration_test.rb37
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb53
-rw-r--r--activesupport/test/core_ext/kernel_test.rb16
-rw-r--r--activesupport/test/core_ext/module/attribute_accessor_test.rb26
-rw-r--r--activesupport/test/core_ext/module/concerning_test.rb35
-rw-r--r--activesupport/test/core_ext/module_test.rb16
-rw-r--r--activesupport/test/core_ext/name_error_test.rb12
-rw-r--r--activesupport/test/core_ext/numeric_ext_test.rb53
-rw-r--r--activesupport/test/core_ext/object/json_test.rb9
-rw-r--r--activesupport/test/core_ext/object_and_class_ext_test.rb25
-rw-r--r--activesupport/test/core_ext/range_ext_test.rb28
-rw-r--r--activesupport/test/core_ext/string_ext_test.rb155
-rw-r--r--activesupport/test/core_ext/thread_test.rb16
-rw-r--r--activesupport/test/core_ext/time_ext_test.rb7
-rw-r--r--activesupport/test/core_ext/time_with_zone_test.rb34
-rw-r--r--activesupport/test/dependencies_test.rb89
-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/fixtures/custom.rb2
-rw-r--r--activesupport/test/inflector_test.rb25
-rw-r--r--activesupport/test/inflector_test_cases.rb7
-rw-r--r--activesupport/test/json/decoding_test.rb32
-rw-r--r--activesupport/test/json/encoding_test.rb260
-rw-r--r--activesupport/test/message_encryptor_test.rb15
-rw-r--r--activesupport/test/message_verifier_test.rb24
-rw-r--r--activesupport/test/multibyte_chars_test.rb6
-rw-r--r--activesupport/test/number_helper_test.rb21
-rw-r--r--activesupport/test/ordered_hash_test.rb2
-rw-r--r--activesupport/test/safe_buffer_test.rb25
-rw-r--r--activesupport/test/subscriber_test.rb40
-rw-r--r--activesupport/test/test_test.rb74
-rw-r--r--activesupport/test/testing/constant_lookup_test.rb1
-rw-r--r--activesupport/test/time_zone_test.rb33
-rw-r--r--activesupport/test/xml_mini_test.rb124
150 files changed, 3694 insertions, 1762 deletions
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index be97b744c8..fa2f2384f9 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,3 +1,420 @@
+* Deprecate custom `BigDecimal` serialization
+
+ Deprecate the custom `BigDecimal` serialization that is included when requiring
+ `active_support/all` as a fix for #12467. Let Ruby handle YAML serialization
+ for `BigDecimal` instead.
+
+ *David Celis*
+
+* Fix parsing bugs in `XmlMini`
+
+ Symbols or boolean parsing would raise an error for non string values (e.g.
+ integers). Decimal parsing would fail due to a missing requirement.
+
+ *Birkir A. Barkarson*
+
+* Maintain the current timezone when calling `wrap_with_time_zone`
+
+ Extend the solution from the fix for #12163 to the general case where `Time`
+ methods are wrapped with a time zone.
+
+ Fixes #12596.
+
+ *Andrew White*
+
+* Remove behavior that automatically remove the Date/Time stubs, added by `travel`
+ and `travel_to` methods, after each test case.
+
+ Now users have to use the `travel_back` or the block version of `travel` and
+ `travel_to` methods to clean the stubs.
+
+ *Rafael Mendonça França*
+
+* Add `travel_back` to remove stubs from `travel` and `travel_to`.
+
+ *Rafael Mendonça França*
+
+* Remove the deprecation about the `#filter` method.
+
+ Filter objects should now rely on method corresponding to the filter type
+ (e.g. `#before`).
+
+ *Aaron Patterson*
+
+* Add `ActiveSupport::JSON::Encoding.time_precision` as a way to configure the
+ precision of encoded time values:
+
+ Time.utc(2000, 1, 1).as_json # => "2000-01-01T00:00:00.000Z"
+ ActiveSupport::JSON::Encoding.time_precision = 0
+ Time.utc(2000, 1, 1).as_json # => "2000-01-01T00:00:00Z"
+
+ *Parker Selbert*
+
+* Maintain the current timezone when calling `change` during DST overlap
+
+ Currently if a time is changed during DST overlap in the autumn then the method
+ `period_for_local` will return the DST period. However if the original time is
+ not DST then this can be surprising and is not what is generally wanted. This
+ commit changes that behavior to maintain the current period if it's in the list
+ of periods returned by `periods_for_local`.
+
+ Fixes #12163.
+
+ *Andrew White*
+
+* Added `Hash#compact` and `Hash#compact!` for removing items with nil value from hash.
+
+ *Celestino Gomes*
+
+* Maintain proleptic gregorian in Time#advance
+
+ `Time#advance` uses `Time#to_date` and `Date#advance` to calculate a new date.
+ The `Date` object returned by `Time#to_date` is constructed with the assumption
+ that the `Time` object represents a proleptic gregorian date, but it is
+ configured to observe the default julian calendar reform date (2299161j)
+ for purposes of calculating month, date and year:
+
+ Time.new(1582, 10, 4).to_date.to_s # => "1582-09-24"
+ Time.new(1582, 10, 4).to_date.gregorian.to_s # => "1582-10-04"
+
+ This patch ensures that when the intermediate `Date` object is advanced
+ to yield a new `Date` object, that the `Time` object for return is constructed
+ with a proleptic gregorian month, date and year.
+
+ *Riley Lynch*
+
+* `MemCacheStore` should only accept a `Dalli::Client`, or create one.
+
+ *arthurnn*
+
+* Don't lazy load the `tzinfo` library as it causes problems on Windows.
+
+ Fixes #13553.
+
+ *Andrew White*
+
+* Use `remove_possible_method` instead of `remove_method` to avoid
+ a `NameError` to be thrown on FreeBSD with the `Date` object.
+
+ *Rafael Mendonça França*, *Robin Dupret*
+
+* `blank?` and `present?` commit to return singletons.
+
+ *Xavier Noria*, *Pavel Pravosud*
+
+* Fixed Float related error in NumberHelper with large precisions.
+
+ Before:
+
+ ActiveSupport::NumberHelper.number_to_rounded '3.14159', precision: 50
+ #=> "3.14158999999999988261834005243144929409027099609375"
+
+ After:
+
+ ActiveSupport::NumberHelper.number_to_rounded '3.14159', precision: 50
+ #=> "3.14159000000000000000000000000000000000000000000000"
+
+ *Kenta Murata*, *Akira Matsuda*
+
+* Default the new `I18n.enforce_available_locales` config to `true`, meaning
+ `I18n` will make sure that all locales passed to it must be declared in the
+ `available_locales` list.
+
+ To disable it add the following configuration to your application:
+
+ config.i18n.enforce_available_locales = false
+
+ This also ensures I18n configuration is properly initialized taking the new
+ option into account, to avoid their deprecations while booting up the app.
+
+ *Carlos Antonio da Silva*, *Yves Senn*
+
+* Introduce Module#concerning: a natural, low-ceremony way to separate
+ responsibilities within a class.
+
+ Imported from https://github.com/37signals/concerning#readme
+
+ class Todo < ActiveRecord::Base
+ concerning :EventTracking do
+ included do
+ has_many :events
+ end
+
+ def latest_event
+ ...
+ end
+
+ private
+ def some_internal_method
+ ...
+ end
+ end
+
+ concerning :Trashable do
+ def trashed?
+ ...
+ end
+
+ def latest_event
+ super some_option: true
+ end
+ end
+ end
+
+ is equivalent to defining these modules inline, extending them into
+ concerns, then mixing them in to the class.
+
+ Inline concerns tame "junk drawer" classes that intersperse many unrelated
+ class-level declarations, public instance methods, and private
+ implementation. Coalesce related bits and give them definition.
+ These are a stepping stone toward future growth & refactoring.
+
+ When to move on from an inline concern:
+ * Encapsulating state? Extract collaborator object.
+ * Encompassing more public behavior or implementation? Move to separate file.
+ * Sharing behavior among classes? Move to separate file.
+
+ *Jeremy Kemper*
+
+* Fix file descriptor being leaked on each call to `Kernel.silence_stream`.
+
+ *Mario Visic*
+
+* Added `Date#all_week/month/quarter/year` for generating date ranges.
+
+ *Dmitriy Meremyanin*
+
+* Add `Time.zone.yesterday` and `Time.zone.tomorrow`. These follow the
+ behavior of Ruby's `Date.yesterday` and `Date.tomorrow` but return localized
+ versions, similar to how `Time.zone.today` has returned a localized version
+ of `Date.today`.
+
+ *Colin Bartlett*
+
+* Show valid keys when `assert_valid_keys` raises an exception, and show the
+ wrong value as it was entered.
+
+ *Gonzalo Rodríguez-Baltanás Díaz*
+
+* Both `cattr_*` and `mattr_*` method definitions now live in `active_support/core_ext/module/attribute_accessors`.
+
+ Requires to `active_support/core_ext/class/attribute_accessors` are
+ deprecated and will be removed in Ruby on Rails 4.2.
+
+ *Genadi Samokovarov*
+
+* Deprecated `Numeric#{ago,until,since,from_now}`, the user is expected to explicitly
+ convert the value into an AS::Duration, i.e. `5.ago` => `5.seconds.ago`
+
+ This will help to catch subtle bugs like:
+
+ def recent?(days = 3)
+ self.created_at >= days.ago
+ end
+
+ The above code would check if the model is created within the last 3 **seconds**.
+
+ In the future, `Numeric#{ago,until,since,from_now}` should be removed completely,
+ or throw some sort of errors to indicate there are no implicit conversion from
+ Numeric to AS::Duration.
+
+ *Godfrey Chan*
+
+* Requires JSON gem version 1.7.7 or above due to a security issue in older versions.
+
+ *Godfrey Chan*
+
+* Removed the old pure-Ruby JSON encoder and switched to a new encoder based on the built-in JSON
+ gem.
+
+ Support for encoding `BigDecimal` as a JSON number, as well as defining custom `encode_json`
+ methods to control the JSON output has been **removed from core**. The new encoder will always
+ encode BigDecimals as `String`s and ignore any custom `encode_json` methods.
+
+ The old encoder has been extracted into the `activesupport-json_encoder` gem. Installing that
+ gem will bring back the ability to encode `BigDecimal`s as numbers as well as `encode_json`
+ support.
+
+ Setting the related configuration `ActiveSupport.encode_big_decimal_as_string` without the
+ `activesupport-json_encoder` gem installed will raise an error.
+
+ *Godfrey Chan*
+
+* Add `ActiveSupport::Testing::TimeHelpers#travel` and `#travel_to`. These methods change current
+ time to the given time or time difference by stubbing `Time.now` and `Date.today` to return the
+ time or date after the difference calculation, or the time or date that got passed into the
+ method respectively.
+
+ Example for `#travel`:
+
+ Time.now # => 2013-11-09 15:34:49 -05:00
+ travel 1.day
+ Time.now # => 2013-11-10 15:34:49 -05:00
+ Date.today # => Sun, 10 Nov 2013
+
+ Example for `#travel_to`:
+
+ Time.now # => 2013-11-09 15:34:49 -05:00
+ travel_to Time.new(2004, 11, 24, 01, 04, 44)
+ Time.now # => 2004-11-24 01:04:44 -05:00
+ Date.today # => Wed, 24 Nov 2004
+
+ Both of these methods also accept a block, which will return the current time back to its
+ original state at the end of the block:
+
+ Time.now # => 2013-11-09 15:34:49 -05:00
+
+ travel 1.day do
+ User.create.created_at # => Sun, 10 Nov 2013 15:34:49 EST -05:00
+ end
+
+ travel_to Time.new(2004, 11, 24, 01, 04, 44) do
+ User.create.created_at # => Wed, 24 Nov 2004 01:04:44 EST -05:00
+ end
+
+ Time.now # => 2013-11-09 15:34:49 -05:00
+
+ This module is included in `ActiveSupport::TestCase` automatically.
+
+ *Prem Sichanugrist*, *DHH*
+
+* Unify `cattr_*` interface: allow to pass a block to `cattr_reader`.
+
+ Example:
+
+ class A
+ cattr_reader(:defr) { 'default_reader_value' }
+ end
+ A.defr # => 'default_reader_value'
+
+ *Alexey Chernenkov*
+
+* Improved compatibility with the stdlib JSON gem.
+
+ Previously, calling `::JSON.{generate,dump}` sometimes causes unexpected
+ failures such as intridea/multi_json#86.
+
+ `::JSON.{generate,dump}` now bypasses the ActiveSupport JSON encoder
+ completely and yields the same result with or without ActiveSupport. This
+ means that it will **not** call `as_json` and will ignore any options that
+ the JSON gem does not natively understand. To invoke ActiveSupport's JSON
+ encoder instead, use `obj.to_json(options)` or
+ `ActiveSupport::JSON.encode(obj, options)`.
+
+ *Godfrey Chan*
+
+* Fix Active Support `Time#to_json` and `DateTime#to_json` to return 3 decimal
+ places worth of fractional seconds, similar to `TimeWithZone`.
+
+ *Ryan Glover*
+
+* Removed circular reference protection in JSON encoder, deprecated
+ `ActiveSupport::JSON::Encoding::CircularReferenceError`.
+
+ *Godfrey Chan*, *Sergio Campamá*
+
+* Add `capitalize` option to `Inflector.humanize`, so strings can be humanized without being capitalized:
+
+ 'employee_salary'.humanize # => "Employee salary"
+ 'employee_salary'.humanize(capitalize: false) # => "employee salary"
+
+ *claudiob*
+
+* Fixed `Object#as_json` and `Struct#as_json` not working properly with options. They now take
+ the same options as `Hash#as_json`:
+
+ struct = Struct.new(:foo, :bar).new
+ struct.foo = "hello"
+ struct.bar = "world"
+ json = struct.as_json(only: [:foo]) # => {foo: "hello"}
+
+ *Sergio Campamá*, *Godfrey Chan*
+
+* Added `Numeric#in_milliseconds`, like `1.hour.in_milliseconds`, so we can feed them to JavaScript functions like `getTime()`.
+
+ *DHH*
+
+* Calling `ActiveSupport::JSON.decode` with unsupported options now raises an error.
+
+ *Godfrey Chan*
+
+* Support `:unless_exist` in `FileStore`.
+
+ *Michael Grosser*
+
+* Fix `slice!` deleting the default value of the hash.
+
+ *Antonio Santos*
+
+* `require_dependency` accepts objects that respond to `to_path`, in
+ particular `Pathname` instances.
+
+ *Benjamin Fleischer*
+
+* Disable the ability to iterate over Range of AS::TimeWithZone
+ due to significant performance issues.
+
+ *Bogdan Gusiev*
+
+* Allow attaching event subscribers to ActiveSupport::Notifications namespaces
+ before they're defined. Essentially, this means instead of this:
+
+ class JokeSubscriber < ActiveSupport::Subscriber
+ def sql(event)
+ puts "A rabbi and a priest walk into a bar..."
+ end
+
+ # This call needs to happen *after* defining the methods.
+ attach_to "active_record"
+ end
+
+ You can do this:
+
+ class JokeSubscriber < ActiveSupport::Subscriber
+ # This is much easier to read!
+ attach_to "active_record"
+
+ def sql(event)
+ puts "A rabbi and a priest walk into a bar..."
+ end
+ end
+
+ This should make it easier to read and understand these subscribers.
+
+ *Daniel Schierbeck*
+
+* Add `Date#middle_of_day`, `DateTime#middle_of_day` and `Time#middle_of_day` methods.
+
+ Also added `midday`, `noon`, `at_midday`, `at_noon` and `at_middle_of_day` as aliases.
+
+ *Anatoli Makarevich*
+
+* Fix ActiveSupport::Cache::FileStore#cleanup to no longer rely on missing each_key method.
+
+ *Murray Steele*
+
+* Ensure that autoloaded constants in all-caps nestings are marked as
+ autoloaded.
+
+ *Simon Coffey*
+
+* 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*
@@ -19,19 +436,21 @@
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
+ 1.9.3 and 2.0.
+
+ Fixes #11512.
*Simeon Simeonov*
* Only raise `Module::DelegationError` if it's the source of the exception.
- Fixes #10559
+ Fixes #10559.
*Andrew White*
* Make `Time.at_with_coercion` retain the second fraction and return local time.
- Fixes #11350
+ Fixes #11350.
*Neer Friedman*, *Andrew White*
@@ -48,7 +467,7 @@
*Arun Agrawal*
-* Remove deprecated `DateTime.local_offset` in favor of `DateTime.civil_from_fromat`.
+* Remove deprecated `DateTime.local_offset` in favor of `DateTime.civil_from_format`.
*Arun Agrawal*
@@ -85,32 +504,33 @@
*Carlos Antonio da Silva*
-* Remove deprecated `BufferedLogger`.
+* Remove deprecated `BufferedLogger`, use `ActiveSupport::Logger` instead.
*Yves Senn*
-* Remove deprecated `assert_present` and `assert_blank` methods.
+* Remove deprecated `assert_present` and `assert_blank` methods, use `assert
+ object.blank?` and `assert object.present?` instead.
*Yves Senn*
* Fix return value from `BacktraceCleaner#noise` when the cleaner is configured
with multiple silencers.
- Fixes #11030
+ Fixes #11030.
*Mark J. Titorenko*
* `HashWithIndifferentAccess#select` now returns a `HashWithIndifferentAccess`
instance instead of a `Hash` instance.
- Fixes #10723
+ Fixes #10723.
*Albert Llop*
* Add `DateTime#usec` and `DateTime#nsec` so that `ActiveSupport::TimeWithZone` keeps
sub-second resolution when wrapping a `DateTime` value.
- Fixes #10855
+ Fixes #10855.
*Andrew White*
@@ -126,10 +546,14 @@
* Prevent side effects to hashes inside arrays when
`Hash#with_indifferent_access` is called.
- Fixes #10526
+ Fixes #10526.
*Yves Senn*
+* Removed deprecated `ActiveSupport::JSON::Variable` with no replacement.
+
+ *Toshinori Kajihara*
+
* 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.
diff --git a/activesupport/MIT-LICENSE b/activesupport/MIT-LICENSE
index 6aeeb7132d..d06d4f3b2d 100644
--- a/activesupport/MIT-LICENSE
+++ b/activesupport/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2005-2013 David Heinemeier Hansson
+Copyright (c) 2005-2014 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/activesupport.gemspec b/activesupport/activesupport.gemspec
index ffc2c2074e..f3625e8b79 100644
--- a/activesupport/activesupport.gemspec
+++ b/activesupport/activesupport.gemspec
@@ -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 'i18n', '~> 0.6', '>= 0.6.9'
+ 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.1'
end
diff --git a/activesupport/bin/generate_tables b/activesupport/bin/generate_tables
index 5fefa429df..f39e89b7d0 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)
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index 5e1fe9e556..ab0054b339 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-2014 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,12 @@ module ActiveSupport
autoload :Rescuable
autoload :SafeBuffer, "active_support/core_ext/string/output_safety"
autoload :TestCase
+
+ def self.eager_load!
+ super
+
+ NumberHelper.eager_load!
+ end
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..d58578b7bc 100644
--- a/activesupport/lib/active_support/backtrace_cleaner.rb
+++ b/activesupport/lib/active_support/backtrace_cleaner.rb
@@ -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.
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index 308a1b2cd9..2b7f5943b5 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
@@ -234,7 +232,7 @@ 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.
# The key is to keep <tt>:race_condition_ttl</tt> small.
@@ -363,7 +361,7 @@ module ActiveSupport
#
# cache.write("bim", "bam")
# cache.fetch_multi("bim", "boom") {|key| key * 2 }
- # #=> ["bam", "boomboom"]
+ # # => ["bam", "boomboom"]
#
def fetch_multi(*names)
options = names.extract_options!
@@ -385,6 +383,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 +395,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 +452,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)
@@ -585,6 +586,7 @@ module ActiveSupport
result = instrument(:generate, name, options) do |payload|
yield(name)
end
+
write(name, result, options)
result
end
@@ -608,6 +610,7 @@ 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
@@ -658,6 +661,7 @@ module ActiveSupport
# 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 +676,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
@@ -696,10 +702,12 @@ module ActiveSupport
@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
diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb
index 472f23c1c5..8ed60aebac 100644
--- a/activesupport/lib/active_support/cache/file_store.rb
+++ b/activesupport/lib/active_support/cache/file_store.rb
@@ -22,18 +22,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 +43,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 +77,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
@@ -182,6 +164,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..4eaf57f385 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 'rack/body_proxy'
module ActiveSupport
module Cache
@@ -23,6 +24,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
@@ -80,9 +84,14 @@ module ActiveSupport
def call(env)
LocalCacheRegistry.set_cache_for(local_cache_key, LocalStore.new)
- @app.call(env)
- ensure
+ response = @app.call(env)
+ response[2] = ::Rack::BodyProxy.new(response[2]) do
+ LocalCacheRegistry.set_cache_for(local_cache_key, nil)
+ end
+ response
+ rescue Exception
LocalCacheRegistry.set_cache_for(local_cache_key, nil)
+ raise
end
end
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index 5c738572a8..e14ece7f35 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -7,14 +7,14 @@ require 'active_support/core_ext/kernel/singleton_class'
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+).
@@ -89,7 +89,7 @@ module ActiveSupport
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)
@@ -574,10 +574,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
@@ -648,7 +648,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
diff --git a/activesupport/lib/active_support/configurable.rb b/activesupport/lib/active_support/configurable.rb
index e0d39d509f..3dd44e32d8 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__
diff --git a/activesupport/lib/active_support/core_ext/array/access.rb b/activesupport/lib/active_support/core_ext/array/access.rb
index 4f1e432b61..67f58bc0fe 100644
--- a/activesupport/lib/active_support/core_ext/array/access.rb
+++ b/activesupport/lib/active_support/core_ext/array/access.rb
@@ -48,6 +48,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/grouping.rb b/activesupport/lib/active_support/core_ext/array/grouping.rb
index 37f007c751..3529d57174 100644
--- a/activesupport/lib/active_support/core_ext/array/grouping.rb
+++ b/activesupport/lib/active_support/core_ext/array/grouping.rb
@@ -53,7 +53,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 +83,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 +95,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..843c592669 100644
--- a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
@@ -1,22 +1,7 @@
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)
diff --git a/activesupport/lib/active_support/core_ext/big_decimal/yaml_conversions.rb b/activesupport/lib/active_support/core_ext/big_decimal/yaml_conversions.rb
new file mode 100644
index 0000000000..46ba93ead4
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/big_decimal/yaml_conversions.rb
@@ -0,0 +1,14 @@
+ActiveSupport::Deprecation.warn 'core_ext/big_decimal/yaml_conversions is deprecated and will be removed in the future.'
+
+require 'bigdecimal'
+require 'yaml'
+require 'active_support/core_ext/big_decimal/conversions'
+
+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
+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..f2a221c396 100644
--- a/activesupport/lib/active_support/core_ext/class/attribute.rb
+++ b/activesupport/lib/active_support/core_ext/class/attribute.rb
@@ -118,7 +118,10 @@ class Class
end
private
- def singleton_class?
- ancestors.first != self
+
+ unless respond_to?(:singleton_class?)
+ def singleton_class?
+ ancestors.first != self
+ end
end
end
diff --git a/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb
index 34859617c9..083b165dce 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,6 @@
-require 'active_support/core_ext/array/extract_options'
+require 'active_support/deprecation'
+require 'active_support/core_ext/module/attribute_accessors'
-# 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
+ActiveSupport::Deprecation.warn(
+ "The cattr_* method definitions have been moved into active_support/core_ext/module/attribute_accessors. Please require that instead."
+)
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..c2219beb5a 100644
--- a/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb
+++ b/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb
@@ -4,20 +4,21 @@ require 'active_support/core_ext/module/remove_method'
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
private
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..b85e49aca5 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
@@ -213,6 +213,27 @@ module DateAndTime
end
alias :at_end_of_year :end_of_year
+ # 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
+
+ # Returns a Range representing the whole month of the current date/time.
+ def all_month
+ beginning_of_month..end_of_month
+ end
+
+ # Returns a Range representing the whole quarter of the current date/time.
+ def all_quarter
+ beginning_of_quarter..end_of_quarter
+ end
+
+ # 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)
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..73ad0aa097 100644
--- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
@@ -151,7 +151,11 @@ 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.respond_to? :to_datetime
+ super other.to_datetime
+ else
+ nil
+ end
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..0e7e3ba378 100644
--- a/activesupport/lib/active_support/core_ext/file/atomic.rb
+++ b/activesupport/lib/active_support/core_ext/file/atomic.rb
@@ -23,7 +23,7 @@ class File
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
diff --git a/activesupport/lib/active_support/core_ext/hash.rb b/activesupport/lib/active_support/core_ext/hash.rb
index 686f12c6da..f68e1662f9 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'
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..6566215a4d
--- /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..7bea461c77 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
@@ -235,6 +238,7 @@ 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..dc86c92003 100644
--- a/activesupport/lib/active_support/core_ext/hash/deep_merge.rb
+++ b/activesupport/lib/active_support/core_ext/hash/deep_merge.rb
@@ -1,13 +1,13 @@
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 = { x: { y: [4, 5, 6] }, z: [7, 8, 9] }
+ # h2 = { x: { y: [7, 8, 9] }, z: 'xyz' }
#
- # 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) # => {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"]}
+ # # => {:x=>{:y=>[4, 5, 6, 7, 8, 9]}, :z=>[7, 8, 9, "xyz"]}
def deep_merge(other_hash, &block)
dup.deep_merge!(other_hash, &block)
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..682d089881 100644
--- a/activesupport/lib/active_support/core_ext/hash/except.rb
+++ b/activesupport/lib/active_support/core_ext/hash/except.rb
@@ -1,5 +1,5 @@
class Hash
- # Return a hash that includes everything but the given keys. This is useful for
+ # Returns 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:
#
# @person.update(params[:person].except(:admin))
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..970d6faa1d 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"=>32}
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..3d41aa8572 100644
--- a/activesupport/lib/active_support/core_ext/hash/keys.rb
+++ b/activesupport/lib/active_support/core_ext/hash/keys.rb
@@ -1,10 +1,10 @@
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 = {}
each_key do |key|
@@ -22,12 +22,12 @@ class Hash
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 }
end
@@ -38,13 +38,13 @@ class Hash
transform_keys!{ |key| key.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
@@ -61,24 +61,26 @@ class Hash
# 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.
#
- # { 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"
+ # { 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.
#
# 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|
@@ -98,14 +100,14 @@ class Hash
self
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.
#
# 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 }
end
@@ -117,14 +119,14 @@ class Hash
deep_transform_keys!{ |key| key.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.
#
# 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
diff --git a/activesupport/lib/active_support/core_ext/hash/slice.rb b/activesupport/lib/active_support/core_ext/hash/slice.rb
index 9fa9b3dac4..8ad600b171 100644
--- a/activesupport/lib/active_support/core_ext/hash/slice.rb
+++ b/activesupport/lib/active_support/core_ext/hash/slice.rb
@@ -26,6 +26,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/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/reporting.rb b/activesupport/lib/active_support/core_ext/kernel/reporting.rb
index 36ab836457..3b5e205244 100644
--- a/activesupport/lib/active_support/core_ext/kernel/reporting.rb
+++ b/activesupport/lib/active_support/core_ext/kernel/reporting.rb
@@ -48,6 +48,7 @@ module Kernel
yield
ensure
stream.reopen(old_stream)
+ old_stream.close
end
# Blocks and ignores any exception passed as argument if raised within the block.
@@ -60,8 +61,7 @@ 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) }
+ rescue *exception_classes
end
# Captures the given stream and returns it:
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/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
index 672cc0256f..d317df5079 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..f855833a24 100644
--- a/activesupport/lib/active_support/core_ext/module/delegation.rb
+++ b/activesupport/lib/active_support/core_ext/module/delegation.rb
@@ -138,6 +138,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]
@@ -176,31 +178,32 @@ class Module
# 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
+ method_def = [
+ "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
+ ].join ';'
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
+ method_def = [
+ "def #{method_prefix}#{method}(#{definition})", # def customer_name(*args, &block)
+ " _ = #{to}", # _ = client
+ " _.#{method}(#{definition})", # _.name(*args, &block)
+ "rescue NoMethodError => e", # rescue NoMethodError => e
+ " if _.nil? && e.name == :#{method}", # if _.nil? && e.name == :name
+ " #{exception}", # # add helpful message to the exception
+ " else", # else
+ " raise", # raise
+ " end", # end
+ "end" # end
+ ].join ';'
end
+
+ 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/numeric/time.rb b/activesupport/lib/active_support/core_ext/numeric/time.rb
index 87b9a23aef..704c4248d9 100644
--- a/activesupport/lib/active_support/core_ext/numeric/time.rb
+++ b/activesupport/lib/active_support/core_ext/numeric/time.rb
@@ -63,6 +63,7 @@ class Numeric
# Reads best without arguments: 10.minutes.ago
def ago(time = ::Time.current)
+ ActiveSupport::Deprecation.warn "Calling #ago or #until on a number (e.g. 5.ago) is deprecated and will be removed in the future, use 5.seconds.ago instead"
time - self
end
@@ -71,9 +72,16 @@ class Numeric
# Reads best with argument: 10.minutes.since(time)
def since(time = ::Time.current)
+ ActiveSupport::Deprecation.warn "Calling #since or #from_now on a number (e.g. 5.since) is deprecated and will be removed in the future, use 5.seconds.since instead"
time + self
end
# Reads best without arguments: 10.minutes.from_now
alias :from_now :since
+
+ # Used with the standard time durations, like 1.hour.in_milliseconds --
+ # so we can feed them to JavaScript functions like getTime().
+ def in_milliseconds
+ self * 1000
+ end
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..2e99f4a1b8 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,8 +22,8 @@ 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 }
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/json.rb b/activesupport/lib/active_support/core_ext/object/json.rb
new file mode 100644
index 0000000000..8e08cfbf26
--- /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].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.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.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(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
index 83cc8066e7..3dcae6fc7f 100644
--- a/activesupport/lib/active_support/core_ext/object/to_json.rb
+++ b/activesupport/lib/active_support/core_ext/object/to_json.rb
@@ -1,27 +1,5 @@
-# Hack to load json gem first so we can overwrite its to_json.
-begin
- require 'json'
-rescue LoadError
-end
+ActiveSupport::Deprecation.warn 'You have required `active_support/core_ext/object/to_json`. ' \
+ 'This file will be removed in Rails 4.2. You should require `active_support/core_ext/object/json` ' \
+ 'instead.'
-# 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
+require 'active_support/core_ext/object/json'
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/string/access.rb b/activesupport/lib/active_support/core_ext/string/access.rb
index ee3b6d2b3f..6018fd9641 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,17 +49,17 @@ 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
@@ -67,11 +67,11 @@ class String
# given limit is greater than or equal to the string length, returns 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
''
@@ -87,11 +87,11 @@ class String
# the given limit is greater than or equal to the string length, returns 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
''
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 a4cacaf789..49c0df6026 100644
--- a/activesupport/lib/active_support/core_ext/string/filters.rb
+++ b/activesupport/lib/active_support/core_ext/string/filters.rb
@@ -20,6 +20,16 @@ class String
self
end
+ # Returns a new string with all occurrences of the pattern removed. Short-hand for String#gsub(pattern, '').
+ def remove(pattern)
+ gsub pattern, ''
+ end
+
+ # Alters the string by removing all occurrences of the pattern. Short-hand for String#gsub!(pattern, '').
+ def remove!(pattern)
+ gsub! pattern, ''
+ end
+
# Truncates a given +text+ after a given <tt>length</tt> if +text+ is longer than <tt>length</tt>:
#
# 'Once upon a time in a world far far away'.truncate(27)
diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb
index 56e8a5f98d..cf9b1a4ec0 100644
--- a/activesupport/lib/active_support/core_ext/string/inflections.rb
+++ b/activesupport/lib/active_support/core_ext/string/inflections.rb
@@ -182,21 +182,23 @@ 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"
+ 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/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb
index dc033ed11b..eb02b6a442 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' }
+ JSON_ESCAPE = { '&' => '\u0026', '>' => '\u003e', '<' => '\u003c', "\u2028" => '\u2028', "\u2029" => '\u2029' }
+ HTML_ESCAPE_REGEXP = /[&"'><]/
HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+));)/
- JSON_ESCAPE_REGEXP = /[&"><]/
+ JSON_ESCAPE_REGEXP = /[\u2028\u2029&><]/u
# A utility method for escaping HTML tag characters.
# This method is also aliased as <tt>h</tt>.
@@ -21,7 +22,7 @@ class ERB
if s.html_safe?
s
else
- s.gsub(/[&"'><]/, HTML_ESCAPE).html_safe
+ s.gsub(HTML_ESCAPE_REGEXP, HTML_ESCAPE).html_safe
end
end
@@ -48,17 +49,56 @@ 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_escape('is a > 0 & a < 10?')
- # # => is a \u003E 0 \u0026 a \u003C 10?
+ # json = JSON.generate({ name: "</script><script>alert('PWNED!!!')</script>"})
+ # # => "{\"name\":\"</script><script>alert('PWNED!!!')</script>\"}"
#
- # Note that after this operation is performed the output is not
- # valid JSON. In particular double quotes are removed:
+ # json_escape(json)
+ # # => "{\"name\":\"\\u003C/script\\u003E\\u003Cscript\\u003Ealert('PWNED!!!')\\u003C/script\\u003E\"}"
#
- # json_escape('{"name":"john","created_at":"2010-04-28T01:39:31Z","id":1}')
- # # => {name:john,created_at:2010-04-28T01:39:31Z,id:1}
+ # 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>
+ #
+ # 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.
+ #
+ # 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
@@ -143,15 +183,14 @@ module ActiveSupport #:nodoc:
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 +210,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,6 +223,12 @@ module ActiveSupport #:nodoc:
EOT
end
end
+
+ private
+
+ def html_escape_interpolated_argument(arg)
+ (!html_safe? || arg.html_safe?) ? arg : ERB::Util.h(arg)
+ end
end
end
diff --git a/activesupport/lib/active_support/core_ext/thread.rb b/activesupport/lib/active_support/core_ext/thread.rb
index 5481766f10..ac1ffa4128 100644
--- a/activesupport/lib/active_support/core_ext/thread.rb
+++ b/activesupport/lib/active_support/core_ext/thread.rb
@@ -23,29 +23,29 @@ class Thread
# 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]
+ _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
+ _locals[key.to_sym] = value
end
- # Returns an an array of the names of the thread-local variables (as Symbols).
+ # Returns 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]
+ # 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
+ _locals.keys
end
# Returns <tt>true</tt> if the given string (or symbol) exists as a
@@ -53,22 +53,27 @@ class Thread
#
# me = Thread.current
# me.thread_variable_set(:oliver, "a")
- # me.thread_variable?(:oliver) #=> true
- # me.thread_variable?(:stanley) #=> false
+ # 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)
+ _locals.has_key?(key.to_sym)
+ end
+
+ def freeze
+ _locals.freeze
+ super
end
private
- def locals
- if defined?(@locals)
- @locals
+ def _locals
+ if defined?(@_locals)
+ @_locals
else
- LOCK.synchronize { @locals ||= {} }
+ LOCK.synchronize { @_locals ||= {} }
end
end
end unless Thread.instance_methods.include?(:thread_variable_set)
diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb
index 6e0af0db4d..89cd7516cd 100644
--- a/activesupport/lib/active_support/core_ext/time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/time/calculations.rb
@@ -91,10 +91,11 @@ class Time
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 +108,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) +
@@ -199,27 +201,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/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index 73559bfe0e..6be19771f5 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'
@@ -175,14 +176,22 @@ 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
+ # Dependencies assumes 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 we assume therefore the user wants to refer to a top-level constant.
+ def guess_for_anonymous(const_name)
+ if Object.const_defined?(const_name)
+ raise NameError, "#{const_name} cannot be autoloaded from an anonymous class or module"
+ else
+ Object
+ end
+ end
+
def unloadable(const_desc = self)
super(const_desc)
end
@@ -198,9 +207,19 @@ module ActiveSupport #:nodoc:
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)
@@ -445,8 +464,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
@@ -459,7 +476,7 @@ module ActiveSupport #:nodoc:
if loaded.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
diff --git a/activesupport/lib/active_support/deprecation.rb b/activesupport/lib/active_support/deprecation.rb
index 0281c9222e..ab16977bda 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 = '4.2', 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 90db180124..328b8c320a 100644
--- a/activesupport/lib/active_support/deprecation/behaviors.rb
+++ b/activesupport/lib/active_support/deprecation/behaviors.rb
@@ -1,14 +1,24 @@
require "active_support/notifications"
module ActiveSupport
+ class DeprecationException < StandardError
+ end
+
class Deprecation
# Default warning behaviors per Rails.env.
DEFAULT_BEHAVIORS = {
- :stderr => Proc.new { |message, callstack|
+ raise: ->(message, callstack) {
+ e = DeprecationException.new(message)
+ e.set_backtrace(callstack)
+ raise e
+ },
+
+ stderr: ->(message, callstack) {
$stderr.puts(message)
$stderr.puts callstack.join("\n ") if debug
},
- :log => Proc.new { |message, callstack|
+
+ log: ->(message, callstack) {
logger =
if defined?(Rails) && Rails.logger
Rails.logger
@@ -19,11 +29,13 @@ module ActiveSupport
logger.warn message
logger.debug callstack.join("\n ") if debug
},
- :notify => Proc.new { |message, callstack|
+
+ notify: ->(message, callstack) {
ActiveSupport::Notifications.instrument("deprecation.rails",
:message => message, :callstack => callstack)
},
- :silence => Proc.new { |message, callstack| }
+
+ silence: ->(message, callstack) {},
}
module Behavior
@@ -40,6 +52,7 @@ module ActiveSupport
#
# Available behaviors:
#
+ # [+raise+] Raise <tt>ActiveSupport::DeprecationException</tt>.
# [+stderr+] Log all deprecation warnings to +$stderr+.
# [+log+] Log all deprecation warnings to +Rails.logger+.
# [+notify+] Use +ActiveSupport::Notifications+ to notify +deprecation.rails+.
@@ -52,7 +65,7 @@ module ActiveSupport
# ActiveSupport::Deprecation.behavior = :stderr
# ActiveSupport::Deprecation.behavior = [:stderr, :log]
# ActiveSupport::Deprecation.behavior = MyCustomHandler
- # ActiveSupport::Deprecation.behavior = proc { |message, callstack|
+ # ActiveSupport::Deprecation.behavior = ->(message, callstack) {
# # custom stuff
# }
def behavior=(behavior)
diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb
index 2cb1f408b6..7df4857c25 100644
--- a/activesupport/lib/active_support/duration.rb
+++ b/activesupport/lib/active_support/duration.rb
@@ -70,13 +70,11 @@ module ActiveSupport
alias :until :ago
def inspect #:nodoc:
- consolidated = parts.inject(::Hash.new(0)) { |h,(l,r)| h[l] += r; h }
- parts = [:years, :months, :days, :minutes, :seconds].map do |length|
- n = consolidated[length]
- "#{n} #{n == 1 ? length.to_s.singularize : length.to_s}" if n.nonzero?
- end.compact
- parts = ["0 seconds"] if parts.empty?
- parts.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 => :en)
end
def as_json(options = nil) #:nodoc:
@@ -101,6 +99,14 @@ module ActiveSupport
private
+ # We define it as a workaround to Ruby 2.0.0-p353 bug.
+ # For more information, check rails/rails#13055.
+ # It should be dropped once a new Ruby patch-level
+ # release after 2.0.0-p353 happens.
+ def ===(other) #:nodoc:
+ value === other
+ end
+
def method_missing(method, *args, &block) #:nodoc:
value.send(method, *args, &block)
end
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/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb
index 3da99872c0..594a4ca938 100644
--- a/activesupport/lib/active_support/hash_with_indifferent_access.rb
+++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb
@@ -159,7 +159,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,7 +172,7 @@ 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.
@@ -207,7 +207,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
@@ -228,7 +228,11 @@ module ActiveSupport
def to_options!; self end
def select(*args, &block)
- dup.tap {|hash| hash.select!(*args, &block)}
+ dup.tap { |hash| hash.select!(*args, &block) }
+ end
+
+ def reject(*args, &block)
+ dup.tap { |hash| hash.reject!(*args, &block) }
end
# Convert to a regular hash with string keys.
diff --git a/activesupport/lib/active_support/i18n_railtie.rb b/activesupport/lib/active_support/i18n_railtie.rb
index 890dd9380b..23cd6716e3 100644
--- a/activesupport/lib/active_support/i18n_railtie.rb
+++ b/activesupport/lib/active_support/i18n_railtie.rb
@@ -8,6 +8,8 @@ module I18n
config.i18n.railties_load_path = []
config.i18n.load_path = []
config.i18n.fallbacks = ActiveSupport::OrderedOptions.new
+ # Enforce I18n to check the available locales when setting a locale.
+ config.i18n.enforce_available_locales = true
# Set the i18n configuration after initialization since a lot of
# configuration is still usually done in application initializers.
@@ -31,6 +33,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 unless I18n.enforce_available_locales.nil?
+ I18n.enforce_available_locales = false
+
app.config.i18n.each do |setting, value|
case setting
when :railties_load_path
@@ -44,6 +52,9 @@ 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 }
diff --git a/activesupport/lib/active_support/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb
index c96debb93f..eda0edff28 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("|")}/
diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
index d063fcb49c..cdee4c2ca5 100644
--- a/activesupport/lib/active_support/inflector/methods.rb
+++ b/activesupport/lib/active_support/inflector/methods.rb
@@ -36,7 +36,7 @@ module ActiveSupport
# 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"
@@ -98,20 +98,27 @@ 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.
- #
- # 'employee_salary'.humanize # => "Employee salary"
- # 'author_id'.humanize # => "Author"
- def humanize(lower_case_and_underscored_word)
+ # Capitalizes the first word, turns underscores into spaces, and strips a
+ # trailing '_id' if present.
+ # Like +titleize+, this is meant for creating pretty output.
+ #
+ # The capitalization of the first word can be turned off by setting the
+ # optional parameter +capitalize+ to false.
+ # By default, this parameter is true.
+ #
+ # humanize('employee_salary') # => "Employee salary"
+ # humanize('author_id') # => "Author"
+ # humanize('author_id', capitalize: false) # => "author"
+ 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.tr!('_', ' ')
- result.gsub(/([a-z\d]*)/i) { |match|
+ result.gsub!(/([a-z\d]*)/i) { |match|
"#{inflections.acronyms[match] || match.downcase}"
- }.gsub(/^\w/) { $&.upcase }
+ }
+ result.gsub!(/^\w/) { $&.upcase } if options.fetch(:capitalize, true)
+ result
end
# Capitalizes all the words and replaces some characters in the string to
diff --git a/activesupport/lib/active_support/json/decoding.rb b/activesupport/lib/active_support/json/decoding.rb
index 30833a4cb1..8b5fc70dee 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.
#
# 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..f29d42276d 100644
--- a/activesupport/lib/active_support/json/encoding.rb
+++ b/activesupport/lib/active_support/json/encoding.rb
@@ -1,344 +1,172 @@
-#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.
#
# 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
-
- 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
-
- 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
- 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
+ # If true, encode >, <, & as escaped unicode sequences (e.g. > as \u003e)
+ # as a safety measure.
+ attr_accessor :escape_html_entities_in_json
-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
+ # Sets the precision of encoded time values.
+ # Defaults to 3 (equivalent to millisecond precision)
+ attr_accessor :time_precision
-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
+ # 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
-class Regexp
- def as_json(options = nil) #:nodoc:
- to_s
- end
-end
+ def encode_big_decimal_as_string=(as_string)
+ message = \
+ "The JSON encoder in Rails 4.1 no longer supports encoding BigDecimals as JSON numbers. Instead, " \
+ "the new encoder will always encode them as strings.\n\n" \
+ "You are seeing this error because you have 'active_support.encode_big_decimal_as_string' in " \
+ "your configuration file. If you have been setting this to true, you can safely remove it from " \
+ "your configuration. Otherwise, you should add the 'activesupport-json_encoder' gem to your " \
+ "Gemfile in order to restore this functionality."
-module Enumerable
- def as_json(options = nil) #:nodoc:
- to_a.as_json(options)
- end
-end
+ raise NotImplementedError, message
+ end
-class Range
- def as_json(options = nil) #:nodoc:
- to_s
- end
-end
+ def encode_big_decimal_as_string
+ message = \
+ "The JSON encoder in Rails 4.1 no longer supports encoding BigDecimals as JSON numbers. Instead, " \
+ "the new encoder will always encode them as strings.\n\n" \
+ "You are seeing this error because you are trying to check the value of the related configuration, " \
+ "'active_support.encode_big_decimal_as_string'. If your application depends on this option, you should " \
+ "add the 'activesupport-json_encoder' gem to your Gemfile. For now, this option will always be true. " \
+ "In the future, it will be removed from Rails, so you should stop checking its value."
-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
+ ActiveSupport::Deprecation.warn message
- 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
+ true
+ 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
+ # Deprecate CircularReferenceError
+ def const_missing(name)
+ if name == :CircularReferenceError
+ message = "The JSON encoder in Rails 4.1 no longer offers protection from circular references. " \
+ "You are seeing this warning because you are rescuing from (or otherwise referencing) " \
+ "ActiveSupport::Encoding::CircularReferenceError. In the future, this error will be " \
+ "removed from Rails. You should remove these rescue blocks from your code and ensure " \
+ "that your data structures are free of circular references so they can be properly " \
+ "serialized into JSON.\n\n" \
+ "For example, the following Hash contains a circular reference to itself:\n" \
+ " h = {}\n" \
+ " h['circular'] = h\n" \
+ "In this case, calling h.to_json would not work properly."
+
+ ActiveSupport::Deprecation.warn message
+
+ SystemStackError
+ else
+ super
+ end
+ end
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.use_standard_json_time_format = true
+ self.escape_html_entities_in_json = true
+ 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..b019ad0dec 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..."
@@ -76,12 +76,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 +91,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..8e6e1dcfeb 100644
--- a/activesupport/lib/active_support/message_verifier.rb
+++ b/activesupport/lib/active_support/message_verifier.rb
@@ -37,7 +37,12 @@ module ActiveSupport
data, digest = signed_message.split("--")
if data.present? && digest.present? && secure_compare(digest, generate_digest(data))
- @serializer.load(::Base64.decode64(data))
+ begin
+ @serializer.load(::Base64.strict_decode64(data))
+ rescue ArgumentError => argument_error
+ raise InvalidSignature if argument_error.message =~ %r{invalid base64}
+ raise
+ end
else
raise InvalidSignature
end
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..84799c2399 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 = '6.3.0'
# The default normalization used for operations that require
# normalization. It can be set to any of the normalizations
@@ -212,37 +212,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)
+ # Ruby >= 2.1 has String#scrub, which is faster than the workaround used for < 2.1.
+ if RUBY_VERSION >= '2.1'
+ # 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_8_MAC)
+
+ source = string.dup
+ out = ''.force_encoding(Encoding::UTF_8_MAC)
+
+ 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)
+ 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 +293,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
@@ -364,14 +377,8 @@ 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
diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb
index b32aa75e59..7a96c66626 100644
--- a/activesupport/lib/active_support/notifications.rb
+++ b/activesupport/lib/active_support/notifications.rb
@@ -178,7 +178,7 @@ module ActiveSupport
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..8f5fa646e8 100644
--- a/activesupport/lib/active_support/notifications/fanout.rb
+++ b/activesupport/lib/active_support/notifications/fanout.rb
@@ -107,21 +107,18 @@ module ActiveSupport
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..3a244b34b5 100644
--- a/activesupport/lib/active_support/notifications/instrumenter.rb
+++ b/activesupport/lib/active_support/notifications/instrumenter.rb
@@ -54,10 +54,11 @@ module ActiveSupport
@transaction_id = transaction_id
@end = ending
@children = []
+ @duration = nil
end
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 54cff6d394..b169e3af01 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
@@ -253,14 +119,7 @@ module ActiveSupport
# 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+
@@ -289,15 +148,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
@@ -340,39 +191,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)
- number = 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 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)
- 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
@@ -420,36 +239,7 @@ module ActiveSupport
# number_to_human_size(1234567890123, precision: 5) # => "1.1229 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
@@ -460,7 +250,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
@@ -550,88 +340,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..9ae27a896a
--- /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..6405afc9a6
--- /dev/null
+++ b/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb
@@ -0,0 +1,21 @@
+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) { "#{$1}#{options[:delimiter]}" }
+ [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..9a3dc526ae
--- /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 { |e| -e }
+ 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..eafe2844f7
--- /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..c42354fc83
--- /dev/null
+++ b/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb
@@ -0,0 +1,92 @@
+module ActiveSupport
+ module NumberHelper
+ class NumberToRoundedConverter < NumberConverter # :nodoc:
+ self.namespace = :precision
+ self.validate_float = true
+
+ def convert
+ precision = options.delete :precision
+ significant = options.delete :significant
+
+ case number
+ when Float, String
+ @number = BigDecimal(number.to_s)
+ when Rational
+ if significant
+ @number = BigDecimal(number, digit_count(number.to_i) + precision)
+ else
+ @number = BigDecimal(number, precision)
+ end
+ else
+ @number = number.to_d
+ end
+
+ if 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 =
+ case rounded_number
+ when BigDecimal
+ s = rounded_number.to_s('F') + '0'*precision
+ a, b = s.split('.', 2)
+ a + '.' + b[0, precision]
+ else
+ "%01.#{precision}f" % rounded_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)
+ (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/ordered_hash.rb b/activesupport/lib/active_support/ordered_hash.rb
index 1a3693f766..58a2ce2105 100644
--- a/activesupport/lib/active_support/ordered_hash.rb
+++ b/activesupport/lib/active_support/ordered_hash.rb
@@ -28,6 +28,10 @@ module ActiveSupport
coder.represent_seq '!omap', map { |k,v| { k => v } }
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/subscriber.rb b/activesupport/lib/active_support/subscriber.rb
index 34c6f900c1..4b9b48539f 100644
--- a/activesupport/lib/active_support/subscriber.rb
+++ b/activesupport/lib/active_support/subscriber.rb
@@ -31,18 +31,41 @@ 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)
+
+ notifier.subscribe("#{event}.#{namespace}", subscriber)
+ end
end
def initialize
@@ -71,7 +94,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..d5c2222d2e 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'
diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb
index 2f27aff2bd..2fb5c04316 100644
--- a/activesupport/lib/active_support/test_case.rb
+++ b/activesupport/lib/active_support/test_case.rb
@@ -7,6 +7,7 @@ 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/core_ext/kernel/reporting'
require 'active_support/deprecation'
@@ -15,23 +16,6 @@ begin
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
@@ -44,15 +28,14 @@ module ActiveSupport
end
# FIXME: we have tests that depend on run order, we should fix that and
- # remove this method.
- def self.test_order # :nodoc:
- :sorted
- end
+ # remove this method call.
+ self.i_suck_and_my_tests_are_order_dependent!
include ActiveSupport::Testing::TaggedLogging
include ActiveSupport::Testing::SetupAndTeardown
include ActiveSupport::Testing::Assertions
include ActiveSupport::Testing::Deprecation
+ include ActiveSupport::Testing::TimeHelpers
extend ActiveSupport::Testing::Declarative
# test/unit backwards compatibility methods
diff --git a/activesupport/lib/active_support/testing/declarative.rb b/activesupport/lib/active_support/testing/declarative.rb
index 1fa73caefa..c349bb5fb1 100644
--- a/activesupport/lib/active_support/testing/declarative.rb
+++ b/activesupport/lib/active_support/testing/declarative.rb
@@ -7,11 +7,18 @@ module ActiveSupport
unless method_defined?(:describe)
def self.describe(text)
- class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
- def self.name
- "#{text}"
- end
- RUBY_EVAL
+ if block_given?
+ super
+ else
+ message = "`describe` without a block is deprecated, please switch to: `def self.name; #{text.inspect}; end`\n"
+ ActiveSupport::Deprecation.warn message
+
+ class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
+ def self.name
+ "#{text}"
+ end
+ RUBY_EVAL
+ end
end
end
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/isolation.rb b/activesupport/lib/active_support/testing/isolation.rb
index d5d31cecbe..908af176be 100644
--- a/activesupport/lib/active_support/testing/isolation.rb
+++ b/activesupport/lib/active_support/testing/isolation.rb
@@ -1,5 +1,4 @@
require 'rbconfig'
-require 'minitest/parallel_each'
module ActiveSupport
module Testing
@@ -7,11 +6,9 @@ module ActiveSupport
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?
@@ -40,6 +37,8 @@ module ActiveSupport
module Forking
def run_in_isolation(&blk)
read, write = IO.pipe
+ read.binmode
+ write.binmode
pid = fork do
read.close
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..9e0a3d6345
--- /dev/null
+++ b/activesupport/lib/active_support/testing/time_helpers.rb
@@ -0,0 +1,109 @@
+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
+ #
+ # 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, &block)
+ simple_stubs.stub_object(Time, :now, date_or_time.to_time)
+ simple_stubs.stub_object(Date, :today, date_or_time.to_date)
+
+ if block_given?
+ block.call
+ travel_back
+ 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_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index 95b9b8e5ae..c25c97cfa8 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+.
@@ -132,8 +132,8 @@ module ActiveSupport
end
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 +146,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
@@ -367,12 +367,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 +390,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..eb785d46ce 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'
@@ -184,6 +186,8 @@ module ActiveSupport
UTC_OFFSET_WITH_COLON = '%s%02d:%02d'
UTC_OFFSET_WITHOUT_COLON = UTC_OFFSET_WITH_COLON.sub(':', '')
+ @lazy_zones_map = ThreadSafe::Cache.new
+
# Assumes self represents an offset from UTC in seconds (as returned from
# Time#utc_offset) and turns this into an +HH:MM formatted string.
#
@@ -205,8 +209,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)
@@ -232,6 +234,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
@@ -314,6 +317,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,6 +352,10 @@ module ActiveSupport
tzinfo.period_for_local(time, dst)
end
+ def periods_for_local(time) #:nodoc:
+ tzinfo.periods_for_local(time)
+ end
+
def self.find_tzinfo(name)
TZInfo::TimezoneProxy.new(MAPPING[name] || name)
end
@@ -346,14 +363,14 @@ module ActiveSupport
class << self
alias_method :create, :new
- # Return a TimeZone instance with the given name, or +nil+ if no
+ # 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
- # Return an array of all TimeZone objects. There are multiple
+ # 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
@@ -362,10 +379,8 @@ module ActiveSupport
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)
+ MAPPING.each_key {|place| self[place]} # load all the zones
+ @lazy_zones_map
end
end
@@ -378,7 +393,7 @@ module ActiveSupport
case arg
when String
begin
- lazy_zones_map[arg] ||= lookup(arg).tap { |tz| tz.utc_offset }
+ @lazy_zones_map[arg] ||= create(arg).tap { |tz| tz.utc_offset }
rescue TZInfo::InvalidTimezoneIdentifier
nil
end
@@ -395,29 +410,6 @@ module ActiveSupport
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
end
private
diff --git a/activesupport/lib/active_support/values/unicode_tables.dat b/activesupport/lib/active_support/values/unicode_tables.dat
index 2571faa019..394ee95f4b 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..b3f0e7198d 100644
--- a/activesupport/lib/active_support/version.rb
+++ b/activesupport/lib/active_support/version.rb
@@ -1,7 +1,7 @@
module ActiveSupport
# Returns the version of the currently loaded ActiveSupport as a Gem::Version
def self.version
- Gem::Version.new "4.1.0.beta"
+ Gem::Version.new "4.1.0.beta1"
end
module VERSION #:nodoc:
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/test/abstract_unit.rb b/activesupport/test/abstract_unit.rb
index 939bd7bfa8..0b393e0c7a 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,16 @@ 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
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/caching_test.rb b/activesupport/test/caching_test.rb
index df570d485a..c3c65cf805 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
@@ -110,12 +146,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
@@ -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({})
@@ -709,6 +746,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
diff --git a/activesupport/test/callbacks_test.rb b/activesupport/test/callbacks_test.rb
index f8e2ce22fa..32c2dfdfc0 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
diff --git a/activesupport/test/configurable_test.rb b/activesupport/test/configurable_test.rb
index d00273a028..ef847fc557 100644
--- a/activesupport/test/configurable_test.rb
+++ b/activesupport/test/configurable_test.rb
@@ -95,6 +95,20 @@ 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
def assert_method_defined(object, method)
diff --git a/activesupport/test/core_ext/array_ext_test.rb b/activesupport/test/core_ext/array_ext_test.rb
index 6cd0eb39b7..57722fd52a 100644
--- a/activesupport/test/core_ext/array_ext_test.rb
+++ b/activesupport/test/core_ext/array_ext_test.rb
@@ -212,18 +212,24 @@ class ArraySplitTests < ActiveSupport::TestCase
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)
+ 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
- assert_equal [[1, 2], [4, 5], [7, 8], [10]], (1..10).to_a.split { |i| i % 3 == 0 }
+ 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
- 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 }
+ 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/big_decimal/yaml_conversions_test.rb b/activesupport/test/core_ext/big_decimal/yaml_conversions_test.rb
new file mode 100644
index 0000000000..e634679d20
--- /dev/null
+++ b/activesupport/test/core_ext/big_decimal/yaml_conversions_test.rb
@@ -0,0 +1,11 @@
+require 'abstract_unit'
+
+class BigDecimalYamlConversionsTest < ActiveSupport::TestCase
+ def test_to_yaml
+ assert_deprecated { require 'active_support/core_ext/big_decimal/yaml_conversions' }
+ 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
+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/blank_test.rb b/activesupport/test/core_ext/blank_test.rb
index a68c074777..246bc7fa61 100644
--- a/activesupport/test/core_ext/blank_test.rb
+++ b/activesupport/test/core_ext/blank_test.rb
@@ -4,17 +4,29 @@ 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/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..0e0742d147 100644
--- a/activesupport/test/core_ext/class/delegating_attributes_test.rb
+++ b/activesupport/test/core_ext/class/delegating_attributes_test.rb
@@ -39,10 +39,13 @@ class DelegatingAttributesTest < ActiveSupport::TestCase
end
def test_simple_accessor_declaration_with_instance_reader_false
+ _instance_methods = single_class.public_instance_methods
single_class.superclass_delegating_accessor :no_instance_reader, :instance_reader => false
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
diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb
index eab3aa7a6e..5d0af035cc 100644
--- a/activesupport/test/core_ext/date_ext_test.rb
+++ b/activesupport/test/core_ext/date_ext_test.rb
@@ -276,6 +276,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)
diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb
index 5e3987265b..28ba33331e 100644
--- a/activesupport/test/core_ext/duration_test.rb
+++ b/activesupport/test/core_ext/duration_test.rb
@@ -27,6 +27,7 @@ class DurationTest < ActiveSupport::TestCase
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
@@ -37,6 +38,8 @@ 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
@@ -50,12 +53,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 +69,19 @@ class DurationTest < ActiveSupport::TestCase
assert_equal 86400 * 1.7, 1.7.days
end
+ def test_since_and_ago
+ t = Time.local(2000)
+ assert t + 1, 1.second.since(t)
+ assert 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 +107,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 +120,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
@@ -142,6 +156,11 @@ class DurationTest < ActiveSupport::TestCase
assert_equal '172800', 2.days.to_json
end
+ def test_case_when
+ cased = case 1.day when 1.day then "ok" end
+ assert_equal cased, "ok"
+ end
+
protected
def with_env_tz(new_tz = 'US/Eastern')
old_tz, ENV['TZ'] = ENV['TZ'], new_tz
diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb
index 2d0c56bef5..40c8f03374 100644
--- a/activesupport/test/core_ext/hash_ext_test.rb
+++ b/activesupport/test/core_ext/hash_ext_test.rb
@@ -54,6 +54,8 @@ 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!
end
def test_transform_keys
@@ -624,10 +626,15 @@ class HashExtTest < ActiveSupport::TestCase
{ :failure => "stuff", :funny => "business" }.assert_valid_keys(:failure, :funny)
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
end
def test_assorted_keys_not_stringified
@@ -781,6 +788,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}
@@ -842,6 +867,32 @@ 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
end
class IWriteMyOwnXML
diff --git a/activesupport/test/core_ext/kernel_test.rb b/activesupport/test/core_ext/kernel_test.rb
index b8951de402..18b251173f 100644
--- a/activesupport/test/core_ext/kernel_test.rb
+++ b/activesupport/test/core_ext/kernel_test.rb
@@ -38,6 +38,22 @@ class KernelTest < ActiveSupport::TestCase
# Skip if we can't STDERR.tell
end
+ def test_silence_stream
+ old_stream_position = STDOUT.tell
+ silence_stream(STDOUT) { STDOUT.puts 'hello world' }
+ assert_equal old_stream_position, STDOUT.tell
+ rescue Errno::ESPIPE
+ # Skip if we can't stream.tell
+ end
+
+ def test_silence_stream_closes_file_descriptors
+ stream = StringIO.new
+ dup_stream = StringIO.new
+ stream.stubs(:dup).returns(dup_stream)
+ dup_stream.expects(:close)
+ silence_stream(stream) { stream.puts 'hello world' }
+ end
+
def test_quietly
old_stdout_position, old_stderr_position = STDOUT.tell, STDERR.tell
quietly do
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..c6863b24a4
--- /dev/null
+++ b/activesupport/test/core_ext/module/concerning_test.rb
@@ -0,0 +1,35 @@
+require 'abstract_unit'
+require 'active_support/core_ext/module/concerning'
+
+class ConcerningTest < ActiveSupport::TestCase
+ def test_concern_shortcut_creates_a_module_but_doesnt_include_it
+ mod = Module.new { concern(:Foo) { } }
+ assert_kind_of Module, mod::Foo
+ assert mod::Foo.respond_to?(:included)
+ assert !mod.ancestors.include?(mod::Foo), mod.ancestors.inspect
+ end
+
+ def test_concern_creates_a_module_extended_with_active_support_concern
+ klass = Class.new do
+ concern :Foo do
+ included { @foo = 1 }
+ def should_be_public; end
+ end
+ end
+
+ # Declares a concern but doesn't include it
+ assert_kind_of Module, klass::Foo
+ assert !klass.ancestors.include?(klass::Foo), klass.ancestors.inspect
+
+ # Public method visibility by default
+ assert klass::Foo.public_instance_methods.map(&:to_s).include?('should_be_public')
+
+ # Calls included hook
+ assert_equal 1, Class.new { include klass::Foo }.instance_variable_get('@foo')
+ end
+
+ 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
diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb
index 283b13ff8b..ff6e21854e 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
@@ -251,6 +245,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
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..3b1dabea8d 100644
--- a/activesupport/test/core_ext/numeric_ext_test.rb
+++ b/activesupport/test/core_ext/numeric_ext_test.rb
@@ -22,21 +22,16 @@ 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
+ def test_deprecated_since_and_ago
+ assert_equal @now + 1, assert_deprecated { 1.since(@now) }
+ assert_equal @now - 1, assert_deprecated { 1.ago(@now) }
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
+ def test_deprecated_since_and_ago_without_argument
+ now = Time.now
+ assert assert_deprecated { 1.since } >= now + 1
+ now = Time.now
+ assert assert_deprecated { 1.ago } >= now - 1
end
def test_irregular_durations
@@ -78,10 +73,10 @@ 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
@@ -94,11 +89,11 @@ class NumericExtTimeAndDateTimeTest < ActiveSupport::TestCase
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
+ assert_not_instance_of ActiveSupport::TimeWithZone, assert_deprecated { 5.since }
+ assert_equal Time.local(2000,1,1,0,0,5), assert_deprecated { 5.since }
# ago
- assert_equal false, 5.ago.is_a?(ActiveSupport::TimeWithZone)
- assert_equal Time.local(1999,12,31,23,59,55), 5.ago
+ assert_not_instance_of ActiveSupport::TimeWithZone, assert_deprecated { 5.ago }
+ assert_equal Time.local(1999,12,31,23,59,55), assert_deprecated { 5.ago }
end
end
@@ -107,13 +102,13 @@ class NumericExtTimeAndDateTimeTest < ActiveSupport::TestCase
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
+ assert_instance_of ActiveSupport::TimeWithZone, assert_deprecated { 5.since }
+ assert_equal Time.utc(2000,1,1,0,0,5), assert_deprecated { 5.since.time }
+ assert_equal 'Eastern Time (US & Canada)', assert_deprecated { 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
+ assert_instance_of ActiveSupport::TimeWithZone, assert_deprecated { 5.ago }
+ assert_equal Time.utc(1999,12,31,23,59,55), assert_deprecated { 5.ago.time }
+ assert_equal 'Eastern Time (US & Canada)', assert_deprecated { 5.ago.time_zone.name }
end
ensure
Time.zone = nil
@@ -440,4 +435,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/json_test.rb b/activesupport/test/core_ext/object/json_test.rb
new file mode 100644
index 0000000000..d3d31530df
--- /dev/null
+++ b/activesupport/test/core_ext/object/json_test.rb
@@ -0,0 +1,9 @@
+require 'abstract_unit'
+
+class JsonTest < ActiveSupport::TestCase
+ # See activesupport/test/json/encoding_test.rb for JSON encoding tests
+
+ def test_deprecated_require_to_json_rb
+ assert_deprecated { require 'active_support/core_ext/object/to_json' }
+ 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
index 8d748791e3..0f454fdd95 100644
--- a/activesupport/test/core_ext/object_and_class_ext_test.rb
+++ b/activesupport/test/core_ext/object_and_class_ext_test.rb
@@ -3,31 +3,6 @@ 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?
diff --git a/activesupport/test/core_ext/range_ext_test.rb b/activesupport/test/core_ext/range_ext_test.rb
index 2f8723a074..150e6b65fb 100644
--- a/activesupport/test/core_ext/range_ext_test.rb
+++ b/activesupport/test/core_ext/range_ext_test.rb
@@ -42,7 +42,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 +90,30 @@ 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
end
diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb
index 12126fb9ef..d4f8ba8cdd 100644
--- a/activesupport/test/core_ext/string_ext_test.rb
+++ b/activesupport/test/core_ext/string_ext_test.rb
@@ -11,13 +11,6 @@ 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
-
class StringInflectionsTest < ActiveSupport::TestCase
include InflectorTestCases
include ConstantizeTestCases
@@ -162,61 +155,17 @@ class StringInflectionsTest < ActiveSupport::TestCase
end
end
+ def test_humanize_without_capitalize
+ UnderscoreToHumanWithoutCapitalize.each do |underscore, human|
+ assert_equal(human, underscore.humanize(capitalize: false))
+ end
+ end
+
def test_ord
assert_equal 97, 'a'.ord
assert_equal 97, 'abc'.ord
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)
- 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
- end
-
def test_starts_ends_with_alias
s = "hello"
assert s.starts_with?('h')
@@ -278,6 +227,11 @@ class StringInflectionsTest < ActiveSupport::TestCase
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 /)
+ end
+
def test_constantize
run_constantize_tests_on do |string|
string.constantize
@@ -291,6 +245,93 @@ class StringInflectionsTest < ActiveSupport::TestCase
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 "#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 "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
def test_string_to_time
with_env_tz "Europe/Moscow" do
diff --git a/activesupport/test/core_ext/thread_test.rb b/activesupport/test/core_ext/thread_test.rb
index 230c1203ad..6a7c6e0604 100644
--- a/activesupport/test/core_ext/thread_test.rb
+++ b/activesupport/test/core_ext/thread_test.rb
@@ -63,15 +63,13 @@ class ThreadExt < ActiveSupport::TestCase
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
+ def test_thread_variable_frozen_after_set
+ t = Thread.new { }.join
+ t.thread_variable_set :foo, "bar"
+ t.freeze
+ assert_raises(RuntimeError) do
+ t.thread_variable_set(:baz, "qux")
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..e0a4b1be3e 100644
--- a/activesupport/test/core_ext/time_ext_test.rb
+++ b/activesupport/test/core_ext/time_ext_test.rb
@@ -476,6 +476,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
diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb
index 5494824a40..7fe4d4a6b2 100644
--- a/activesupport/test/core_ext/time_with_zone_test.rb
+++ b/activesupport/test/core_ext/time_with_zone_test.rb
@@ -1,6 +1,5 @@
require 'abstract_unit'
require 'active_support/time'
-require 'active_support/json'
class TimeWithZoneTest < ActiveSupport::TestCase
@@ -66,25 +65,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)
@@ -131,6 +111,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
@@ -511,6 +495,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
diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb
index 68b6cc6e8c..00bec5bd9d 100644
--- a/activesupport/test/dependencies_test.rb
+++ b/activesupport/test/dependencies_test.rb
@@ -35,6 +35,17 @@ 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'
@@ -62,12 +73,11 @@ 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)
@@ -355,26 +365,19 @@ class DependenciesTest < ActiveSupport::TestCase
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
+
+ e = assert_raise(NameError) { A::B::DoesNotExist.nil? }
+ assert_equal "uninitialized constant A::B::DoesNotExist", e.message
end
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
@@ -519,29 +522,21 @@ class DependenciesTest < ActiveSupport::TestCase
end
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
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
end
end
@@ -550,12 +545,10 @@ class DependenciesTest < ActiveSupport::TestCase
c = ServiceOne
ActiveSupport::Dependencies.clear
assert ! defined?(ServiceOne)
- begin
+ 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
end
@@ -647,6 +640,14 @@ class DependenciesTest < ActiveSupport::TestCase
Object.class_eval { remove_const :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
+ end
+
def test_unloadable
with_autoloading_fixtures do
Object.const_set :M, Module.new
@@ -886,12 +887,10 @@ class DependenciesTest < ActiveSupport::TestCase
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
diff --git a/activesupport/test/deprecation_test.rb b/activesupport/test/deprecation_test.rb
index 9616e42f44..ee1c69502e 100644
--- a/activesupport/test/deprecation_test.rb
+++ b/activesupport/test/deprecation_test.rb
@@ -98,6 +98,19 @@ class DeprecationTest < ActiveSupport::TestCase
assert_match(/foo=nil/, @b)
end
+ def test_raise_behaviour
+ ActiveSupport::Deprecation.behavior = :raise
+
+ message = 'Revise this deprecated stuff now!'
+ callstack = %w(foo bar baz)
+
+ e = assert_raise ActiveSupport::DeprecationException do
+ ActiveSupport::Deprecation.behavior.first.call(message, callstack)
+ end
+ assert_equal message, e.message
+ assert_equal callstack, e.backtrace
+ end
+
def test_default_stderr_behavior
ActiveSupport::Deprecation.behavior = :stderr
behavior = ActiveSupport::Deprecation.behavior.first
@@ -158,7 +171,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
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/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/inflector_test.rb b/activesupport/test/inflector_test.rb
index 1bb2d7e920..35967ba656 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|
@@ -230,25 +231,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 +288,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')
diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb
index cc36d62138..4bd1b2e47c 100644
--- a/activesupport/test/inflector_test_cases.rb
+++ b/activesupport/test/inflector_test_cases.rb
@@ -63,6 +63,7 @@ module InflectorTestCases
"news" => "news",
"series" => "series",
+ "miniseries" => "miniseries",
"species" => "species",
"quiz" => "quizzes",
@@ -212,6 +213,12 @@ module InflectorTestCases
"underground" => "Underground"
}
+ UnderscoreToHumanWithoutCapitalize = {
+ "employee_salary" => "employee salary",
+ "employee_id" => "employee",
+ "underground" => "underground"
+ }
+
MixtureToTitleCase = {
'active_record' => 'Active Record',
'ActiveRecord' => 'Active Record',
diff --git a/activesupport/test/json/decoding_test.rb b/activesupport/test/json/decoding_test.rb
index 34ed866848..07d7e530ca 100644
--- a/activesupport/test/json/decoding_test.rb
+++ b/activesupport/test/json/decoding_test.rb
@@ -4,6 +4,12 @@ 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,15 +58,26 @@ 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 do |json, expected|
- test "json decodes #{json}" do
+ 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)
+ assert_equal expected, ActiveSupport::JSON.decode(json), "JSON decoding \
+ failed for #{json}"
end
ActiveSupport.parse_json_times = prev
end
@@ -75,7 +92,14 @@ class TestJSONDecoding < ActiveSupport::TestCase
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
end
diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb
index ed1326705c..c4283ee79a 100644
--- a/activesupport/test/json/encoding_test.rb
+++ b/activesupport/test/json/encoding_test.rb
@@ -1,7 +1,9 @@
# encoding: utf-8
+require 'securerandom'
require 'abstract_unit'
require 'active_support/core_ext/string/inflections'
require 'active_support/json'
+require 'active_support/time'
class TestJSONEncoding < ActiveSupport::TestCase
class Foo
@@ -12,13 +14,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 +37,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) ]]
@@ -42,11 +67,11 @@ class TestJSONEncoding < ActiveSupport::TestCase
[ BigDecimal('0.0')/BigDecimal('0.0'), %(null) ],
[ BigDecimal('2.5'), %("#{BigDecimal('2.5').to_s}") ]]
- 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 +85,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 +101,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 +127,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,22 +173,44 @@ class TestJSONEncoding < ActiveSupport::TestCase
assert_equal "𐒑", decoded_hash['string']
end
+ def test_reading_encode_big_decimal_as_string_option
+ assert_deprecated do
+ assert ActiveSupport.encode_big_decimal_as_string
+ end
+ end
+
+ def test_setting_deprecated_encode_big_decimal_as_string_option
+ assert_raise(NotImplementedError) do
+ ActiveSupport.encode_big_decimal_as_string = true
+ end
+
+ assert_raise(NotImplementedError) do
+ ActiveSupport.encode_big_decimal_as_string = false
+ end
+ 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) }
+ assert_deprecated do
+ assert_raise(ActiveSupport::JSON::Encoding::CircularReferenceError) { ActiveSupport::JSON.encode(a) }
+ end
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) }
+ assert_deprecated do
+ assert_raise(ActiveSupport::JSON::Encoding::CircularReferenceError) { ActiveSupport::JSON.encode(a) }
+ end
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) }
+ assert_deprecated do
+ assert_raise(ActiveSupport::JSON::Encoding::CircularReferenceError) { ActiveSupport::JSON.encode(a) }
+ end
end
def test_hash_key_identifiers_are_always_quoted
@@ -167,21 +227,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 +252,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',
@@ -270,14 +351,34 @@ class TestJSONEncoding < ActiveSupport::TestCase
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 +403,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,6 +418,89 @@ 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
+ ActiveSupport::JSON::Encoding.time_precision = 0
+ 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
+ ensure
+ ActiveSupport::JSON::Encoding.time_precision = 3
+ end
+
+ def test_time_to_json_with_custom_time_precision
+ with_standard_json_time_format(true) do
+ ActiveSupport::JSON::Encoding.time_precision = 0
+ assert_equal "\"2000-01-01T00:00:00Z\"", ActiveSupport::JSON.encode(Time.utc(2000))
+ end
+ ensure
+ ActiveSupport::JSON::Encoding.time_precision = 3
+ end
+
+ def test_datetime_to_json_with_custom_time_precision
+ with_standard_json_time_format(true) do
+ ActiveSupport::JSON::Encoding.time_precision = 0
+ assert_equal "\"2000-01-01T00:00:00+00:00\"", ActiveSupport::JSON.encode(DateTime.new(2000))
+ end
+ ensure
+ ActiveSupport::JSON::Encoding.time_precision = 3
+ 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)
@@ -340,4 +513,11 @@ class TestJSONEncoding < ActiveSupport::TestCase
ensure
old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ')
end
+
+ 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
end
diff --git a/activesupport/test/message_encryptor_test.rb b/activesupport/test/message_encryptor_test.rb
index 509c453b5c..b6c0a08b05 100644
--- a/activesupport/test/message_encryptor_test.rb
+++ b/activesupport/test/message_encryptor_test.rb
@@ -60,12 +60,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,7 +92,7 @@ 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
diff --git a/activesupport/test/message_verifier_test.rb b/activesupport/test/message_verifier_test.rb
index a8633f7299..a5748d28ba 100644
--- a/activesupport/test/message_verifier_test.rb
+++ b/activesupport/test/message_verifier_test.rb
@@ -11,7 +11,7 @@ require 'active_support/time'
require 'active_support/json'
class MessageVerifierTest < ActiveSupport::TestCase
-
+
class JSONSerializer
def dump(value)
ActiveSupport::JSON.encode(value)
@@ -21,7 +21,7 @@ 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) }
@@ -43,18 +43,32 @@ class MessageVerifierTest < ActiveSupport::TestCase
assert_not_verified("#{data}--#{hash.reverse}")
assert_not_verified("purejunk")
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.verify(message)
ensure
ActiveSupport.use_standard_json_time_format = prev
end
-
+
+ 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.verify(valid_message)
+ end
+ assert_includes ["uninitialized constant MessageVerifierTest::AutoloadClass",
+ "undefined class/module MessageVerifierTest::AutoloadClass"], exception.message
+ end
+
def assert_not_verified(message)
assert_raise(ActiveSupport::MessageVerifier::InvalidSignature) do
@verifier.verify(message)
diff --git a/activesupport/test/multibyte_chars_test.rb b/activesupport/test/multibyte_chars_test.rb
index 2bf73291a2..659fceb852 100644
--- a/activesupport/test/multibyte_chars_test.rb
+++ b/activesupport/test/multibyte_chars_test.rb
@@ -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/number_helper_test.rb b/activesupport/test/number_helper_test.rb
index 1fadef3637..6d8d835de7 100644
--- a/activesupport/test/number_helper_test.rb
+++ b/activesupport/test/number_helper_test.rb
@@ -102,7 +102,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
@@ -124,6 +123,12 @@ 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))
end
end
@@ -156,6 +161,14 @@ 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 )
end
end
@@ -370,12 +383,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/ordered_hash_test.rb b/activesupport/test/ordered_hash_test.rb
index c3fe89de4b..0b54026c64 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'
diff --git a/activesupport/test/safe_buffer_test.rb b/activesupport/test/safe_buffer_test.rb
index 047b89be2a..efa9d5e61f 100644
--- a/activesupport/test/safe_buffer_test.rb
+++ b/activesupport/test/safe_buffer_test.rb
@@ -140,4 +140,29 @@ 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
end
diff --git a/activesupport/test/subscriber_test.rb b/activesupport/test/subscriber_test.rb
new file mode 100644
index 0000000000..253411aa3d
--- /dev/null
+++ b/activesupport/test/subscriber_test.rb
@@ -0,0 +1,40 @@
+require 'abstract_unit'
+require 'active_support/subscriber'
+
+class TestSubscriber < ActiveSupport::Subscriber
+ attach_to :doodle
+
+ cattr_reader :event
+
+ def self.clear
+ @@event = nil
+ end
+
+ def open_party(event)
+ @@event = event
+ end
+
+ private
+
+ def private_party(event)
+ @@event = 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.event.name
+ end
+
+ def test_does_not_attach_private_methods
+ ActiveSupport::Notifications.instrument("private_party.doodle")
+
+ assert_nil TestSubscriber.event
+ end
+end
diff --git a/activesupport/test/test_test.rb b/activesupport/test/test_test.rb
index 0c8cc1f883..0fa08c0e3a 100644
--- a/activesupport/test/test_test.rb
+++ b/activesupport/test/test_test.rb
@@ -1,4 +1,6 @@
require 'abstract_unit'
+require 'active_support/core_ext/date'
+require 'active_support/core_ext/numeric/time'
class AssertDifferenceTest < ActiveSupport::TestCase
def setup
@@ -19,10 +21,10 @@ 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
@@ -71,7 +73,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 +81,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 +124,6 @@ class SetupAndTeardownTest < ActiveSupport::TestCase
end
end
-
class SubclassSetupAndTeardownTest < SetupAndTeardownTest
setup :bar
teardown :bar
@@ -143,7 +144,6 @@ class SubclassSetupAndTeardownTest < SetupAndTeardownTest
end
end
-
class TestCaseTaggedLoggingTest < ActiveSupport::TestCase
def before_setup
require 'stringio'
@@ -156,3 +156,65 @@ class TestCaseTaggedLoggingTest < ActiveSupport::TestCase
assert_match "#{self.class}: #{name}\n", @out.string
end
end
+
+class TimeHelperTest < 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, Time.now
+ assert_equal expected_time.to_date, Date.today
+ end
+
+ def test_time_helper_travel_with_block
+ expected_time = Time.now + 1.day
+
+ travel 1.day do
+ assert_equal expected_time, Time.now
+ assert_equal expected_time.to_date, Date.today
+ end
+
+ assert_not_equal expected_time, Time.now
+ assert_not_equal expected_time.to_date, Date.today
+ 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
+ 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
+ end
+
+ assert_not_equal expected_time, Time.now
+ assert_not_equal Date.new(2004, 11, 24), Date.today
+ 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
+ travel_back
+
+ assert_not_equal expected_time, Time.now
+ assert_not_equal Date.new(2004, 11, 24), Date.today
+ end
+end
diff --git a/activesupport/test/testing/constant_lookup_test.rb b/activesupport/test/testing/constant_lookup_test.rb
index aca2951450..71a9561189 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
diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb
index 84c3154e53..cd79efbe8c 100644
--- a/activesupport/test/time_zone_test.rb
+++ b/activesupport/test/time_zone_test.rb
@@ -89,14 +89,39 @@ 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_local
diff --git a/activesupport/test/xml_mini_test.rb b/activesupport/test/xml_mini_test.rb
index d992028323..753effb54e 100644
--- a/activesupport/test/xml_mini_test.rb
+++ b/activesupport/test/xml_mini_test.rb
@@ -169,4 +169,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