[DRE-commits] [ruby-activesupport-3.2] 01/01: New upstream version 4.0.2
Ondrej Sury
ondrej at moszumanska.debian.org
Tue Apr 29 10:53:22 UTC 2014
This is an automated email from the git hooks/post-receive script.
ondrej pushed a commit to annotated tag upstream/4.0.2
in repository ruby-activesupport-3.2.
commit aaaf9f5a0ab8a76ed116695b6887cabadc832149
Author: Ondřej Surý <ondrej at sury.org>
Date: Wed Dec 4 17:30:18 2013 +0100
New upstream version 4.0.2
---
CHANGELOG.md | 570 ++++++++++++++----
MIT-LICENSE | 2 +-
README.rdoc | 10 +-
checksums.yaml.gz | Bin 271 -> 269 bytes
lib/active_support.rb | 29 +-
lib/active_support/backtrace_cleaner.rb | 64 ++-
lib/active_support/base64.rb | 54 --
lib/active_support/basic_object.rb | 24 +-
lib/active_support/benchmarkable.rb | 34 +-
lib/active_support/buffered_logger.rb | 122 +---
lib/active_support/cache.rb | 411 +++++++------
lib/active_support/cache/file_store.rb | 28 +-
lib/active_support/cache/mem_cache_store.rb | 54 +-
lib/active_support/cache/memory_store.rb | 18 +-
lib/active_support/cache/strategy/local_cache.rb | 85 +--
lib/active_support/callbacks.rb | 454 +++++++--------
lib/active_support/concern.rb | 39 +-
lib/active_support/concurrency/latch.rb | 27 +
lib/active_support/configurable.rb | 81 ++-
lib/active_support/core_ext.rb | 5 +-
lib/active_support/core_ext/array.rb | 1 -
lib/active_support/core_ext/array/access.rb | 26 +-
lib/active_support/core_ext/array/conversions.rb | 170 ++++--
.../core_ext/array/extract_options.rb | 4 +-
lib/active_support/core_ext/array/grouping.rb | 43 +-
lib/active_support/core_ext/array/random_access.rb | 30 -
lib/active_support/core_ext/array/uniq_by.rb | 21 +-
lib/active_support/core_ext/array/wrap.rb | 29 +-
lib/active_support/core_ext/benchmark.rb | 7 +
.../core_ext/big_decimal/conversions.rb | 30 +-
lib/active_support/core_ext/class/attribute.rb | 72 ++-
.../core_ext/class/attribute_accessors.rb | 26 +-
.../core_ext/class/delegating_attributes.rb | 34 +-
lib/active_support/core_ext/class/subclasses.rb | 16 +-
lib/active_support/core_ext/date.rb | 5 +
lib/active_support/core_ext/date/calculations.rb | 231 ++------
lib/active_support/core_ext/date/conversions.rb | 54 +-
lib/active_support/core_ext/date/freeze.rb | 33 --
lib/active_support/core_ext/date/zones.rb | 27 +-
.../core_ext/date_and_time/calculations.rb | 232 ++++++++
lib/active_support/core_ext/date_time.rb | 4 +
lib/active_support/core_ext/date_time/acts_like.rb | 1 +
.../core_ext/date_time/calculations.rb | 133 +++--
.../core_ext/date_time/conversions.rb | 64 +--
lib/active_support/core_ext/date_time/zones.rb | 19 +-
lib/active_support/core_ext/enumerable.rb | 99 +---
lib/active_support/core_ext/exception.rb | 3 -
lib/active_support/core_ext/file.rb | 1 -
lib/active_support/core_ext/file/atomic.rb | 38 +-
lib/active_support/core_ext/file/path.rb | 5 -
lib/active_support/core_ext/float.rb | 1 -
lib/active_support/core_ext/float/rounding.rb | 19 -
lib/active_support/core_ext/hash.rb | 1 -
lib/active_support/core_ext/hash/conversions.rb | 224 +++++---
lib/active_support/core_ext/hash/deep_dup.rb | 18 -
lib/active_support/core_ext/hash/deep_merge.rb | 22 +-
lib/active_support/core_ext/hash/diff.rb | 9 +-
lib/active_support/core_ext/hash/except.rb | 10 +-
.../core_ext/hash/indifferent_access.rb | 9 +-
lib/active_support/core_ext/hash/keys.rb | 132 ++++-
lib/active_support/core_ext/hash/reverse_merge.rb | 5 +-
lib/active_support/core_ext/hash/slice.rb | 24 +-
lib/active_support/core_ext/integer/inflections.rb | 14 +-
lib/active_support/core_ext/integer/time.rb | 29 +-
lib/active_support/core_ext/io.rb | 15 -
lib/active_support/core_ext/kernel/debugger.rb | 4 +-
lib/active_support/core_ext/kernel/reporting.rb | 58 +-
.../core_ext/kernel/singleton_class.rb | 7 -
lib/active_support/core_ext/load_error.rb | 12 +-
lib/active_support/core_ext/logger.rb | 30 +-
lib/active_support/core_ext/marshal.rb | 21 +
lib/active_support/core_ext/module.rb | 4 +-
lib/active_support/core_ext/module/aliasing.rb | 17 +-
lib/active_support/core_ext/module/anonymous.rb | 9 +-
.../core_ext/module/attr_internal.rb | 1 -
.../core_ext/module/attribute_accessors.rb | 22 +-
lib/active_support/core_ext/module/delegation.rb | 154 +++--
lib/active_support/core_ext/module/deprecation.rb | 22 +-
.../core_ext/module/introspection.rb | 44 +-
lib/active_support/core_ext/module/method_names.rb | 14 -
.../core_ext/module/qualified_const.rb | 28 +-
.../core_ext/module/remove_method.rb | 6 +-
.../core_ext/module/synchronization.rb | 45 --
lib/active_support/core_ext/numeric.rb | 1 +
lib/active_support/core_ext/numeric/conversions.rb | 135 +++++
lib/active_support/core_ext/numeric/time.rb | 12 +-
lib/active_support/core_ext/object.rb | 1 +
lib/active_support/core_ext/object/acts_like.rb | 8 +-
lib/active_support/core_ext/object/blank.rb | 30 +-
lib/active_support/core_ext/object/deep_dup.rb | 46 ++
lib/active_support/core_ext/object/duplicable.rb | 31 +-
lib/active_support/core_ext/object/inclusion.rb | 15 +-
.../core_ext/object/instance_variables.rb | 19 +-
lib/active_support/core_ext/object/to_json.rb | 8 +
lib/active_support/core_ext/object/to_param.rb | 7 +-
lib/active_support/core_ext/object/try.rb | 71 ++-
lib/active_support/core_ext/object/with_options.rb | 15 +-
lib/active_support/core_ext/proc.rb | 3 +
lib/active_support/core_ext/process.rb | 1 -
lib/active_support/core_ext/process/daemon.rb | 23 -
lib/active_support/core_ext/range.rb | 3 +-
.../core_ext/range/blockless_step.rb | 29 -
lib/active_support/core_ext/range/conversions.rb | 2 -
lib/active_support/core_ext/range/cover.rb | 3 -
lib/active_support/core_ext/range/each.rb | 24 +
lib/active_support/core_ext/range/include_range.rb | 4 +-
lib/active_support/core_ext/range/overlaps.rb | 2 +-
lib/active_support/core_ext/rexml.rb | 46 --
lib/active_support/core_ext/string.rb | 5 +-
lib/active_support/core_ext/string/access.rb | 185 +++---
lib/active_support/core_ext/string/conversions.rb | 65 +--
lib/active_support/core_ext/string/encoding.rb | 15 +-
lib/active_support/core_ext/string/filters.rb | 42 +-
lib/active_support/core_ext/string/indent.rb | 43 ++
lib/active_support/core_ext/string/inflections.rb | 130 +++--
lib/active_support/core_ext/string/inquiry.rb | 4 +-
.../core_ext/string/interpolation.rb | 2 -
lib/active_support/core_ext/string/multibyte.rb | 105 ++--
.../core_ext/string/output_safety.rb | 110 ++--
lib/active_support/core_ext/string/xchar.rb | 18 -
lib/active_support/core_ext/string/zones.rb | 13 +
lib/active_support/core_ext/struct.rb | 6 +
lib/active_support/core_ext/thread.rb | 79 +++
lib/active_support/core_ext/time.rb | 5 +
lib/active_support/core_ext/time/calculations.rb | 292 ++++------
lib/active_support/core_ext/time/conversions.rb | 78 +--
lib/active_support/core_ext/time/marshal.rb | 27 -
.../core_ext/time/publicize_conversion_methods.rb | 10 -
lib/active_support/core_ext/time/zones.rb | 44 +-
lib/active_support/core_ext/uri.rb | 30 +-
lib/active_support/dependencies.rb | 304 +++++-----
lib/active_support/dependencies/autoload.rb | 67 ++-
lib/active_support/deprecation.rb | 53 +-
lib/active_support/deprecation/behaviors.rb | 87 ++-
.../deprecation/instance_delegator.rb | 24 +
lib/active_support/deprecation/method_wrappers.rb | 51 +-
lib/active_support/deprecation/proxy_wrappers.rb | 71 ++-
lib/active_support/deprecation/reporting.rb | 51 +-
lib/active_support/descendants_tracker.rb | 53 +-
lib/active_support/duration.rb | 14 +-
lib/active_support/file_update_checker.rb | 110 ++--
lib/active_support/gzip.rb | 16 +-
lib/active_support/hash_with_indifferent_access.rb | 169 ++++--
lib/active_support/i18n.rb | 4 +
lib/active_support/i18n_railtie.rb | 27 +-
lib/active_support/inflections.rb | 26 +-
lib/active_support/inflector/inflections.rb | 179 +++---
lib/active_support/inflector/methods.rb | 341 +++++------
lib/active_support/inflector/transliterate.rb | 33 +-
lib/active_support/json/decoding.rb | 35 +-
lib/active_support/json/encoding.rb | 124 ++--
lib/active_support/json/variable.rb | 11 +-
lib/active_support/key_generator.rb | 75 +++
lib/active_support/lazy_load_hooks.rb | 40 +-
lib/active_support/locale/en.yml | 103 +++-
lib/active_support/log_subscriber.rb | 83 ++-
lib/active_support/log_subscriber/test_helper.rb | 33 +-
lib/active_support/logger.rb | 57 ++
lib/active_support/logger_silence.rb | 24 +
lib/active_support/memoizable.rb | 116 ----
lib/active_support/message_encryptor.rb | 73 +--
lib/active_support/message_verifier.rb | 24 +-
lib/active_support/multibyte.rb | 33 +-
lib/active_support/multibyte/chars.rb | 413 +++----------
lib/active_support/multibyte/exceptions.rb | 8 -
lib/active_support/multibyte/unicode.rb | 140 ++---
lib/active_support/multibyte/utils.rb | 60 --
lib/active_support/notifications.rb | 105 +++-
lib/active_support/notifications/fanout.rb | 130 ++++-
lib/active_support/notifications/instrumenter.rb | 47 +-
lib/active_support/number_helper.rb | 637 +++++++++++++++++++++
lib/active_support/ordered_hash.rb | 198 +------
lib/active_support/ordered_options.rb | 44 +-
lib/active_support/per_thread_registry.rb | 52 ++
lib/active_support/proxy_object.rb | 13 +
lib/active_support/rails.rb | 27 +
lib/active_support/railtie.rb | 51 +-
lib/active_support/rescuable.rb | 14 +-
lib/active_support/ruby/shim.rb | 22 -
lib/active_support/string_inquirer.rb | 21 +-
lib/active_support/subscriber.rb | 93 +++
lib/active_support/tagged_logging.rb | 109 ++--
lib/active_support/test_case.rb | 59 +-
lib/active_support/testing/assertions.rb | 82 ++-
lib/active_support/testing/autorun.rb | 5 +
lib/active_support/testing/constant_lookup.rb | 54 ++
lib/active_support/testing/declarative.rb | 2 +-
lib/active_support/testing/deprecation.rb | 19 -
lib/active_support/testing/isolation.rb | 82 +--
lib/active_support/testing/mochaing.rb | 7 -
lib/active_support/testing/pending.rb | 48 +-
lib/active_support/testing/performance.rb | 317 ----------
lib/active_support/testing/performance/jruby.rb | 115 ----
lib/active_support/testing/performance/rubinius.rb | 113 ----
lib/active_support/testing/performance/ruby.rb | 152 -----
lib/active_support/testing/performance/ruby/mri.rb | 57 --
.../testing/performance/ruby/yarv.rb | 57 --
lib/active_support/testing/setup_and_teardown.rb | 98 +---
lib/active_support/testing/tagged_logging.rb | 25 +
lib/active_support/time.rb | 27 +-
lib/active_support/time/autoload.rb | 5 -
lib/active_support/time_with_zone.rb | 129 +++--
lib/active_support/values/time_zone.rb | 152 +++--
lib/active_support/values/unicode_tables.dat | Bin 813343 -> 904483 bytes
lib/active_support/version.rb | 13 +-
lib/active_support/whiny_nil.rb | 24 -
lib/active_support/xml_mini.rb | 49 +-
lib/active_support/xml_mini/jdom.rb | 9 +-
lib/active_support/xml_mini/libxml.rb | 3 +-
lib/active_support/xml_mini/libxmlsax.rb | 5 +-
lib/active_support/xml_mini/nokogiri.rb | 3 +-
lib/active_support/xml_mini/nokogirisax.rb | 5 +-
lib/active_support/xml_mini/rexml.rb | 3 +-
metadata.yml | 136 +++--
214 files changed, 6911 insertions(+), 6085 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ea72f69..f0a0459 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,58 +1,346 @@
-## unreleased ##
+## Rails 4.0.1 (November 01, 2013) ##
-* No changes.
+* Disable the ability to iterate over Range of AS::TimeWithZone
+ due to significant performance issues.
+ *Bogdan Gusiev*
-## Rails 3.2.13 (Feb 17, 2013) ##
+* Fix `ActiveSupport::Cache::FileStore#cleanup` to no longer rely on missing `each_key` method.
+ *Murray Steele*
-* Fix DateTime comparison with DateTime::Infinity object.
+* Ensure that autoloaded constants in all-caps nestings are marked as
+ autoloaded.
- *Dan Kubb*
+ *Simon Coffey*
-* Remove surrogate unicode character encoding from ActiveSupport::JSON.encode
- The encoding scheme was broken for unicode characters outside the basic
- multilingual plane; since json is assumed to be UTF-8, and we already force the
- encoding to UTF-8 simply pass through the un-encoded characters.
+* Adds a new deprecation behaviour that raises an exception. Throwing this
+ line into `config/environments/development.rb`:
- *Brett Carter*
+ ActiveSupport::Deprecation.behavior = :raise
+
+ will cause the application to raise an `ActiveSupport::DeprecationException`
+ on deprecations.
+
+ Use this for aggressive deprecation cleanups.
+
+ *Xavier Noria*
+
+* Improve `ActiveSupport::Cache::MemoryStore` cache size calculation.
+ The memory used by a key/entry pair is calculated via `#cached_size`:
+
+ def cached_size(key, entry)
+ key.to_s.bytesize + entry.size + PER_ENTRY_OVERHEAD
+ end
+
+ The value of `PER_ENTRY_OVERHEAD` is 240 bytes based on an [empirical
+ estimation](https://gist.github.com/ssimeonov/6047200) for 64-bit MRI on
+ 1.9.3 and 2.0.
+
+ Fixes #11512.
+
+ *Simeon Simeonov*
+
+* Only raise `Module::DelegationError` if it's the source of the exception.
+
+ Fixes #10559.
+
+* Add `DateTime#usec` and `DateTime#nsec` so that `ActiveSupport::TimeWithZone` keeps
+ sub-second resolution when wrapping a `DateTime` value.
+
+ Fixes #10855.
+
+ *Andrew White*
+
+* Make `Time.at_with_coercion` retain the second fraction and return local time.
+
+ Fixes #11350.
+
+ *Neer Friedman*, *Andrew White*
+
+* Fix return value from `BacktraceCleaner#noise` when the cleaner is configured
+ with multiple silencers.
+
+ Fixes #11030.
-* Fix mocha v0.13.0 compatibility. *James Mead*
+ *Mark J. Titorenko*
-* `#as_json` isolates options when encoding a hash. [Backport #8185]
- Fix #8182
+* Fix `ActiveSupport::Dependencies::Loadable#load_dependency` calling
+ `#blame_file!` on Exceptions that do not have the Blamable mixin
+
+ *Andrew Kreiling*
+
+
+## Rails 4.0.0 (June 25, 2013) ##
+
+* Override `Time.at` to support the passing of Time-like values when called with a single argument.
+
+ *Andrew White*
+
+* Allow Date to be compared with Time (like it was possible to compare Time with Date).
+
+ *DHH*
+
+* Deprecate multiple parameters support of `Object#in?`.
+
+ *Brian Morearty + Carlos Antonio da Silva*
+
+* An `ActiveSupport::Subscriber` class has been extracted from
+ `ActiveSupport::LogSubscriber`, allowing you to use the event attachment
+ API for other kinds of subscribers.
+
+ *Daniel Schierbeck*
+
+* `Class#class_attribute` accepts an `instance_predicate` option which
+ defaults to `true`. If set to `false` the predicate method will not
+ be defined.
+
+ *Agis Anastasopoulos*
+
+* `fast_xs` support has been removed. Use `String#encode(xml: :attr)`.
+
+* `ActiveSupport::Notifications::Instrumenter#instrument` should
+ yield its payload.
+
+ *stopdropandrew*
+
+* `ActiveSupport::TimeWithZone` raises `NoMethodError` in proper context.
+ Fixes #9772.
*Yves Senn*
-* Handle the possible Permission Denied errors atomic.rb might trigger due to
- its chown and chmod calls. [Backport #8027]
+* Fix deletion of empty directories in `ActiveSupport::Cache::FileStore`.
- *Daniele Sluijters*
+ *Charles Jones*
+
+* Improve singularizing a singular for multiple cases.
+ Fixes #2608 #1825 #2395.
+
+ Example:
+ # Before
+ 'address'.singularize # => 'addres'
-## Rails 3.2.12 (Feb 11, 2013) ##
+ # After
+ 'address'.singularize # => 'address'
-* No changes.
+ *Mark McSpadden*
+* Prevent `DateTime#change` from truncating the second fraction, when seconds
+ do not need to be changed.
-## Rails 3.2.11 (Jan 8, 2012) ##
+ *Chris Baynes*
-* Hash.from_xml raises when it encounters type="symbol" or type="yaml".
- Use Hash.from_trusted_xml to parse this XML.
+* Added `ActiveSupport::TimeWithZone#to_r` for `Time#at` compatibility.
+
+ Before this change:
+
+ Time.zone = 'Tokyo'
+ time = Time.zone.now
+ time == Time.at(time) # => false
+
+ After the change:
+
+ Time.zone = 'Tokyo'
+ time = Time.zone.now
+ time == Time.at(time) # => true
+
+ *stopdropandrew*
+
+* `ActiveSupport::NumberHelper#number_to_human` returns the number unaltered when
+ the given units hash does not contain the needed key, e.g. when the number provided
+ is less than the largest key provided.
+ Fixes #9269.
+
+ Examples:
+
+ number_to_human(123, units: {}) # => 123
+ number_to_human(123, units: { thousand: 'k' }) # => 123
+
+ *Michael Hoffman*
+
+* Added `beginning_of_minute` support to core ext calculations for `Time` and `DateTime`.
+
+ *Gagan Awhad*
+
+* Add `:nsec` date format.
+
+ *Jamie Gaskins*
+
+* `ActiveSupport::Gzip.compress` allows two optional arguments for compression
+ level and strategy.
+
+ *Beyond*
+
+* Modify `TimeWithZone#as_json` to include 3 decimal places of sub-second accuracy
+ by default, which is optional as per the ISO8601 spec, but extremely useful. Also
+ the default behaviour of `Date#toJSON()` in recent versions of Chrome, Safari and
+ Firefox.
+
+ *James Harton*
+
+* Improve `String#squish` to handle Unicode whitespace. *Antoine Lyset*
+
+* Standardise on `to_time` returning an instance of `Time` in the local system timezone
+ across `String`, `Time`, `Date`, `DateTime` and `ActiveSupport::TimeWithZone`.
+
+ *Andrew White*
+
+* Extract `ActiveSupport::Testing::Performance` into https://github.com/rails/rails-perftest
+ You can add the gem to your `Gemfile` to keep using performance tests.
+
+ gem 'rails-perftest'
+
+ *Yves Senn*
+
+* `Hash.from_xml` raises when it encounters `type="symbol"` or `type="yaml"`.
+ Use `Hash.from_trusted_xml` to parse this XML.
CVE-2013-0156
*Jeremy Kemper*
+* Deprecate `assert_present` and `assert_blank` in favor of
+ `assert object.blank?` and `assert object.present?`
+
+ *Yves Senn*
+
+* Change `String#to_date` to use `Date.parse`. This gives more consistent error
+ messages and allows the use of partial dates.
+
+ "gibberish".to_date => Argument Error: invalid date
+ "3rd Feb".to_date => Sun, 03 Feb 2013
+
+ *Kelly Stannard*
+
+* Remove meaningless `ActiveSupport::FrozenObjectError`, which was just an alias of `RuntimeError`.
+
+ *Akira Matsuda*
+
+* Introduce `assert_not` to replace warty `assert !foo`. *Jeremy Kemper*
+
+* Prevent `Callbacks#set_callback` from setting the same callback twice.
+
+ before_save :foo, :bar, :foo
+
+ will at first call `bar`, then `foo`. `foo` will no more be called
+ twice.
+
+ *Dmitriy Kiriyenko*
+
+* Add `ActiveSupport::Logger#silence` that works the same as the old `Logger#silence` extension.
+
+ *DHH*
+
+* Remove surrogate unicode character encoding from `ActiveSupport::JSON.encode`
+ The encoding scheme was broken for unicode characters outside the basic multilingual plane;
+ since json is assumed to be UTF-8, and we already force the encoding to UTF-8,
+ simply pass through the un-encoded characters.
-## Rails 3.2.10 (Jan 2, 2013) ##
+ *Brett Carter*
+
+* Deprecate `Time.time_with_date_fallback`, `Time.utc_time` and `Time.local_time`.
+ These methods were added to handle the limited range of Ruby's native `Time`
+ implementation. Those limitations no longer apply so we are deprecating them in 4.0
+ and they will be removed in 4.1.
+
+ *Andrew White*
+
+* Deprecate `Date#to_time_in_current_zone` and add `Date#in_time_zone`. *Andrew White*
+
+* Add `String#in_time_zone` method to convert a string to an `ActiveSupport::TimeWithZone`. *Andrew White*
+
+* Deprecate `ActiveSupport::BasicObject` in favor of `ActiveSupport::ProxyObject`.
+ This class is used for proxy classes. It avoids confusion with Ruby's `BasicObject`
+ class.
+
+ *Francesco Rodriguez*
+
+* Patched `Marshal#load` to work with constant autoloading. Fixes autoloading
+ with cache stores that rely on `Marshal` (`MemCacheStore` and `FileStore`).
+ Fixes #8167.
+
+ *Uriel Katz*
+
+* Make `Time.zone.parse` to work with JavaScript format date strings. *Andrew White*
+
+* Add `DateTime#seconds_until_end_of_day` and `Time#seconds_until_end_of_day`
+ as a complement for `seconds_from_midnight`; useful when setting expiration
+ times for caches, e.g.:
+
+ <% cache('dashboard', expires_in: Date.current.seconds_until_end_of_day) do %>
+ ...
+
+ *Olek Janiszewski*
+
+* No longer proxy `ActiveSupport::Multibyte#class`. *Steve Klabnik*
+
+* Deprecate `ActiveSupport::TestCase#pending` method, use `skip` from minitest instead. *Carlos Antonio da Silva*
+
+* `XmlMini.with_backend` now may be safely used with threads:
+
+ Thread.new do
+ XmlMini.with_backend("REXML") { rexml_power }
+ end
+ Thread.new do
+ XmlMini.with_backend("LibXML") { libxml_power }
+ end
+
+ Each thread will use it's own backend.
+
+ *Nikita Afanasenko*
+
+* Dependencies no longer trigger `Kernel#autoload` in `remove_constant`. Fixes #8213. *Xavier Noria*
+
+* Simplify `mocha` integration and remove monkey-patches, bumping `mocha` to 0.13.0. *James Mead*
+
+* `#as_json` isolates options when encoding a hash. Fixes #8182.
+
+ *Yves Senn*
+
+* Deprecate `Hash#diff` in favor of minitest's #diff. *Steve Klabnik*
+
+* `Kernel#capture` can catch output from subprocesses. *Dmitry Vorotilin*
+
+* `to_xml` conversions now use builder's `tag!` method instead of explicit invocation of `method_missing`.
+
+ *Nikita Afanasenko*
+
+* Fixed timezone mapping of the Solomon Islands. *Steve Klabnik*
+
+* Make callstack attribute optional in `ActiveSupport::Deprecation::Reporting`
+ methods `warn` and `deprecation_warning`.
+
+ *Alexey Gaziev*
+
+* Implement `HashWithIndifferentAccess#replace` so `key?` works correctly. *David Graham*
+
+* Handle the possible permission denied errors `atomic.rb` might trigger due to its `chown`
+ and `chmod` calls.
+
+ *Daniele Sluijters*
+
+* `Hash#extract!` returns only those keys that present in the receiver.
+
+ {a: 1, b: 2}.extract!(:a, :x) # => {:a => 1}
+
+ *Mikhail Dieterle*
-* No changes.
+* `Hash#extract!` returns the same subclass, that the receiver is. I.e.
+ `HashWithIndifferentAccess#extract!` returns a `HashWithIndifferentAccess` instance.
+ *Mikhail Dieterle*
-## Rails 3.2.9 (Nov 12, 2012) ##
+* Optimize `ActiveSupport::Cache::Entry` to reduce memory and processing overhead. *Brian Durand*
-* Add logger.push_tags and .pop_tags to complement logger.tagged:
+* Tests tag the Rails log with the current test class and test case:
+
+ [SessionsControllerTest] [test_0002_sign in] Processing by SessionsController#create as HTML
+ [SessionsControllerTest] [test_0002_sign in] ...
+
+ *Jeremy Kemper*
+
+* Add `logger.push_tags` and `.pop_tags` to complement `logger.tagged`:
class Job
def before
@@ -66,139 +354,223 @@
*Jeremy Kemper*
-* Add %:z and %::z format string support to ActiveSupport::TimeWithZone#strftime. [fixes #6962] *kennyj*
+* Allow delegation to the class using the `:class` keyword, replacing
+ `self.class` usage:
+
+ class User
+ def self.hello
+ "world"
+ end
+
+ delegate :hello, to: :class
+ end
+
+ *Marc-Andre Lafortune*
+
+* `Date.beginning_of_week` thread local and `beginning_of_week` application
+ config option added (default is Monday).
+
+ *Innokenty Mikhailov*
+
+* An optional block can be passed to `config_accessor` to set its default value
+
+ class User
+ include ActiveSupport::Configurable
+
+ config_accessor :hair_colors do
+ [:brown, :black, :blonde, :red]
+ end
+ end
+
+ User.hair_colors # => [:brown, :black, :blonde, :red]
+
+ *Larry Lv*
+
+* `ActiveSupport::Benchmarkable#silence` has been deprecated due to its lack of
+ thread safety. It will be removed without replacement in Rails 4.1.
+
+ *Steve Klabnik*
+* An optional block can be passed to `Hash#deep_merge`. The block will be invoked
+ for each duplicated key and used to resolve the conflict.
-## Rails 3.2.8 (Aug 9, 2012) ##
+ *Pranas Kiziela*
-* Fix ActiveSupport integration with Mocha > 0.12.1. *Mike Gunderloy*
+* `ActiveSupport::Deprecation` is now a class. It is possible to create an instance
+ of deprecator. Backwards compatibility has been preserved.
-* Reverted the deprecation of ActiveSupport::JSON::Variable. *Rafael Mendonça França*
+ You can choose which instance of the deprecator will be used.
-* ERB::Util.html_escape now escapes single quotes. *Santiago Pastorino*
+ deprecate :method_name, deprecator: deprecator_instance
+ You can use `ActiveSupport::Deprecation` in your gem.
-## Rails 3.2.7 (Jul 26, 2012) ##
+ require 'active_support/deprecation'
+ require 'active_support/core_ext/module/deprecation'
-* Hash#fetch(fetch) is not the same as doing hash[key]
+ class MyGem
+ def self.deprecator
+ ActiveSupport::Deprecation.new('2.0', 'MyGem')
+ end
+
+ def old_method
+ end
+
+ def new_method
+ end
+
+ deprecate old_method: :new_method, deprecator: deprecator
+ end
+
+ MyGem.new.old_method
+ # => DEPRECATION WARNING: old_method is deprecated and will be removed from MyGem 2.0 (use new_method instead). (called from <main> at file.rb:18)
+
+ *Piotr Niełacny & Robert Pankowecki*
+
+* `ERB::Util.html_escape` encodes single quote as `#39`. Decimal form has better support in old browsers. *Kalys Osmonov*
-* adds a missing require [fixes #6896]
+* `ActiveSupport::Callbacks`: deprecate monkey patch of object callbacks.
+ Using the `filter` method like this:
-* make sure the inflection rules are loaded when cherry-picking active_support/core_ext/string/inflections.rb [fixes #6884]
+ before_filter MyFilter.new
-* Merge pull request #6857 from rsutphin/as_core_ext_time_missing_require
+ class MyFilter
+ def filter(controller)
+ end
+ end
+
+ Is now deprecated with recommendation to use the corresponding filter type
+ (`#before`, `#after` or `#around`):
+
+ before_filter MyFilter.new
+
+ class MyFilter
+ def before(controller)
+ end
+ end
+
+ *Bogdan Gusiev*
-* bump AS deprecation_horizon to 4.0
+* An optional block can be passed to `HashWithIndifferentAccess#update` and `#merge`.
+ The block will be invoked for each duplicated key, and used to resolve the conflict,
+ thus replicating the behaviour of the corresponding methods on the `Hash` class.
+ *Leo Cassarani*
-## Rails 3.2.6 (Jun 12, 2012) ##
+* Remove `j` alias for `ERB::Util#json_escape`.
+ The `j` alias is already used for `ActionView::Helpers::JavaScriptHelper#escape_javascript`
+ and both modules are included in the view context that would confuse the developers.
-* No changes.
+ *Akira Matsuda*
+* Replace deprecated `memcache-client` gem with `dalli` in `ActiveSupport::Cache::MemCacheStore`.
-## Rails 3.2.5 (Jun 1, 2012) ##
+ *Guillermo Iguaran*
-* ActiveSupport::JSON::Variable is deprecated. Define your own #as_json and #encode_json methods
- for custom JSON string literals. *Erich Menge*
+* Add default values to all `ActiveSupport::NumberHelper` methods, to avoid
+ errors with empty locales or missing values.
+ *Carlos Antonio da Silva*
-## Rails 3.2.4 (May 31, 2012) ##
+* `ActiveSupport::JSON::Variable` is deprecated. Define your own `#as_json` and
+ `#encode_json` methods for custom JSON string literals.
-* Added #beginning_of_hour and #end_of_hour to Time and DateTime core
- extensions. *Mark J. Titorenko*
+ *Erich Menge*
+* Add `String#indent`. *fxn & Ace Suares*
-## Rails 3.2.3 (March 30, 2012) ##
+* Inflections can now be defined per locale. `singularize` and `pluralize`
+ accept locale as an extra argument.
-* No changes.
+ *David Celis*
+* `Object#try` will now return `nil` instead of raise a `NoMethodError` if the
+ receiving object does not implement the method, but you can still get the
+ old behavior by using the new `Object#try!`.
-## Rails 3.2.2 (March 1, 2012) ##
+ *DHH*
-* No changes.
+* `ERB::Util.html_escape` now escapes single quotes. *Santiago Pastorino*
+* `Time#change` now works with time values with offsets other than UTC or the local time zone. *Andrew White*
-## Rails 3.2.1 (January 26, 2012) ##
+* `ActiveSupport::Callbacks`: deprecate usage of filter object with `#before` and `#after` methods as `around` callback. *Bogdan Gusiev*
-* Documentation fixes and improvements.
+* Add `Time#prev_quarter` and `Time#next_quarter` short-hands for `months_ago(3)` and `months_since(3)`. *SungHee Kang*
-* Update time zone offset information. *Ravil Bayramgalin*
+* Remove obsolete and unused `require_association` method from dependencies. *fxn*
-* The deprecated `ActiveSupport::Base64.decode64` calls `::Base64.decode64`
- now. *Jonathan Viney*
+* Add `:instance_accessor` option for `config_accessor`.
+
+ class User
+ include ActiveSupport::Configurable
+ config_accessor :allowed_access, instance_accessor: false
+ end
-* Fixes uninitialized constant `ActiveSupport::TaggedLogging::ERROR`. *kennyj*
+ User.new.allowed_access = true # => NoMethodError
+ User.new.allowed_access # => NoMethodError
+ *Francesco Rodriguez*
-## Rails 3.2.0 (January 20, 2012) ##
+* `ActionView::Helpers::NumberHelper` methods have been moved to `ActiveSupport::NumberHelper` and are now available via
+ `Numeric#to_s`. `Numeric#to_s` now accepts the formatting options `:phone`, `:currency`, `:percentage`, `:delimited`,
+ `:rounded`, `:human`, and `:human_size`.
-* ActiveSupport::Base64 is deprecated in favor of ::Base64. *Sergey Nartimov*
+ *Andrew Mutz*
-* Module#synchronize is deprecated with no replacement. Please use `monitor`
- from ruby's standard library.
+* Add `Hash#transform_keys`, `Hash#transform_keys!`, `Hash#deep_transform_keys`, and `Hash#deep_transform_keys!`. *Mark McSpadden*
-* (Date|DateTime|Time)#beginning_of_week accept an optional argument to
- be able to set the day at which weeks are assumed to start.
+* Changed XML type `datetime` to `dateTime` (with upper case letter `T`). *Angelo Capilleri*
-* Deprecated ActiveSupport::MessageEncryptor#encrypt and decrypt. *José Valim*
+* Add `:instance_accessor` option for `class_attribute`. *Alexey Vakhov*
-* ActiveSupport::Notifications.subscribed provides subscriptions to events while a block runs. *fxn*
+* `constantize` now looks in the ancestor chain. *Marc-Andre Lafortune & Andrew White*
-* Module#qualified_const_(defined?|get|set) are analogous to the corresponding methods
- in the standard API, but accept qualified constant names. *fxn*
+* Adds `Hash#deep_stringify_keys` and `Hash#deep_stringify_keys!` to convert all keys from a `Hash` instance into strings. *Lucas Húngaro*
-* Added inflection #deconstantize which complements #demodulize. This inflection
- removes the righmost segment in a qualified constant name. *fxn*
+* Adds `Hash#deep_symbolize_keys` and `Hash#deep_symbolize_keys!` to convert all keys from a `Hash` instance into symbols. *Lucas Húngaro*
-* Added ActiveSupport:TaggedLogging that can wrap any standard Logger class to provide tagging capabilities *DHH*
+* `Object#try` can't call private methods. *Vasiliy Ermolovich*
- Logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT))
- Logger.tagged("BCX") { Logger.info "Stuff" } # Logs "[BCX] Stuff"
- Logger.tagged("BCX", "Jason") { Logger.info "Stuff" } # Logs "[BCX] [Jason] Stuff"
- Logger.tagged("BCX") { Logger.tagged("Jason") { Logger.info "Stuff" } } # Logs "[BCX] [Jason] Stuff"
+* `AS::Callbacks#run_callbacks` remove `key` argument. *Francesco Rodriguez*
-* Added safe_constantize that constantizes a string but returns nil instead of an exception if the constant (or part of it) does not exist *Ryan Oblak*
+* `deep_dup` works more expectedly now and duplicates also values in `Hash` instances and elements in `Array` instances. *Alexey Gaziev*
-* ActiveSupport::OrderedHash is now marked as extractable when using Array#extract_options! *Prem Sichanugrist*
+* Inflector no longer applies ice -> ouse to words like "slice", "police", etc. *Wes Morgan*
-* Added Array#prepend as an alias for Array#unshift and Array#append as an alias for Array#<< *DHH*
+* Add `ActiveSupport::Deprecations.behavior = :silence` to completely ignore Rails runtime deprecations. *twinturbo*
-* The definition of blank string for Ruby 1.9 has been extended to Unicode whitespace.
- Also, in 1.8 the ideographic space U+3000 is considered to be whitespace. *Akira Matsuda, Damien Mathieu*
+* Make `Module#delegate` stop using `send` - can no longer delegate to private methods. *dasch*
-* The inflector understands acronyms. *dlee*
+* `ActiveSupport::Callbacks`: deprecate `:rescuable` option. *Bogdan Gusiev*
-* Deprecated ActiveSupport::Memoizable in favor of Ruby memoization pattern *José Valim*
+* Adds `Integer#ordinal` to get the ordinal suffix string of an integer. *Tim Gildea*
-* Added Time#all_day/week/quarter/year as a way of generating ranges (example: Event.where(created_at: Time.now.all_week)) *DHH*
+* `ActiveSupport::Callbacks`: `:per_key` option is no longer supported. *Bogdan Gusiev*
-* Added instance_accessor: false as an option to Class#cattr_accessor and friends *DHH*
+* `ActiveSupport::Callbacks#define_callbacks`: add `:skip_after_callbacks_if_terminated` option. *Bogdan Gusiev*
-* Removed ActiveSupport::SecureRandom in favor of SecureRandom from the standard library *Jon Leighton*
+* Add `html_escape_once` to `ERB::Util`, and delegate the `escape_once` tag helper to it. *Carlos Antonio da Silva*
-* ActiveSupport::OrderedHash now has different behavior for #each and
- \#each_pair when given a block accepting its parameters with a splat. *Andrew Radev*
+* Deprecates the compatibility method `Module#local_constant_names`,
+ use `Module#local_constants` instead (which returns symbols). *Xavier Noria*
-* ActiveSupport::BufferedLogger#silence is deprecated. If you want to squelch
- logs for a certain block, change the log level for that block.
+* Deletes the compatibility method `Module#method_names`,
+ use `Module#methods` from now on (which returns symbols). *Xavier Noria*
-* ActiveSupport::BufferedLogger#open_log is deprecated. This method should
- not have been public in the first place.
+* Deletes the compatibility method `Module#instance_method_names`,
+ use `Module#instance_methods` from now on (which returns symbols). *Xavier Noria*
-* ActiveSupport::BufferedLogger's behavior of automatically creating the
- directory for your log file is deprecated. Please make sure to create the
- directory for your log file before instantiating.
+* `BufferedLogger` is deprecated. Use `ActiveSupport::Logger`, or the logger
+ from the Ruby standard library.
-* ActiveSupport::BufferedLogger#auto_flushing is deprecated. Either set the
- sync level on the underlying file handle like this:
+ *Aaron Patterson*
- f = File.open('foo.log', 'w')
- f.sync = true
- ActiveSupport::BufferedLogger.new f
+* Unicode database updated to 6.1.0. *Norman Clarke*
- Or tune your filesystem. The FS cache is now what controls flushing.
+* Adds `encode_big_decimal_as_string` option to force JSON serialization of `BigDecimal` as numeric instead
+ of wrapping them in strings for safety.
-* ActiveSupport::BufferedLogger#flush is deprecated. Set sync on your
- filehandle, or tune your filesystem.
+* Optimize log subscribers to check log level before doing any processing. *Brian Durand*
-Please check [3-1-stable](https://github.com/rails/rails/blob/3-1-stable/activesupport/CHANGELOG.md) for previous changes.
+Please check [3-2-stable](https://github.com/rails/rails/blob/3-2-stable/activesupport/CHANGELOG.md) for previous changes.
diff --git a/MIT-LICENSE b/MIT-LICENSE
index 5e8b7a9..6aeeb71 100644
--- a/MIT-LICENSE
+++ b/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2005-2011 David Heinemeier Hansson
+Copyright (c) 2005-2013 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/README.rdoc b/README.rdoc
index ab19263..58ced93 100644
--- a/README.rdoc
+++ b/README.rdoc
@@ -12,19 +12,21 @@ The latest version of Active Support can be installed with RubyGems:
% [sudo] gem install activesupport
-Source code can be downloaded as part of the Rails project on GitHub
+Source code can be downloaded as part of the Rails project on GitHub:
-* https://github.com/rails/rails/tree/3-2-stable/activesupport
+* https://github.com/rails/rails/tree/4-0-stable/activesupport
== License
-Active Support is released under the MIT license.
+Active Support is released under the MIT license:
+
+* http://www.opensource.org/licenses/MIT
== Support
-API documentation is at
+API documentation is at:
* http://api.rubyonrails.org
diff --git a/checksums.yaml.gz b/checksums.yaml.gz
index 3f51d0a..5110d76 100644
Binary files a/checksums.yaml.gz and b/checksums.yaml.gz differ
diff --git a/lib/active_support.rb b/lib/active_support.rb
index ff78e71..ffa6ffd 100644
--- a/lib/active_support.rb
+++ b/lib/active_support.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2005-2011 David Heinemeier Hansson
+# Copyright (c) 2005-2013 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -22,59 +22,46 @@
#++
require 'securerandom'
-
-module ActiveSupport
- class << self
- attr_accessor :load_all_hooks
- def on_load_all(&hook) load_all_hooks << hook end
- def load_all!; load_all_hooks.each { |hook| hook.call } end
- end
- self.load_all_hooks = []
-
- on_load_all do
- [Dependencies, Deprecation, Gzip, MessageVerifier, Multibyte]
- end
-end
-
require "active_support/dependencies/autoload"
require "active_support/version"
+require "active_support/logger"
+require "active_support/lazy_load_hooks"
module ActiveSupport
extend ActiveSupport::Autoload
+ autoload :Concern
+ autoload :Dependencies
autoload :DescendantsTracker
autoload :FileUpdateChecker
autoload :LogSubscriber
autoload :Notifications
- # TODO: Narrow this list down
eager_autoload do
autoload :BacktraceCleaner
- autoload :Base64
autoload :BasicObject
+ autoload :ProxyObject
autoload :Benchmarkable
- autoload :BufferedLogger
autoload :Cache
autoload :Callbacks
- autoload :Concern
autoload :Configurable
autoload :Deprecation
autoload :Gzip
autoload :Inflector
autoload :JSON
- autoload :Memoizable
+ autoload :KeyGenerator
autoload :MessageEncryptor
autoload :MessageVerifier
autoload :Multibyte
autoload :OptionMerger
autoload :OrderedHash
autoload :OrderedOptions
- autoload :Rescuable
autoload :StringInquirer
autoload :TaggedLogging
autoload :XmlMini
end
+ autoload :Rescuable
autoload :SafeBuffer, "active_support/core_ext/string/output_safety"
autoload :TestCase
end
diff --git a/lib/active_support/backtrace_cleaner.rb b/lib/active_support/backtrace_cleaner.rb
index 8f8deb9..c719c3b 100644
--- a/lib/active_support/backtrace_cleaner.rb
+++ b/lib/active_support/backtrace_cleaner.rb
@@ -1,24 +1,29 @@
module ActiveSupport
- # Backtraces often include many lines that are not relevant for the context under review. This makes it hard to find the
- # signal amongst the backtrace noise, and adds debugging time. With a BacktraceCleaner, filters and silencers are used to
- # remove the noisy lines, so that only the most relevant lines remain.
+ # Backtraces often include many lines that are not relevant for the context
+ # under review. This makes it hard to find the signal amongst the backtrace
+ # noise, and adds debugging time. With a BacktraceCleaner, filters and
+ # silencers are used to remove the noisy lines, so that only the most relevant
+ # lines remain.
#
- # Filters are used to modify lines of data, while silencers are used to remove lines entirely. The typical filter use case
- # is to remove lengthy path information from the start of each line, and view file paths relevant to the app directory
- # instead of the file system root. The typical silencer use case is to exclude the output of a noisy library from the
- # backtrace, so that you can focus on the rest.
- #
- # ==== Example:
+ # Filters are used to modify lines of data, while silencers are used to remove
+ # lines entirely. The typical filter use case is to remove lengthy path
+ # information from the start of each line, and view file paths relevant to the
+ # app directory instead of the file system root. The typical silencer use case
+ # is to exclude the output of a noisy library from the backtrace, so that you
+ # can focus on the rest.
#
# bc = BacktraceCleaner.new
# bc.add_filter { |line| line.gsub(Rails.root, '') }
# bc.add_silencer { |line| line =~ /mongrel|rubygems/ }
# bc.clean(exception.backtrace) # will strip the Rails.root prefix and skip any lines from mongrel or rubygems
#
- # To reconfigure an existing BacktraceCleaner (like the default one in Rails) and show as much data as possible, you can
- # always call <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 BacktraceCleaner#remove_filters! These two methods will give you a completely untouched backtrace.
+ # To reconfigure an existing BacktraceCleaner (like the default one in Rails)
+ # and show as much data as possible, you can always call
+ # <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 BacktraceCleaner#remove_filters! These two
+ # methods will give you a completely untouched backtrace.
#
# Inspired by the Quiet Backtrace gem by Thoughtbot.
class BacktraceCleaner
@@ -26,9 +31,10 @@ module ActiveSupport
@filters, @silencers = [], []
end
- # Returns the backtrace after all filters and silencers have been run against it. Filters run first, then silencers.
+ # Returns the backtrace after all filters and silencers have been run
+ # against it. Filters run first, then silencers.
def clean(backtrace, kind = :silent)
- filtered = filter(backtrace)
+ filtered = filter_backtrace(backtrace)
case kind
when :silent
@@ -39,10 +45,10 @@ module ActiveSupport
filtered
end
end
+ alias :filter :clean
- # Adds a filter from the block provided. Each line in the backtrace will be mapped against this filter.
- #
- # Example:
+ # Adds a filter from the block provided. Each line in the backtrace will be
+ # mapped against this filter.
#
# # Will turn "/my/rails/root/app/models/person.rb" into "/app/models/person.rb"
# backtrace_cleaner.add_filter { |line| line.gsub(Rails.root, '') }
@@ -50,10 +56,8 @@ module ActiveSupport
@filters << block
end
- # Adds a silencer from the block provided. If the silencer returns true for a given line, it will be excluded from
- # the clean backtrace.
- #
- # Example:
+ # Adds a silencer from the block provided. If the silencer returns +true+
+ # for a given line, it will be excluded from the clean backtrace.
#
# # Will reject all lines that include the word "mongrel", like "/gems/mongrel/server.rb" or "/app/my_mongrel_server/rb"
# backtrace_cleaner.add_silencer { |line| line =~ /mongrel/ }
@@ -61,18 +65,22 @@ module ActiveSupport
@silencers << block
end
- # Will remove all silencers, but leave in the filters. This is useful if your context of debugging suddenly expands as
- # you suspect a bug in one of the libraries you use.
+ # Will remove all silencers, but leave in the filters. This is useful if
+ # your context of debugging suddenly expands as you suspect a bug in one of
+ # the libraries you use.
def remove_silencers!
@silencers = []
end
+ # Removes all filters, but leaves in silencers. Useful if you suddenly
+ # need to see entire filepaths in the backtrace that you had already
+ # filtered out.
def remove_filters!
@filters = []
end
private
- def filter(backtrace)
+ def filter_backtrace(backtrace)
@filters.each do |f|
backtrace = backtrace.map { |line| f.call(line) }
end
@@ -89,11 +97,7 @@ module ActiveSupport
end
def noise(backtrace)
- @silencers.each do |s|
- backtrace = backtrace.select { |line| s.call(line) }
- end
-
- backtrace
+ backtrace - silence(backtrace)
end
end
end
diff --git a/lib/active_support/base64.rb b/lib/active_support/base64.rb
deleted file mode 100644
index da14107..0000000
--- a/lib/active_support/base64.rb
+++ /dev/null
@@ -1,54 +0,0 @@
-require 'active_support/deprecation'
-
-begin
- require 'base64'
-rescue LoadError
- # The Base64 module isn't available in earlier versions of Ruby 1.9.
- module Base64
- # Encodes a string to its base 64 representation. Each 60 characters of
- # output is separated by a newline character.
- #
- # ActiveSupport::Base64.encode64("Original unencoded string")
- # # => "T3JpZ2luYWwgdW5lbmNvZGVkIHN0cmluZw==\n"
- def self.encode64(data)
- [data].pack("m")
- end
-
- # Decodes a base 64 encoded string to its original representation.
- #
- # ActiveSupport::Base64.decode64("T3JpZ2luYWwgdW5lbmNvZGVkIHN0cmluZw==")
- # # => "Original unencoded string"
- def self.decode64(data)
- data.unpack("m").first
- end
- end
-end
-
-unless Base64.respond_to?(:strict_encode64)
- # Included in Ruby 1.9
- def Base64.strict_encode64(value)
- encode64(value).gsub(/\n/, '')
- end
-end
-
-module ActiveSupport
- module Base64
- def self.encode64(value)
- ActiveSupport::Deprecation.warn "ActiveSupport::Base64.encode64 " \
- "is deprecated. Use Base64.encode64 instead", caller
- ::Base64.encode64(value)
- end
-
- def self.decode64(value)
- ActiveSupport::Deprecation.warn "ActiveSupport::Base64.decode64 " \
- "is deprecated. Use Base64.decode64 instead", caller
- ::Base64.decode64(value)
- end
-
- def self.encode64s(value)
- ActiveSupport::Deprecation.warn "ActiveSupport::Base64.encode64s " \
- "is deprecated. Use Base64.strict_encode64 instead", caller
- ::Base64.strict_encode64(value)
- end
- end
-end
diff --git a/lib/active_support/basic_object.rb b/lib/active_support/basic_object.rb
index 3b5277c..91aac6d 100644
--- a/lib/active_support/basic_object.rb
+++ b/lib/active_support/basic_object.rb
@@ -1,21 +1,11 @@
-module ActiveSupport
- if defined? ::BasicObject
- # A class with no predefined methods that behaves similarly to Builder's
- # BlankSlate. Used for proxy classes.
- class BasicObject < ::BasicObject
- undef_method :==
- undef_method :equal?
+require 'active_support/deprecation'
+require 'active_support/proxy_object'
- # Let ActiveSupport::BasicObject at least raise exceptions.
- def raise(*args)
- ::Object.send(:raise, *args)
- end
- end
- else
- class BasicObject #:nodoc:
- instance_methods.each do |m|
- undef_method(m) if m.to_s !~ /(?:^__|^nil\?$|^send$|^object_id$)/
- end
+module ActiveSupport
+ class BasicObject < ProxyObject # :nodoc:
+ def self.inherited(*)
+ ::ActiveSupport::Deprecation.warn 'ActiveSupport::BasicObject is deprecated! Use ActiveSupport::ProxyObject instead.'
+ super
end
end
end
diff --git a/lib/active_support/benchmarkable.rb b/lib/active_support/benchmarkable.rb
index f149a7f..6413502 100644
--- a/lib/active_support/benchmarkable.rb
+++ b/lib/active_support/benchmarkable.rb
@@ -3,30 +3,33 @@ require 'active_support/core_ext/hash/keys'
module ActiveSupport
module Benchmarkable
- # Allows you to measure the execution time of a block in a template and records the result to
- # the log. Wrap this block around expensive operations or possible bottlenecks to get a time
- # reading for the operation. For example, let's say you thought your file processing method
- # was taking too long; you could wrap it in a benchmark block.
+ # Allows you to measure the execution time of a block in a template and
+ # records the result to the log. Wrap this block around expensive operations
+ # or possible bottlenecks to get a time reading for the operation. For
+ # example, let's say you thought your file processing method was taking too
+ # long; you could wrap it in a benchmark block.
#
- # <% benchmark "Process data files" do %>
+ # <% benchmark 'Process data files' do %>
# <%= expensive_files_operation %>
# <% end %>
#
- # That would add something like "Process data files (345.2ms)" to the log, which you can then
- # use to compare timings when optimizing your code.
+ # That would add something like "Process data files (345.2ms)" to the log,
+ # which you can then use to compare timings when optimizing your code.
#
- # You may give an optional logger level (:debug, :info, :warn, :error) as the :level option.
- # The default logger level value is :info.
+ # You may give an optional logger level (<tt>:debug</tt>, <tt>:info</tt>,
+ # <tt>:warn</tt>, <tt>:error</tt>) as the <tt>:level</tt> option. The
+ # default logger level value is <tt>:info</tt>.
#
- # <% benchmark "Low-level files", :level => :debug do %>
+ # <% benchmark 'Low-level files', level: :debug do %>
# <%= lowlevel_files_operation %>
# <% end %>
#
- # Finally, you can pass true as the third argument to silence all log activity (other than the
- # timing information) from inside the block. This is great for boiling down a noisy block to
- # just a single statement that produces one log line:
+ # Finally, you can pass true as the third argument to silence all log
+ # activity (other than the timing information) from inside the block. This
+ # is great for boiling down a noisy block to just a single statement that
+ # produces one log line:
#
- # <% benchmark "Process data files", :level => :info, :silence => true do %>
+ # <% benchmark 'Process data files', level: :info, silence: true do %>
# <%= expensive_and_chatty_files_operation %>
# <% end %>
def benchmark(message = "Benchmarking", options = {})
@@ -44,8 +47,9 @@ module ActiveSupport
end
# Silence the logger during the execution of the block.
- #
def silence
+ message = "ActiveSupport::Benchmarkable#silence is deprecated. It will be removed from Rails 4.1."
+ ActiveSupport::Deprecation.warn message
old_logger_level, logger.level = logger.level, ::Logger::ERROR if logger
yield
ensure
diff --git a/lib/active_support/buffered_logger.rb b/lib/active_support/buffered_logger.rb
index 85fb41f..1cd0c2f 100644
--- a/lib/active_support/buffered_logger.rb
+++ b/lib/active_support/buffered_logger.rb
@@ -1,125 +1,21 @@
-require 'thread'
-require 'logger'
-require 'active_support/core_ext/logger'
-require 'active_support/core_ext/class/attribute_accessors'
require 'active_support/deprecation'
-require 'fileutils'
+require 'active_support/logger'
module ActiveSupport
- # Inspired by the buffered logger idea by Ezra
- class BufferedLogger
- module Severity
- DEBUG = 0
- INFO = 1
- WARN = 2
- ERROR = 3
- FATAL = 4
- UNKNOWN = 5
- end
- include Severity
-
- MAX_BUFFER_SIZE = 1000
-
- ##
- # :singleton-method:
- # Set to false to disable the silencer
- cattr_accessor :silencer
- self.silencer = true
-
- # Silences the logger for the duration of the block.
- def silence(temporary_level = ERROR)
- if silencer
- begin
- logger = self.class.new @log_dest.dup, temporary_level
- yield logger
- ensure
- logger.close
- end
- else
- yield self
- end
- end
- deprecate :silence
-
- attr_reader :auto_flushing
- deprecate :auto_flushing
-
- def initialize(log, level = DEBUG)
- @log_dest = log
-
- unless log.respond_to?(:write)
- unless File.exist?(File.dirname(log))
- ActiveSupport::Deprecation.warn(<<-eowarn)
-Automatic directory creation for '#{log}' is deprecated. Please make sure the directory for your log file exists before creating the logger.
- eowarn
- FileUtils.mkdir_p(File.dirname(log))
- end
- end
-
- @log = open_logfile log
- self.level = level
- end
-
- def open_log(log, mode)
- open(log, mode).tap do |open_log|
- open_log.set_encoding(Encoding::BINARY) if open_log.respond_to?(:set_encoding)
- open_log.sync = true
- end
- end
- deprecate :open_log
-
- def level
- @log.level
- end
-
- def level=(l)
- @log.level = l
- end
+ class BufferedLogger < Logger
- def add(severity, message = nil, progname = nil, &block)
- @log.add(severity, message, progname, &block)
- end
-
- # Dynamically add methods such as:
- # def info
- # def warn
- # def debug
- Severity.constants.each do |severity|
- class_eval <<-EOT, __FILE__, __LINE__ + 1
- def #{severity.downcase}(message = nil, progname = nil, &block) # def debug(message = nil, progname = nil, &block)
- add(#{severity}, message, progname, &block) # add(DEBUG, message, progname, &block)
- end # end
-
- def #{severity.downcase}? # def debug?
- #{severity} >= level # DEBUG >= level
- end # end
- EOT
- end
-
- # Set the auto-flush period. Set to true to flush after every log message,
- # to an integer to flush every N messages, or to false, nil, or zero to
- # never auto-flush. If you turn auto-flushing off, be sure to regularly
- # flush the log yourself -- it will eat up memory until you do.
- def auto_flushing=(period)
- end
- deprecate :auto_flushing=
-
- def flush
- end
- deprecate :flush
-
- def respond_to?(method, include_private = false)
- return false if method.to_s == "flush"
+ def initialize(*args)
+ self.class._deprecation_warning
super
end
- def close
- @log.close
+ def self.inherited(*)
+ _deprecation_warning
+ super
end
- private
- def open_logfile(log)
- Logger.new log
+ def self._deprecation_warning
+ ::ActiveSupport::Deprecation.warn 'ActiveSupport::BufferedLogger is deprecated! Use ActiveSupport::Logger instead.'
end
end
end
diff --git a/lib/active_support/cache.rb b/lib/active_support/cache.rb
index 42b11b1..fc470f3 100644
--- a/lib/active_support/cache.rb
+++ b/lib/active_support/cache.rb
@@ -3,7 +3,6 @@ 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/exception'
require 'active_support/core_ext/class/attribute_accessors'
require 'active_support/core_ext/numeric/bytes'
require 'active_support/core_ext/numeric/time'
@@ -45,8 +44,8 @@ module ActiveSupport
# Any additional arguments will be passed to the corresponding cache store
# class's constructor:
#
- # ActiveSupport::Cache.lookup_store(:file_store, "/tmp/cache")
- # # => same as: ActiveSupport::Cache::FileStore.new("/tmp/cache")
+ # ActiveSupport::Cache.lookup_store(:file_store, '/tmp/cache')
+ # # => same as: ActiveSupport::Cache::FileStore.new('/tmp/cache')
#
# If the first argument is not a Symbol, then it will simply be returned:
#
@@ -57,16 +56,7 @@ module ActiveSupport
case store
when Symbol
- store_class_name = store.to_s.camelize
- store_class =
- begin
- require "active_support/cache/#{store}"
- rescue LoadError => e
- raise "Could not find cache store adapter for #{store} (#{e})"
- else
- ActiveSupport::Cache.const_get(store_class_name)
- end
- store_class.new(*parameters)
+ retrieve_store_class(store).new(*parameters)
when nil
ActiveSupport::Cache::MemoryStore.new
else
@@ -74,6 +64,18 @@ module ActiveSupport
end
end
+ # Expands out the +key+ argument into a key that can be used for the
+ # cache store. Optionally accepts a namespace, and all keys will be
+ # scoped within that namespace.
+ #
+ # If the +key+ argument provided is an array, or responds to +to_a+, then
+ # each of elements in the array will be turned into parameters/keys and
+ # concatenated into a single key. For example:
+ #
+ # expand_cache_key([:foo, :bar]) # => "foo/bar"
+ # expand_cache_key([:foo, :bar], "namespace") # => "namespace/foo/bar"
+ #
+ # The +key+ argument can also respond to +cache_key+ or +to_param+.
def expand_cache_key(key, namespace = nil)
expanded_cache_key = namespace ? "#{namespace}/" : ""
@@ -91,9 +93,20 @@ module ActiveSupport
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
end
# An abstract cache store class. There are multiple cache store
@@ -109,9 +122,9 @@ module ActiveSupport
#
# cache = ActiveSupport::Cache::MemoryStore.new
#
- # cache.read("city") # => nil
- # cache.write("city", "Duckburgh")
- # cache.read("city") # => "Duckburgh"
+ # cache.read('city') # => nil
+ # cache.write('city', "Duckburgh")
+ # cache.read('city') # => "Duckburgh"
#
# Keys are always translated into Strings and are case sensitive. When an
# object is specified as a key and has a +cache_key+ method defined, this
@@ -120,7 +133,7 @@ module ActiveSupport
# elements will be delimited by slashes, and the elements within a Hash
# will be sorted by key so they are consistent.
#
- # cache.read("city") == cache.read(:city) # => true
+ # cache.read('city') == cache.read(:city) # => true
#
# Nil values can be cached.
#
@@ -130,14 +143,13 @@ module ActiveSupport
# is a Proc, it will be invoked when each key is evaluated so that you can
# use application logic to invalidate keys.
#
- # cache.namespace = lambda { @last_mod_time } # Set the namespace to a variable
+ # cache.namespace = -> { @last_mod_time } # Set the namespace to a variable
# @last_mod_time = Time.now # Invalidate the entire cache by changing namespace
#
- #
# Caches can also store values in a compressed format to save space and
# reduce time spent sending data. Since there is overhead, values must be
# large enough to warrant compression. To turn on compression either pass
- # <tt>:compress => true</tt> in the initializer or as an option to +fetch+
+ # <tt>compress: true</tt> in the initializer or as an option to +fetch+
# or +write+. To specify the threshold at which to compress values, set the
# <tt>:compress_threshold</tt> option. The default threshold is 16K.
class Store
@@ -147,8 +159,9 @@ module ActiveSupport
attr_reader :silence, :options
alias :silence? :silence
- # Create a new cache. The options will be passed to any write method calls except
- # for :namespace which can be used to set the global namespace for the cache.
+ # Create a new cache. The options will be passed to any write method calls
+ # except for <tt>:namespace</tt> which can be used to set the global
+ # namespace for the cache.
def initialize(options = nil)
@options = options ? options.dup : {}
end
@@ -167,7 +180,8 @@ module ActiveSupport
@silence = previous_silence
end
- # Set to true if cache stores should be instrumented. Default is false.
+ # Set to +true+ if cache stores should be instrumented.
+ # Default is +false+.
def self.instrument=(boolean)
Thread.current[:instrument_cache_store] = boolean
end
@@ -179,125 +193,109 @@ module ActiveSupport
# Fetches data from the cache, using the given key. If there is data in
# the cache with the given key, then that data is returned.
#
- # If there is no such data in the cache (a cache miss), then nil will be
- # returned. However, if a block has been passed, that block will be run
- # in the event of a cache miss. The return value of the block will be
- # written to the cache under the given cache key, and that return value
- # will be returned.
+ # If there is no such data in the cache (a cache miss), then +nil+ will be
+ # returned. However, if a block has been passed, that block will be passed
+ # the key and executed in the event of a cache miss. The return value of the
+ # block will be written to the cache under the given cache key, and that
+ # return value will be returned.
#
- # cache.write("today", "Monday")
- # cache.fetch("today") # => "Monday"
+ # cache.write('today', 'Monday')
+ # cache.fetch('today') # => "Monday"
#
- # cache.fetch("city") # => nil
- # cache.fetch("city") do
- # "Duckburgh"
+ # cache.fetch('city') # => nil
+ # cache.fetch('city') do
+ # 'Duckburgh'
# end
- # cache.fetch("city") # => "Duckburgh"
+ # cache.fetch('city') # => "Duckburgh"
#
# You may also specify additional options via the +options+ argument.
- # Setting <tt>:force => true</tt> will force a cache miss:
+ # Setting <tt>force: true</tt> will force a cache miss:
#
- # cache.write("today", "Monday")
- # cache.fetch("today", :force => true) # => nil
+ # cache.write('today', 'Monday')
+ # cache.fetch('today', force: true) # => nil
#
# Setting <tt>:compress</tt> will store a large cache entry set by the call
# in a compressed format.
#
- #
# Setting <tt>:expires_in</tt> will set an expiration time on the cache.
# All caches support auto-expiring content after a specified number of
# seconds. This value can be specified as an option to the constructor
# (in which case all entries will be affected), or it can be supplied to
# the +fetch+ or +write+ method to effect just one entry.
#
- # cache = ActiveSupport::Cache::MemoryStore.new(:expires_in => 5.minutes)
- # cache.write(key, value, :expires_in => 1.minute) # Set a lower value for one entry
- #
- # Setting <tt>:race_condition_ttl</tt> is very useful in situations where a cache entry
- # is used very frequently and is under heavy load. If a cache expires and due to heavy load
- # seven different processes will try to read data natively and then they all will try to
- # write to cache. To avoid that case the first process to find an expired cache entry will
- # 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 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.
- #
- # If the process regenerating the entry errors out, the entry will be regenerated
- # after the specified number of seconds. Also note that the life of stale cache is
- # extended only if it expired recently. Otherwise a new value is generated and
- # <tt>:race_condition_ttl</tt> does not play any role.
+ # cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 5.minutes)
+ # cache.write(key, value, expires_in: 1.minute) # Set a lower value for one entry
+ #
+ # Setting <tt>:race_condition_ttl</tt> is very useful in situations where
+ # a cache entry is used very frequently and is under heavy load. If a
+ # cache expires and due to heavy load seven different processes will try
+ # to read data natively and then they all will try to write to cache. To
+ # avoid that case the first process to find an expired cache entry will
+ # 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 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.
+ #
+ # If the process regenerating the entry errors out, the entry will be
+ # regenerated after the specified number of seconds. Also note that the
+ # life of stale cache is extended only if it expired recently. Otherwise
+ # a new value is generated and <tt>:race_condition_ttl</tt> does not play
+ # any role.
#
# # Set all values to expire after one minute.
- # cache = ActiveSupport::Cache::MemoryStore.new(:expires_in => 1.minute)
+ # cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 1.minute)
#
- # cache.write("foo", "original value")
+ # cache.write('foo', 'original value')
# val_1 = nil
# val_2 = nil
# sleep 60
#
# Thread.new do
- # val_1 = cache.fetch("foo", :race_condition_ttl => 10) do
+ # val_1 = cache.fetch('foo', race_condition_ttl: 10) do
# sleep 1
- # "new value 1"
+ # 'new value 1'
# end
# end
#
# Thread.new do
- # val_2 = cache.fetch("foo", :race_condition_ttl => 10) do
- # "new value 2"
+ # val_2 = cache.fetch('foo', race_condition_ttl: 10) do
+ # 'new value 2'
# end
# end
#
# # val_1 => "new value 1"
# # val_2 => "original value"
# # sleep 10 # First thread extend the life of cache by another 10 seconds
- # # cache.fetch("foo") => "new value 1"
+ # # cache.fetch('foo') => "new value 1"
#
# Other options will be handled by the specific cache store implementation.
- # Internally, #fetch calls #read_entry, and calls #write_entry on a cache miss.
- # +options+ will be passed to the #read and #write calls.
+ # Internally, #fetch calls #read_entry, and calls #write_entry on a cache
+ # miss. +options+ will be passed to the #read and #write calls.
#
# For example, MemCacheStore's #write method supports the +:raw+
# option, which tells the memcached server to store all values as strings.
# We can use this option with #fetch too:
#
# cache = ActiveSupport::Cache::MemCacheStore.new
- # cache.fetch("foo", :force => true, :raw => true) do
+ # cache.fetch("foo", force: true, raw: true) do
# :bar
# end
- # cache.fetch("foo") # => "bar"
+ # cache.fetch('foo') # => "bar"
def fetch(name, options = nil)
if block_given?
options = merged_options(options)
key = namespaced_key(name, options)
- unless options[:force]
- entry = instrument(:read, name, options) do |payload|
- payload[:super_operation] = :fetch if payload
- read_entry(key, options)
- end
- end
- if entry && entry.expired?
- race_ttl = options[:race_condition_ttl].to_f
- if race_ttl and Time.now.to_f - entry.expires_at <= race_ttl
- entry.expires_at = Time.now + race_ttl
- write_entry(key, entry, :expires_in => race_ttl * 2)
- else
- delete_entry(key, options)
- end
- entry = nil
- end
+
+ cached_entry = find_cached_entry(key, name, options) unless options[:force]
+ entry = handle_expired_entry(cached_entry, key, options)
if entry
- instrument(:fetch_hit, name, options) { |payload| }
- entry.value
+ get_entry_value(entry, name, options)
else
- result = instrument(:generate, name, options) do |payload|
- yield
- end
- write(name, result, options)
- result
+ save_block_result_to_cache(name, options) { |_name| yield _name }
end
else
read(name, options)
@@ -306,7 +304,7 @@ module ActiveSupport
# Fetches data from the cache, using the given key. If there is data in
# the cache with the given key, then that data is returned. Otherwise,
- # nil is returned.
+ # +nil+ is returned.
#
# Options are passed to the underlying cache implementation.
def read(name, options = nil)
@@ -359,7 +357,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 |payload|
+ instrument(:write, name, options) do
entry = Entry.new(value, options)
write_entry(namespaced_key(name, options), entry, options)
end
@@ -370,23 +368,19 @@ module ActiveSupport
# Options are passed to the underlying cache implementation.
def delete(name, options = nil)
options = merged_options(options)
- instrument(:delete, name) do |payload|
+ 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.
+ # Return +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 |payload|
+ instrument(:exist?, name) do
entry = read_entry(namespaced_key(name, options), options)
- if entry && !entry.expired?
- true
- else
- false
- end
+ entry && !entry.expired?
end
end
@@ -408,7 +402,7 @@ module ActiveSupport
raise NotImplementedError.new("#{self.class.name} does not support increment")
end
- # Increment an integer value in the cache.
+ # Decrement an integer value in the cache.
#
# Options are passed to the underlying cache implementation.
#
@@ -437,9 +431,10 @@ module ActiveSupport
end
protected
- # Add the namespace defined in the options to a pattern designed to match keys.
- # Implementations that support delete_matched should call this method to translate
- # a pattern that matches names into one that matches namespaced keys.
+ # Add the namespace defined in the options to a pattern designed to
+ # match keys. Implementations that support delete_matched should call
+ # this method to translate a pattern that matches names into one that
+ # matches namespaced keys.
def key_matcher(pattern, options)
prefix = options[:namespace].is_a?(Proc) ? options[:namespace].call : options[:namespace]
if prefix
@@ -455,17 +450,20 @@ module ActiveSupport
end
end
- # Read an entry from the cache implementation. Subclasses must implement this method.
+ # Read an entry from the cache implementation. Subclasses must implement
+ # this method.
def read_entry(key, options) # :nodoc:
raise NotImplementedError.new
end
- # Write an entry to the cache implementation. Subclasses must implement this method.
+ # Write an entry to the cache implementation. Subclasses must implement
+ # this method.
def write_entry(key, entry, options) # :nodoc:
raise NotImplementedError.new
end
- # Delete an entry from the cache implementation. Subclasses must implement this method.
+ # Delete an entry from the cache implementation. Subclasses must
+ # implement this method.
def delete_entry(key, options) # :nodoc:
raise NotImplementedError.new
end
@@ -481,7 +479,7 @@ module ActiveSupport
end
# Expand key to be a consistent string value. Invoke +cache_key+ if
- # object responds to +cache_key+. Otherwise, to_param method will be
+ # object responds to +cache_key+. Otherwise, +to_param+ method will be
# called. If the key is a Hash, then keys will be sorted alphabetically.
def expanded_key(key) # :nodoc:
return key.cache_key.to_s if key.respond_to?(:cache_key)
@@ -500,7 +498,8 @@ module ActiveSupport
key.to_param
end
- # Prefix a key with the namespace. Namespace and key will be delimited with a colon.
+ # Prefix a key with the namespace. Namespace and key will be delimited
+ # with a colon.
def namespaced_key(key, options)
key = expanded_key(key)
namespace = options[:namespace] if options
@@ -525,114 +524,160 @@ module ActiveSupport
return unless logger && logger.debug? && !silence?
logger.debug("Cache #{operation}: #{key}#{options.blank? ? "" : " (#{options.inspect})"}")
end
- end
- # Entry that is put into caches. It supports expiration time on entries and can compress values
- # to save space in the cache.
- class Entry
- attr_reader :created_at, :expires_in
-
- DEFAULT_COMPRESS_LIMIT = 16.kilobytes
+ def find_cached_entry(key, name, options)
+ instrument(:read, name, options) do |payload|
+ payload[:super_operation] = :fetch if payload
+ read_entry(key, options)
+ end
+ end
- class << self
- # Create an entry with internal attributes set. This method is intended to be
- # used by implementations that store cache entries in a native format instead
- # of as serialized Ruby objects.
- def create(raw_value, created_at, options = {})
- entry = new(nil)
- entry.instance_variable_set(:@value, raw_value)
- entry.instance_variable_set(:@created_at, created_at.to_f)
- entry.instance_variable_set(:@compressed, options[:compressed])
- entry.instance_variable_set(:@expires_in, options[:expires_in])
+ def handle_expired_entry(entry, key, options)
+ if entry && entry.expired?
+ race_ttl = options[:race_condition_ttl].to_i
+ if race_ttl && (Time.now.to_f - entry.expires_at <= race_ttl)
+ # When an entry has :race_condition_ttl defined, put the stale entry back into the cache
+ # for a brief period while the entry is begin recalculated.
+ entry.expires_at = Time.now + race_ttl
+ write_entry(key, entry, :expires_in => race_ttl * 2)
+ else
+ delete_entry(key, options)
+ end
+ entry = nil
+ end
entry
end
- end
+
+ def get_entry_value(entry, name, options)
+ instrument(:fetch_hit, name, options) { |payload| }
+ entry.value
+ end
+
+ def save_block_result_to_cache(name, options)
+ result = instrument(:generate, name, options) do |payload|
+ yield(name)
+ end
+ write(name, result, options)
+ result
+ end
+ end
+
+ # This class is used to represent cache entries. Cache entries have a value and an optional
+ # expiration time. The expiration time is used to support the :race_condition_ttl option
+ # on the cache.
+ #
+ # Since cache entries in most instances will be serialized, the internals of this class are highly optimized
+ # using short instance variable names that are lazily defined.
+ class Entry # :nodoc:
+ DEFAULT_COMPRESS_LIMIT = 16.kilobytes
# Create a new cache entry for the specified value. Options supported are
# +:compress+, +:compress_threshold+, and +:expires_in+.
def initialize(value, options = {})
- @compressed = false
- @expires_in = options[:expires_in]
- @expires_in = @expires_in.to_f if @expires_in
- @created_at = Time.now.to_f
- if value.nil?
- @value = nil
+ if should_compress?(value, options)
+ @value = compress(value)
+ @compressed = true
else
- @value = Marshal.dump(value)
- if should_compress?(@value, options)
- @value = Zlib::Deflate.deflate(@value)
- @compressed = true
- end
+ @value = value
end
+ @created_at = Time.now.to_f
+ @expires_in = options[:expires_in]
+ @expires_in = @expires_in.to_f if @expires_in
end
- # Get the raw value. This value may be serialized and compressed.
- def raw_value
- @value
- end
-
- # Get the value stored in the cache.
def value
- # If the original value was exactly false @value is still true because
- # it is marshalled and eventually compressed. Both operations yield
- # strings.
- if @value
- # In rails 3.1 and earlier values in entries did not marshaled without
- # options[:compress] and if it's Numeric.
- # But after commit a263f377978fc07515b42808ebc1f7894fafaa3a
- # all values in entries are marshalled. And after that code below expects
- # that all values in entries will be marshaled (and will be strings).
- # So here we need a check for old ones.
- begin
- Marshal.load(compressed? ? Zlib::Inflate.inflate(@value) : @value)
- rescue TypeError
- compressed? ? Zlib::Inflate.inflate(@value) : @value
- end
- end
- end
-
- def compressed?
- @compressed
+ convert_version_4beta1_entry! if defined?(@v)
+ compressed? ? uncompress(@value) : @value
end
- # Check if the entry is expired. The +expires_in+ parameter can override the
- # value set when the entry was created.
+ # Check if the entry is expired. The +expires_in+ parameter can override
+ # the value set when the entry was created.
def expired?
+ convert_version_4beta1_entry! if defined?(@value)
@expires_in && @created_at + @expires_in <= Time.now.to_f
end
- # Set a new time when the entry will expire.
- def expires_at=(time)
- if time
- @expires_in = time.to_f - @created_at
+ def expires_at
+ @expires_in ? @created_at + @expires_in : nil
+ end
+
+ def expires_at=(value)
+ if value
+ @expires_in = value.to_f - @created_at
else
@expires_in = nil
end
end
- # Seconds since the epoch when the entry will expire.
- def expires_at
- @expires_in ? @created_at + @expires_in : nil
- end
-
- # Returns the size of the cached value. This could be less than value.size
- # if the data is compressed.
+ # Returns the size of the cached value. This could be less than
+ # <tt>value.size</tt> if the data is compressed.
def size
- if @value.nil?
- 0
+ if defined?(@s)
+ @s
else
- @value.bytesize
+ case value
+ when NilClass
+ 0
+ when String
+ @value.bytesize
+ else
+ @s = Marshal.dump(@value).bytesize
+ end
+ end
+ end
+
+ # Duplicate the value in a class. This is used by cache implementations that don't natively
+ # serialize entries to protect against accidental cache modifications.
+ def dup_value!
+ convert_version_4beta1_entry! if defined?(@v)
+ if @value && !compressed? && !(@value.is_a?(Numeric) || @value == true || @value == false)
+ if @value.is_a?(String)
+ @value = @value.dup
+ else
+ @value = Marshal.load(Marshal.dump(@value))
+ end
end
end
private
- def should_compress?(serialized_value, options)
- if options[:compress]
+ def should_compress?(value, options)
+ if value && options[:compress]
compress_threshold = options[:compress_threshold] || DEFAULT_COMPRESS_LIMIT
- return true if serialized_value.size >= compress_threshold
+ serialized_value_size = (value.is_a?(String) ? value : Marshal.dump(value)).bytesize
+ return true if serialized_value_size >= compress_threshold
end
false
end
+
+ def compressed?
+ defined?(@compressed) ? @compressed : false
+ end
+
+ def compress(value)
+ Zlib::Deflate.deflate(Marshal.dump(value))
+ end
+
+ def uncompress(value)
+ Marshal.load(Zlib::Inflate.inflate(value))
+ end
+
+ # The internals of this method changed between Rails 3.x and 4.0. This method provides the glue
+ # to ensure that cache entries created under the old version still work with the new class definition.
+ def convert_version_4beta1_entry!
+ if defined?(@v)
+ @value = @v
+ remove_instance_variable(:@v)
+ end
+ if defined?(@c)
+ @compressed = @c
+ remove_instance_variable(:@c)
+ end
+ if defined?(@x) && @x
+ @created_at ||= Time.now.to_f
+ @expires_in = @x - @created_at
+ remove_instance_variable(:@x)
+ end
+ end
end
end
end
diff --git a/lib/active_support/cache/file_store.rb b/lib/active_support/cache/file_store.rb
index 9460532..2670648 100644
--- a/lib/active_support/cache/file_store.rb
+++ b/lib/active_support/cache/file_store.rb
@@ -1,7 +1,7 @@
+require 'active_support/core_ext/marshal'
require 'active_support/core_ext/file/atomic'
require 'active_support/core_ext/string/conversions'
-require 'active_support/core_ext/object/inclusion'
-require 'rack/utils'
+require 'uri/common'
module ActiveSupport
module Cache
@@ -13,7 +13,7 @@ module ActiveSupport
attr_reader :cache_path
DIR_FORMATTER = "%03X"
- FILENAME_MAX_SIZE = 230 # max filename size on file system is 255, minus room for timestamp and random characters appended by Tempfile (used by atomic write)
+ FILENAME_MAX_SIZE = 228 # max filename size on file system is 255, minus room for timestamp and random characters appended by Tempfile (used by atomic write)
EXCLUDED_DIRS = ['.', '..'].freeze
def initialize(cache_path, options = nil)
@@ -23,13 +23,14 @@ module ActiveSupport
end
def clear(options = nil)
- root_dirs = Dir.entries(cache_path).reject{|f| f.in?(EXCLUDED_DIRS)}
+ 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
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
@@ -81,7 +82,8 @@ module ActiveSupport
if File.exist?(file_name)
File.open(file_name) { |f| Marshal.load(f) }
end
- rescue
+ rescue => e
+ logger.error("FileStoreError (#{e}): #{e.message}") if logger
nil
end
@@ -126,7 +128,7 @@ module ActiveSupport
# Translate a key into a file path.
def key_file_path(key)
- fname = Rack::Utils.escape(key)
+ fname = URI.encode_www_form_component(key)
hash = Zlib.adler32(fname)
hash, dir_1 = hash.divmod(0x1000)
dir_2 = hash.modulo(0x1000)
@@ -143,15 +145,15 @@ module ActiveSupport
# Translate a file path into a key.
def file_path_key(path)
- fname = path[cache_path.size, path.size].split(File::SEPARATOR, 4).last
- Rack::Utils.unescape(fname)
+ fname = path[cache_path.to_s.size..-1].split(File::SEPARATOR, 4).last
+ URI.decode_www_form_component(fname, Encoding::UTF_8)
end
# Delete empty directories in the cache.
def delete_empty_directories(dir)
- return if dir == cache_path
- if Dir.entries(dir).reject{|f| f.in?(EXCLUDED_DIRS)}.empty?
- File.delete(dir) rescue nil
+ return if File.realpath(dir) == File.realpath(cache_path)
+ if Dir.entries(dir).reject {|f| EXCLUDED_DIRS.include?(f)}.empty?
+ Dir.delete(dir) rescue nil
delete_empty_directories(File.dirname(dir))
end
end
@@ -164,7 +166,7 @@ module ActiveSupport
def search_dir(dir, &callback)
return if !File.exist?(dir)
Dir.foreach(dir) do |d|
- next if d.in?(EXCLUDED_DIRS)
+ next if EXCLUDED_DIRS.include?(d)
name = File.join(dir, d)
if File.directory?(name)
search_dir(name, &callback)
diff --git a/lib/active_support/cache/mem_cache_store.rb b/lib/active_support/cache/mem_cache_store.rb
index 530839b..5122965 100644
--- a/lib/active_support/cache/mem_cache_store.rb
+++ b/lib/active_support/cache/mem_cache_store.rb
@@ -1,12 +1,13 @@
begin
- require 'memcache'
+ require 'dalli'
rescue LoadError => e
- $stderr.puts "You don't have memcache-client installed in your application. Please add it to your Gemfile and run bundle install"
+ $stderr.puts "You don't have dalli installed in your application. Please add it to your Gemfile and run bundle install"
raise e
end
require 'digest/md5'
-require 'active_support/core_ext/string/encoding'
+require 'active_support/core_ext/marshal'
+require 'active_support/core_ext/array/extract_options'
module ActiveSupport
module Cache
@@ -23,21 +24,13 @@ module ActiveSupport
# MemCacheStore implements the Strategy::LocalCache strategy which implements
# an in-memory cache inside of a block.
class MemCacheStore < Store
- module Response # :nodoc:
- STORED = "STORED\r\n"
- NOT_STORED = "NOT_STORED\r\n"
- EXISTS = "EXISTS\r\n"
- NOT_FOUND = "NOT_FOUND\r\n"
- DELETED = "DELETED\r\n"
- end
-
ESCAPE_KEY_CHARS = /[\x00-\x20%\x7F-\xFF]/n
def self.build_mem_cache(*addresses)
addresses = addresses.flatten
options = addresses.extract_options!
addresses = ["localhost:11211"] if addresses.empty?
- MemCache.new(addresses, options)
+ Dalli::Client.new(addresses, options)
end
# Creates a new MemCacheStore object, with the given memcached server
@@ -91,11 +84,11 @@ module ActiveSupport
# to zero.
def increment(name, amount = 1, options = nil) # :nodoc:
options = merged_options(options)
- response = instrument(:increment, name, :amount => amount) do
+ instrument(:increment, name, :amount => amount) do
@data.incr(escape_key(namespaced_key(name, options)), amount)
end
- response == Response::NOT_FOUND ? nil : response.to_i
- rescue MemCache::MemCacheError
+ rescue Dalli::DalliError
+ logger.error("DalliError (#{e}): #{e.message}") if logger
nil
end
@@ -105,11 +98,11 @@ module ActiveSupport
# to zero.
def decrement(name, amount = 1, options = nil) # :nodoc:
options = merged_options(options)
- response = instrument(:decrement, name, :amount => amount) do
+ instrument(:decrement, name, :amount => amount) do
@data.decr(escape_key(namespaced_key(name, options)), amount)
end
- response == Response::NOT_FOUND ? nil : response.to_i
- rescue MemCache::MemCacheError
+ rescue Dalli::DalliError
+ logger.error("DalliError (#{e}): #{e.message}") if logger
nil
end
@@ -117,6 +110,9 @@ module ActiveSupport
# be used with care when shared cache is being used.
def clear(options = nil)
@data.flush_all
+ rescue Dalli::DalliError => e
+ logger.error("DalliError (#{e}): #{e.message}") if logger
+ nil
end
# Get the statistics from the memcached servers.
@@ -127,9 +123,9 @@ module ActiveSupport
protected
# Read an entry from the cache.
def read_entry(key, options) # :nodoc:
- deserialize_entry(@data.get(escape_key(key), true))
- rescue MemCache::MemCacheError => e
- logger.error("MemCacheError (#{e}): #{e.message}") if logger
+ deserialize_entry(@data.get(escape_key(key), options))
+ rescue Dalli::DalliError => e
+ logger.error("DalliError (#{e}): #{e.message}") if logger
nil
end
@@ -142,19 +138,17 @@ module ActiveSupport
# Set the memcache expire a few minutes in the future to support race condition ttls on read
expires_in += 5.minutes
end
- response = @data.send(method, escape_key(key), value, expires_in, options[:raw])
- response == Response::STORED
- rescue MemCache::MemCacheError => e
- logger.error("MemCacheError (#{e}): #{e.message}") if logger
+ @data.send(method, escape_key(key), value, expires_in, options)
+ rescue Dalli::DalliError => e
+ logger.error("DalliError (#{e}): #{e.message}") if logger
false
end
# Delete an entry from the cache.
def delete_entry(key, options) # :nodoc:
- response = @data.delete(escape_key(key))
- response == Response::DELETED
- rescue MemCache::MemCacheError => e
- logger.error("MemCacheError (#{e}): #{e.message}") if logger
+ @data.delete(escape_key(key))
+ rescue Dalli::DalliError => e
+ logger.error("DalliError (#{e}): #{e.message}") if logger
false
end
@@ -165,7 +159,7 @@ module ActiveSupport
# characters properly.
def escape_key(key)
key = key.to_s.dup
- key = key.force_encoding("BINARY") if key.encoding_aware?
+ key = key.force_encoding(Encoding::ASCII_8BIT)
key = key.gsub(ESCAPE_KEY_CHARS){ |match| "%#{match.getbyte(0).to_s(16).upcase}" }
key = "#{key[0, 213]}:md5:#{Digest::MD5.hexdigest(key)}" if key.size > 250
key
diff --git a/lib/active_support/cache/memory_store.rb b/lib/active_support/cache/memory_store.rb
index b15bb42..d319819 100644
--- a/lib/active_support/cache/memory_store.rb
+++ b/lib/active_support/cache/memory_store.rb
@@ -122,6 +122,13 @@ module ActiveSupport
end
protected
+
+ PER_ENTRY_OVERHEAD = 240
+
+ def cached_size(key, entry)
+ key.to_s.bytesize + entry.size + PER_ENTRY_OVERHEAD
+ end
+
def read_entry(key, options) # :nodoc:
entry = @data[key]
synchronize do
@@ -135,10 +142,15 @@ module ActiveSupport
end
def write_entry(key, entry, options) # :nodoc:
+ entry.dup_value!
synchronize do
old_entry = @data[key]
- @cache_size -= old_entry.size if old_entry
- @cache_size += entry.size
+ return false if @data.key?(key) && options[:unless_exist]
+ if old_entry
+ @cache_size -= (old_entry.size - entry.size)
+ else
+ @cache_size += cached_size(key, entry)
+ end
@key_access[key] = Time.now.to_f
@data[key] = entry
prune(@max_size * 0.75, @max_prune_time) if @cache_size > @max_size
@@ -150,7 +162,7 @@ module ActiveSupport
synchronize do
@key_access.delete(key)
entry = @data.delete(key)
- @cache_size -= entry.size if entry
+ @cache_size -= cached_size(key, entry) if entry
!!entry
end
end
diff --git a/lib/active_support/cache/strategy/local_cache.rb b/lib/active_support/cache/strategy/local_cache.rb
index db5f228..fb42c4a 100644
--- a/lib/active_support/cache/strategy/local_cache.rb
+++ b/lib/active_support/cache/strategy/local_cache.rb
@@ -8,6 +8,23 @@ module ActiveSupport
# duration of a block. Repeated calls to the cache for the same key will hit the
# in-memory cache for faster access.
module LocalCache
+ # Class for storing and registering the local caches.
+ class LocalCacheRegistry # :nodoc:
+ extend ActiveSupport::PerThreadRegistry
+
+ def initialize
+ @registry = {}
+ end
+
+ def cache_for(local_cache_key)
+ @registry[local_cache_key]
+ end
+
+ def set_cache_for(local_cache_key, value)
+ @registry[local_cache_key] = value
+ end
+ end
+
# Simple memory backed cache. This cache is not thread safe and is intended only
# for serving as a temporary memory cache for a single thread.
class LocalStore < Store
@@ -41,24 +58,18 @@ module ActiveSupport
# Use a local cache for the duration of block.
def with_local_cache
- save_val = Thread.current[thread_local_key]
- begin
- Thread.current[thread_local_key] = LocalStore.new
- yield
- ensure
- Thread.current[thread_local_key] = save_val
- end
+ use_temporary_local_cache(LocalStore.new) { yield }
end
#--
# This class wraps up local storage for middlewares. Only the middleware method should
# construct them.
class Middleware # :nodoc:
- attr_reader :name, :thread_local_key
+ attr_reader :name, :local_cache_key
- def initialize(name, thread_local_key)
+ def initialize(name, local_cache_key)
@name = name
- @thread_local_key = thread_local_key
+ @local_cache_key = local_cache_key
@app = nil
end
@@ -68,10 +79,10 @@ module ActiveSupport
end
def call(env)
- Thread.current[thread_local_key] = LocalStore.new
+ LocalCacheRegistry.set_cache_for(local_cache_key, LocalStore.new)
@app.call(env)
ensure
- Thread.current[thread_local_key] = nil
+ LocalCacheRegistry.set_cache_for(local_cache_key, nil)
end
end
@@ -80,7 +91,7 @@ module ActiveSupport
def middleware
@middleware ||= Middleware.new(
"ActiveSupport::Cache::Strategy::LocalCache",
- thread_local_key)
+ local_cache_key)
end
def clear(options = nil) # :nodoc:
@@ -95,29 +106,13 @@ module ActiveSupport
def increment(name, amount = 1, options = nil) # :nodoc:
value = bypass_local_cache{super}
- if local_cache
- local_cache.mute do
- if value
- local_cache.write(name, value, options)
- else
- local_cache.delete(name, options)
- end
- end
- end
+ increment_or_decrement(value, name, amount, options)
value
end
def decrement(name, amount = 1, options = nil) # :nodoc:
value = bypass_local_cache{super}
- if local_cache
- local_cache.mute do
- if value
- local_cache.write(name, value, options)
- else
- local_cache.delete(name, options)
- end
- end
- end
+ increment_or_decrement(value, name, amount, options)
value
end
@@ -146,21 +141,37 @@ module ActiveSupport
end
private
- def thread_local_key
- @thread_local_key ||= "#{self.class.name.underscore}_local_cache_#{object_id}".gsub(/[\/-]/, '_').to_sym
+ def increment_or_decrement(value, name, amount, options)
+ if local_cache
+ local_cache.mute do
+ if value
+ local_cache.write(name, value, options)
+ else
+ local_cache.delete(name, options)
+ end
+ end
+ end
+ end
+
+ def local_cache_key
+ @local_cache_key ||= "#{self.class.name.underscore}_local_cache_#{object_id}".gsub(/[\/-]/, '_').to_sym
end
def local_cache
- Thread.current[thread_local_key]
+ LocalCacheRegistry.cache_for(local_cache_key)
end
def bypass_local_cache
- save_cache = Thread.current[thread_local_key]
+ use_temporary_local_cache(nil) { yield }
+ end
+
+ def use_temporary_local_cache(temporary_cache)
+ save_cache = LocalCacheRegistry.cache_for(local_cache_key)
begin
- Thread.current[thread_local_key] = nil
+ LocalCacheRegistry.set_cache_for(local_cache_key, temporary_cache)
yield
ensure
- Thread.current[thread_local_key] = save_cache
+ LocalCacheRegistry.set_cache_for(local_cache_key, save_cache)
end
end
end
diff --git a/lib/active_support/callbacks.rb b/lib/active_support/callbacks.rb
index a3c956f..22adcd6 100644
--- a/lib/active_support/callbacks.rb
+++ b/lib/active_support/callbacks.rb
@@ -1,30 +1,29 @@
+require 'thread_safe'
require 'active_support/concern'
require 'active_support/descendants_tracker'
-require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/kernel/reporting'
require 'active_support/core_ext/kernel/singleton_class'
-require 'active_support/core_ext/object/inclusion'
module ActiveSupport
- # \Callbacks are code hooks that are run at key points in an object's lifecycle.
- # 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.
+ # Callbacks are code hooks that are run at key points in an object's lifecycle.
+ # 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+), 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+).
+ # Mixing in this module allows you to define the events in the object's
+ # lifecycle 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+).
#
- # Three kinds of callbacks are supported: before callbacks, run before a certain event;
- # after callbacks, run after the event; and around callbacks, blocks that surround the
- # event, triggering it when they yield. Callback code can be contained in instance
- # methods, procs or lambdas, or callback objects that respond to certain predetermined
- # methods. See +ClassMethods.set_callback+ for details.
- #
- # ==== Example
+ # Three kinds of callbacks are supported: before callbacks, run before a
+ # certain event; after callbacks, run after the event; and around callbacks,
+ # blocks that surround the event, triggering it when they yield. Callback code
+ # can be contained in instance methods, procs or lambdas, or callback objects
+ # that respond to certain predetermined methods. See +ClassMethods.set_callback+
+ # for details.
#
# class Record
# include ActiveSupport::Callbacks
@@ -55,7 +54,6 @@ module ActiveSupport
# saving...
# - save
# saved
- #
module Callbacks
extend Concern
@@ -63,28 +61,29 @@ module ActiveSupport
extend ActiveSupport::DescendantsTracker
end
+ CALLBACK_FILTER_TYPES = [:before, :after, :around]
+
# Runs the callbacks for the given event.
#
# Calls the before and around callbacks in the order they were set, yields
- # the block (if given one), and then runs the after callbacks in reverse order.
- # Optionally accepts a key, which will be used to compile an optimized callback
- # method for each key. See +ClassMethods.define_callbacks+ for more information.
+ # the block (if given one), and then runs the after callbacks in reverse
+ # order.
#
- # If the callback chain was halted, returns +false+. Otherwise returns the result
- # of the block, or +true+ if no block is given.
+ # If the callback chain was halted, returns +false+. Otherwise returns the
+ # result of the block, or +true+ if no block is given.
#
# run_callbacks :save do
# save
# end
- #
- def run_callbacks(kind, *args, &block)
- send("_run_#{kind}_callbacks", *args, &block)
+ def run_callbacks(kind, &block)
+ runner_name = self.class.__define_callbacks(kind, self)
+ send(runner_name, &block)
end
private
# A hook invoked everytime a before callback is halted.
- # This can be overriden in AS::Callback implementors in order
+ # This can be overridden in AS::Callback implementors in order
# to provide better debugging/logging.
def halted_callback_hook(filter)
end
@@ -92,41 +91,37 @@ module ActiveSupport
class Callback #:nodoc:#
@@_callback_sequence = 0
- attr_accessor :chain, :filter, :kind, :options, :per_key, :klass, :raw_filter
+ attr_accessor :chain, :filter, :kind, :options, :klass, :raw_filter
def initialize(chain, filter, kind, options, klass)
@chain, @kind, @klass = chain, kind, klass
+ deprecate_per_key_option(options)
normalize_options!(options)
- @per_key = options.delete(:per_key)
@raw_filter, @options = filter, options
@filter = _compile_filter(filter)
- @compiled_options = _compile_options(options)
- @callback_id = next_id
+ recompile_options!
+ end
- _compile_per_key_options
+ def deprecate_per_key_option(options)
+ if options[:per_key]
+ raise NotImplementedError, ":per_key option is no longer supported. Use generic :if and :unless options instead."
+ end
end
def clone(chain, klass)
obj = super()
obj.chain = chain
obj.klass = klass
- obj.per_key = @per_key.dup
obj.options = @options.dup
- obj.per_key[:if] = @per_key[:if].dup
- obj.per_key[:unless] = @per_key[:unless].dup
obj.options[:if] = @options[:if].dup
obj.options[:unless] = @options[:unless].dup
obj
end
def normalize_options!(options)
- options[:if] = Array.wrap(options[:if])
- options[:unless] = Array.wrap(options[:unless])
-
- options[:per_key] ||= {}
- options[:per_key][:if] = Array.wrap(options[:per_key][:if])
- options[:per_key][:unless] = Array.wrap(options[:per_key][:unless])
+ options[:if] = Array(options[:if])
+ options[:unless] = Array(options[:unless])
end
def name
@@ -141,44 +136,26 @@ module ActiveSupport
@kind == _kind && @filter == _filter
end
- def _update_filter(filter_options, new_options)
- filter_options[:if].push(new_options[:unless]) if new_options.key?(:unless)
- filter_options[:unless].push(new_options[:if]) if new_options.key?(:if)
+ def duplicates?(other)
+ matches?(other.kind, other.filter)
end
- def recompile!(_options, _per_key)
- _update_filter(self.options, _options)
- _update_filter(self.per_key, _per_key)
-
- @callback_id = next_id
- @filter = _compile_filter(@raw_filter)
- @compiled_options = _compile_options(@options)
- _compile_per_key_options
+ def _update_filter(filter_options, new_options)
+ filter_options[:if].concat(Array(new_options[:unless])) if new_options.key?(:unless)
+ filter_options[:unless].concat(Array(new_options[:if])) if new_options.key?(:if)
end
- def _compile_per_key_options
- key_options = _compile_options(@per_key)
+ def recompile!(_options)
+ deprecate_per_key_option(_options)
+ _update_filter(self.options, _options)
- @klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
- def _one_time_conditions_valid_#{@callback_id}?
- true if #{key_options}
- end
- RUBY_EVAL
+ recompile_options!
end
- # This will supply contents for before and around filters, and no
- # contents for after filters (for the forward pass).
- def start(key=nil, object=nil)
- return if key && !object.send("_one_time_conditions_valid_#{@callback_id}?")
-
- # options[0] is the compiled form of supplied conditions
- # options[1] is the "end" for the conditional
- #
+ # Wraps code with filter
+ def apply(code)
case @kind
when :before
- # if condition # before_save :filter_name, :if => :condition
- # filter_name
- # end
<<-RUBY_EVAL
if !halted && #{@compiled_options}
# This double assignment is to prevent warnings in 1.9.3 as
@@ -190,54 +167,20 @@ module ActiveSupport
halted_callback_hook(#{@raw_filter.inspect.inspect})
end
end
+ #{code}
RUBY_EVAL
- when :around
- # Compile around filters with conditions into proxy methods
- # that contain the conditions.
- #
- # For `around_save :filter_name, :if => :condition':
- #
- # def _conditional_callback_save_17
- # if condition
- # filter_name do
- # yield self
- # end
- # else
- # yield self
- # end
- # end
- #
- name = "_conditional_callback_#{@kind}_#{next_id}"
- @klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
- def #{name}(halted)
- if #{@compiled_options} && !halted
- #{@filter} do
- yield self
- end
- else
- yield self
- end
- end
- RUBY_EVAL
- "#{name}(halted) do"
- end
- end
-
- # This will supply contents for around and after filters, but not
- # before filters (for the backward pass).
- def end(key=nil, object=nil)
- return if key && !object.send("_one_time_conditions_valid_#{@callback_id}?")
-
- case @kind
when :after
- # after_save :filter_name, :if => :condition
<<-RUBY_EVAL
- if #{@compiled_options}
+ #{code}
+ if #{!chain.config[:skip_after_callbacks_if_terminated] || "!halted"} && #{@compiled_options}
#{@filter}
end
RUBY_EVAL
when :around
+ name = define_conditional_callback
<<-RUBY_EVAL
+ #{name}(halted) do
+ #{code}
value
end
RUBY_EVAL
@@ -246,46 +189,74 @@ module ActiveSupport
private
+ # Compile around filters with conditions into proxy methods
+ # that contain the conditions.
+ #
+ # For `set_callback :save, :around, :filter_name, if: :condition':
+ #
+ # def _conditional_callback_save_17
+ # if condition
+ # filter_name do
+ # yield self
+ # end
+ # else
+ # yield self
+ # end
+ # end
+ def define_conditional_callback
+ name = "_conditional_callback_#{@kind}_#{next_id}"
+ @klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
+ def #{name}(halted)
+ if #{@compiled_options} && !halted
+ #{@filter} do
+ yield self
+ end
+ else
+ yield self
+ end
+ end
+ RUBY_EVAL
+ name
+ end
+
# Options support the same options as filters themselves (and support
# symbols, string, procs, and objects), so compile a conditional
- # expression based on the options
- def _compile_options(options)
+ # expression based on the options.
+ def recompile_options!
conditions = ["true"]
unless options[:if].empty?
- conditions << Array.wrap(_compile_filter(options[:if]))
+ conditions << Array(_compile_filter(options[:if]))
end
unless options[:unless].empty?
- conditions << Array.wrap(_compile_filter(options[:unless])).map {|f| "!#{f}"}
+ conditions << Array(_compile_filter(options[:unless])).map {|f| "!#{f}"}
end
- conditions.flatten.join(" && ")
+ @compiled_options = conditions.flatten.join(" && ")
end
# Filters support:
#
# Arrays:: Used in conditions. This is used to specify
# multiple conditions. Used internally to
- # merge conditions from skip_* filters
- # Symbols:: A method to call
- # Strings:: Some content to evaluate
- # Procs:: A proc to call with the object
- # Objects:: An object with a before_foo method on it to call
+ # merge conditions from skip_* filters.
+ # Symbols:: A method to call.
+ # Strings:: Some content to evaluate.
+ # Procs:: A proc to call with the object.
+ # Objects:: An object with a <tt>before_foo</tt> method on it to call.
#
# All of these objects are compiled into methods and handled
# the same after this point:
#
- # Arrays:: Merged together into a single filter
- # Symbols:: Already methods
- # Strings:: class_eval'ed into methods
- # Procs:: define_method'ed into methods
+ # Arrays:: Merged together into a single filter.
+ # Symbols:: Already methods.
+ # Strings:: class_eval'ed into methods.
+ # Procs:: define_method'ed into methods.
# Objects::
# a method is created that calls the before_foo method
# on the object.
- #
def _compile_filter(filter)
- method_name = "_callback_#{@kind}_#{next_id}"
case filter
when Array
filter.map {|f| _compile_filter(f)}
@@ -294,15 +265,17 @@ module ActiveSupport
when String
"(#{filter})"
when Proc
+ method_name = "_callback_#{@kind}_#{next_id}"
@klass.send(:define_method, method_name, &filter)
return method_name if filter.arity <= 0
method_name << (filter.arity == 1 ? "(self)" : " self, Proc.new ")
else
+ method_name = "_callback_#{@kind}_#{next_id}"
@klass.send(:define_method, "#{method_name}_object") { filter }
_normalize_legacy_filter(kind, filter)
- scopes = Array.wrap(chain.config[:scope])
+ scopes = Array(chain.config[:scope])
method_to_call = scopes.map{ |s| s.is_a?(Symbol) ? send(s) : s }.join("_")
@klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
@@ -317,10 +290,15 @@ module ActiveSupport
def _normalize_legacy_filter(kind, filter)
if !filter.respond_to?(kind) && filter.respond_to?(:filter)
+ message = "Filter object with #filter method is deprecated. Define method corresponding " \
+ "to filter type (#before, #after or #around)."
+ ActiveSupport::Deprecation.warn message
filter.singleton_class.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
def #{kind}(context, &block) filter(context, &block) end
RUBY_EVAL
- elsif filter.respond_to?(:before) && filter.respond_to?(:after) && kind == :around
+ elsif filter.respond_to?(:before) && filter.respond_to?(:after) && kind == :around && !filter.respond_to?(:around)
+ message = "Filter object with #before and #after methods is deprecated. Define #around method instead."
+ ActiveSupport::Deprecation.warn message
def filter.around(context)
should_continue = before(context)
yield if should_continue
@@ -330,7 +308,7 @@ module ActiveSupport
end
end
- # An Array with a compile method
+ # An Array with a compile method.
class CallbackChain < Array #:nodoc:#
attr_reader :name, :config
@@ -338,87 +316,86 @@ module ActiveSupport
@name = name
@config = {
:terminator => "false",
- :rescuable => false,
:scope => [ :kind ]
- }.merge(config)
+ }.merge!(config)
end
- def compile(key=nil, object=nil)
- method = []
- method << "value = nil"
- method << "halted = false"
-
- each do |callback|
- method << callback.start(key, object)
+ def compile
+ method = ["value = nil", "halted = false"]
+ callbacks = "value = !halted && (!block_given? || yield)"
+ reverse_each do |callback|
+ callbacks = callback.apply(callbacks)
end
+ method << callbacks
- if config[:rescuable]
- method << "rescued_error = nil"
- method << "begin"
- end
+ method << "value"
+ method.join("\n")
+ end
- method << "value = yield if block_given? && !halted"
+ def append(*callbacks)
+ callbacks.each { |c| append_one(c) }
+ end
- if config[:rescuable]
- method << "rescue Exception => e"
- method << "rescued_error = e"
- method << "end"
- end
+ def prepend(*callbacks)
+ callbacks.each { |c| prepend_one(c) }
+ end
- reverse_each do |callback|
- method << callback.end(key, object)
- end
+ private
- method << "raise rescued_error if rescued_error" if config[:rescuable]
- method << "halted ? false : (block_given? ? value : true)"
- method.compact.join("\n")
+ def append_one(callback)
+ remove_duplicates(callback)
+ push(callback)
end
+
+ def prepend_one(callback)
+ remove_duplicates(callback)
+ unshift(callback)
+ end
+
+ def remove_duplicates(callback)
+ delete_if { |c| callback.duplicates?(c) }
+ end
+
end
module ClassMethods
- # Generate the internal runner method called by +run_callbacks+.
- def __define_runner(symbol) #:nodoc:
- runner_method = "_run_#{symbol}_callbacks"
- unless private_method_defined?(runner_method)
- class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
- def #{runner_method}(key = nil, &blk)
- self.class.__run_callback(key, :#{symbol}, self, &blk)
- end
- private :#{runner_method}
- RUBY_EVAL
- end
- end
- # This method calls the callback method for the given key.
- # If this called first time it creates a new callback method for the key,
- # calculating which callbacks can be omitted because of per_key conditions.
- #
- def __run_callback(key, kind, object, &blk) #:nodoc:
- name = __callback_runner_name(key, kind)
+ # This method defines callback chain method for the given kind
+ # if it was not yet defined.
+ # This generated method plays caching role.
+ def __define_callbacks(kind, object) #:nodoc:
+ name = __callback_runner_name(kind)
unless object.respond_to?(name, true)
- str = object.send("_#{kind}_callbacks").compile(key, object)
+ str = object.send("_#{kind}_callbacks").compile
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
def #{name}() #{str} end
protected :#{name}
RUBY_EVAL
end
- object.send(name, &blk)
+ name
end
def __reset_runner(symbol)
- name = __callback_runner_name(nil, symbol)
+ name = __callback_runner_name(symbol)
undef_method(name) if method_defined?(name)
end
- def __callback_runner_name(key, kind)
- "_run__#{self.name.hash.abs}__#{kind}__#{key.hash.abs}__callbacks"
+ def __callback_runner_name_cache
+ @__callback_runner_name_cache ||= ThreadSafe::Cache.new {|cache, kind| cache[kind] = __generate_callback_runner_name(kind) }
+ end
+
+ def __generate_callback_runner_name(kind)
+ "_run__#{self.name.hash.abs}__#{kind}__callbacks"
+ end
+
+ def __callback_runner_name(kind)
+ __callback_runner_name_cache[kind]
end
# This is used internally to append, prepend and skip callbacks to the
# CallbackChain.
- #
def __update_callbacks(name, filters = [], block = nil) #:nodoc:
- type = filters.first.in?([:before, :after, :around]) ? filters.shift : :before
+ type = CALLBACK_FILTER_TYPES.include?(filters.first) ? filters.shift : :before
options = filters.last.is_a?(Hash) ? filters.pop : {}
filters.unshift(block) if block
@@ -432,8 +409,8 @@ module ActiveSupport
# Install a callback for the given event.
#
# set_callback :save, :before, :before_meth
- # set_callback :save, :after, :after_meth, :if => :condition
- # set_callback :save, :around, lambda { |r| stuff; result = yield; stuff }
+ # set_callback :save, :after, :after_meth, if: :condition
+ # set_callback :save, :around, ->(r, &block) { stuff; result = block.call; stuff }
#
# The second arguments indicates whether the callback is to be run +:before+,
# +:after+, or +:around+ the event. If omitted, +:before+ is assumed. This
@@ -441,53 +418,29 @@ module ActiveSupport
#
# set_callback :save, :before_meth
#
- # The callback can 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+.
+ # 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+.
#
# 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
# an argument.
#
- # Before and around callbacks are called in the order that they are set; after
- # callbacks are called in the reverse order.
- #
+ # Before and around callbacks are called in the order that they are set;
+ # after callbacks are called in the reverse order.
+ #
# Around callbacks can access the return value from the event, if it
# wasn't halted, from the +yield+ call.
#
# ===== Options
#
- # * <tt>:if</tt> - A symbol naming an instance method or a proc; the callback
- # will be called only when it returns a true value.
- # * <tt>:unless</tt> - A symbol naming an instance method or a proc; the callback
- # will be called only when it returns a false value.
- # * <tt>:prepend</tt> - If true, the callback will be prepended to the existing
- # chain rather than appended.
- # * <tt>:per_key</tt> - A hash with <tt>:if</tt> and <tt>:unless</tt> options;
- # see "Per-key conditions" below.
- #
- # ===== Per-key conditions
- #
- # When creating or skipping callbacks, you can specify conditions that
- # are always the same for a given key. For instance, in Action Pack,
- # we convert :only and :except conditions into per-key conditions.
- #
- # before_filter :authenticate, :except => "index"
- #
- # becomes
- #
- # set_callback :process_action, :before, :authenticate, :per_key => {:unless => proc {|c| c.action_name == "index"}}
- #
- # Per-key conditions are evaluated only once per use of a given key.
- # In the case of the above example, you would do:
- #
- # run_callbacks(:process_action, action_name) { ... dispatch stuff ... }
- #
- # In that case, each action_name would get its own compiled callback
- # method that took into consideration the per_key conditions. This
- # is a speed improvement for ActionPack.
- #
+ # * <tt>:if</tt> - A symbol naming an instance method or a proc; the
+ # callback will be called only when it returns a +true+ value.
+ # * <tt>:unless</tt> - A symbol naming an instance method or a proc; the
+ # callback will be called only when it returns a +false+ value.
+ # * <tt>:prepend</tt> - If +true+, the callback will be prepended to the
+ # existing chain rather than appended.
def set_callback(name, *filter_list, &block)
mapped = nil
@@ -496,23 +449,19 @@ module ActiveSupport
Callback.new(chain, filter, type, options.dup, self)
end
- filters.each do |filter|
- chain.delete_if {|c| c.matches?(type, filter) }
- end
-
- options[:prepend] ? chain.unshift(*(mapped.reverse)) : chain.push(*mapped)
+ options[:prepend] ? chain.prepend(*mapped) : chain.append(*mapped)
target.send("_#{name}_callbacks=", chain)
end
end
- # Skip a previously set callback. Like +set_callback+, <tt>:if</tt> or <tt>:unless</tt>
- # options may be passed in order to control when the callback is skipped.
+ # Skip a previously set callback. Like +set_callback+, <tt>:if</tt> or
+ # <tt>:unless</tt> options may be passed in order to control when the
+ # callback is skipped.
#
# class Writer < Person
- # skip_callback :validate, :before, :check_membership, :if => lambda { self.age > 18 }
+ # skip_callback :validate, :before, :check_membership, if: -> { self.age > 18 }
# end
- #
def skip_callback(name, *filter_list, &block)
__update_callbacks(name, filter_list, block) do |target, chain, type, filters, options|
filters.each do |filter|
@@ -521,7 +470,7 @@ module ActiveSupport
if filter && options.any?
new_filter = filter.clone(chain, self)
chain.insert(chain.index(filter), new_filter)
- new_filter.recompile!(options, options[:per_key] || {})
+ new_filter.recompile!(options)
end
chain.delete(filter)
@@ -531,7 +480,6 @@ module ActiveSupport
end
# Remove all set callbacks for the given event.
- #
def reset_callbacks(symbol)
callbacks = send("_#{symbol}_callbacks")
@@ -554,24 +502,25 @@ module ActiveSupport
#
# ===== Options
#
- # * <tt>:terminator</tt> - Determines when a before filter will halt the callback
- # chain, preventing following callbacks from being called and the event from being
- # triggered. This is a string to be eval'ed. The result of the callback is available
- # in the <tt>result</tt> variable.
+ # * <tt>:terminator</tt> - Determines when a before filter will halt the
+ # callback chain, preventing following callbacks from being called and
+ # the event from being triggered. This is a string to be eval'ed. The
+ # result of the callback is available in the +result+ variable.
#
- # define_callbacks :validate, :terminator => "result == false"
+ # define_callbacks :validate, terminator: 'result == false'
#
# In this example, if any before validate callbacks returns +false+,
- # other callbacks are not executed. Defaults to "false", meaning no value
+ # other callbacks are not executed. Defaults to +false+, meaning no value
# halts the chain.
#
- # * <tt>:rescuable</tt> - By default, after filters are not executed if
- # the given block or a before filter raises an error. By setting this option
- # to <tt>true</tt> exception raised by given block is stored and after
- # executing all the after callbacks the stored exception is raised.
+ # * <tt>:skip_after_callbacks_if_terminated</tt> - Determines if after
+ # callbacks should be terminated by the <tt>:terminator</tt> option. By
+ # default after callbacks executed no matter if callback chain was
+ # terminated or not. Option makes sense only when <tt>:terminator</tt>
+ # option is specified.
#
- # * <tt>:scope</tt> - Indicates which methods should be executed when an object
- # is used as a callback.
+ # * <tt>:scope</tt> - Indicates which methods should be executed when an
+ # object is used as a callback.
#
# class Audit
# def before(caller)
@@ -596,29 +545,28 @@ module ActiveSupport
# end
# end
#
- # In the above case whenever you save an account the method <tt>Audit#before</tt> will
- # be called. On the other hand
+ # In the above case whenever you save an account the method
+ # <tt>Audit#before</tt> will be called. On the other hand
#
- # define_callbacks :save, :scope => [:kind, :name]
+ # define_callbacks :save, scope: [:kind, :name]
#
- # would trigger <tt>Audit#before_save</tt> instead. That's constructed by calling
- # <tt>#{kind}_#{name}</tt> on the given instance. In this case "kind" is "before" and
- # "name" is "save". In this context +:kind+ and +:name+ have special meanings: +:kind+
- # refers to the kind of callback (before/after/around) and +:name+ refers to the
- # method on which callbacks are being defined.
+ # would trigger <tt>Audit#before_save</tt> instead. That's constructed
+ # by calling <tt>#{kind}_#{name}</tt> on the given instance. In this
+ # case "kind" is "before" and "name" is "save". In this context +:kind+
+ # and +:name+ have special meanings: +:kind+ refers to the kind of
+ # callback (before/after/around) and +:name+ refers to the method on
+ # which callbacks are being defined.
#
# A declaration like
#
- # define_callbacks :save, :scope => [:name]
+ # define_callbacks :save, scope: [:name]
#
# would call <tt>Audit#save</tt>.
- #
def define_callbacks(*callbacks)
config = callbacks.last.is_a?(Hash) ? callbacks.pop : {}
callbacks.each do |callback|
class_attribute "_#{callback}_callbacks"
send("_#{callback}_callbacks=", CallbackChain.new(callback, config))
- __define_runner(callback)
end
end
end
diff --git a/lib/active_support/concern.rb b/lib/active_support/concern.rb
index 677f7c0..eeeba60 100644
--- a/lib/active_support/concern.rb
+++ b/lib/active_support/concern.rb
@@ -1,5 +1,3 @@
-require 'active_support/deprecation'
-
module ActiveSupport
# A typical module looks like this:
#
@@ -7,7 +5,7 @@ module ActiveSupport
# def self.included(base)
# base.extend ClassMethods
# base.class_eval do
- # scope :disabled, where(:disabled => true)
+ # scope :disabled, -> { where(disabled: true) }
# end
# end
#
@@ -16,7 +14,8 @@ module ActiveSupport
# end
# end
#
- # By using <tt>ActiveSupport::Concern</tt> the above module could instead be written as:
+ # By using <tt>ActiveSupport::Concern</tt> the above module could instead be
+ # written as:
#
# require 'active_support/concern'
#
@@ -24,7 +23,7 @@ module ActiveSupport
# extend ActiveSupport::Concern
#
# included do
- # scope :disabled, where(:disabled => true)
+ # scope :disabled, -> { where(disabled: true) }
# end
#
# module ClassMethods
@@ -32,8 +31,9 @@ module ActiveSupport
# end
# end
#
- # Moreover, it gracefully handles module dependencies. Given a +Foo+ module and a +Bar+
- # module which depends on the former, we would typically write the following:
+ # Moreover, it gracefully handles module dependencies. Given a +Foo+ module
+ # and a +Bar+ module which depends on the former, we would typically write the
+ # following:
#
# module Foo
# def self.included(base)
@@ -56,11 +56,11 @@ module ActiveSupport
# include Bar # Bar is the module that Host really needs
# end
#
- # But why should +Host+ care about +Bar+'s dependencies, namely +Foo+? We could try to hide
- # these from +Host+ directly including +Foo+ in +Bar+:
+ # But why should +Host+ care about +Bar+'s dependencies, namely +Foo+? We
+ # could try to hide these from +Host+ directly including +Foo+ in +Bar+:
#
# module Bar
- # include Foo
+ # include Foo
# def self.included(base)
# base.method_injected_by_foo
# end
@@ -70,18 +70,17 @@ module ActiveSupport
# include Bar
# end
#
- # Unfortunately this won't work, since when +Foo+ is included, its <tt>base</tt> is the +Bar+ module,
- # not the +Host+ class. With <tt>ActiveSupport::Concern</tt>, module dependencies are properly resolved:
+ # Unfortunately this won't work, since when +Foo+ is included, its <tt>base</tt>
+ # is the +Bar+ module, not the +Host+ class. With <tt>ActiveSupport::Concern</tt>,
+ # module dependencies are properly resolved:
#
# require 'active_support/concern'
#
# module Foo
# extend ActiveSupport::Concern
# included do
- # class_eval do
- # def self.method_injected_by_foo
- # ...
- # end
+ # def self.method_injected_by_foo
+ # ...
# end
# end
# end
@@ -98,9 +97,8 @@ module ActiveSupport
# class Host
# include Bar # works, Bar takes care now of its dependencies
# end
- #
module Concern
- def self.extended(base)
+ def self.extended(base) #:nodoc:
base.instance_variable_set("@_dependencies", [])
end
@@ -113,11 +111,6 @@ module ActiveSupport
@_dependencies.each { |dep| base.send(:include, dep) }
super
base.extend const_get("ClassMethods") if const_defined?("ClassMethods")
- if const_defined?("InstanceMethods")
- base.send :include, const_get("InstanceMethods")
- ActiveSupport::Deprecation.warn "The InstanceMethods module inside ActiveSupport::Concern will be " \
- "no longer included automatically. Please define instance methods directly in #{self} instead.", caller
- end
base.class_eval(&@_included_block) if instance_variable_defined?("@_included_block")
end
end
diff --git a/lib/active_support/concurrency/latch.rb b/lib/active_support/concurrency/latch.rb
new file mode 100644
index 0000000..1507de4
--- /dev/null
+++ b/lib/active_support/concurrency/latch.rb
@@ -0,0 +1,27 @@
+require 'thread'
+require 'monitor'
+
+module ActiveSupport
+ module Concurrency
+ class Latch
+ def initialize(count = 1)
+ @count = count
+ @lock = Monitor.new
+ @cv = @lock.new_cond
+ end
+
+ def release
+ @lock.synchronize do
+ @count -= 1 if @count > 0
+ @cv.broadcast if @count.zero?
+ end
+ end
+
+ def await
+ @lock.synchronize do
+ @cv.wait_while { @count > 0 }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/active_support/configurable.rb b/lib/active_support/configurable.rb
index a2d2719..e0d39d5 100644
--- a/lib/active_support/configurable.rb
+++ b/lib/active_support/configurable.rb
@@ -1,7 +1,5 @@
require 'active_support/concern'
require 'active_support/ordered_options'
-require 'active_support/core_ext/kernel/singleton_class'
-require 'active_support/core_ext/module/delegation'
require 'active_support/core_ext/array/extract_options'
module ActiveSupport
@@ -15,7 +13,7 @@ module ActiveSupport
self.class.compile_methods!(keys)
end
- # compiles reader methods so we don't have to go through method_missing
+ # Compiles reader methods so we don't have to go through method_missing.
def self.compile_methods!(keys)
keys.reject { |m| method_defined?(m) }.each do |key|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
@@ -39,29 +37,89 @@ module ActiveSupport
yield config
end
- # Allows you to add shortcut so that you don't have to refer to attribute through config.
- # Also look at the example for config to contrast.
+ # Allows you to add shortcut so that you don't have to refer to attribute
+ # through config. Also look at the example for config to contrast.
+ #
+ # Defines both class and instance config accessors.
#
# class User
# include ActiveSupport::Configurable
# config_accessor :allowed_access
# end
#
+ # User.allowed_access # => nil
+ # User.allowed_access = false
+ # User.allowed_access # => false
+ #
# user = User.new
+ # user.allowed_access # => false
# user.allowed_access = true
# user.allowed_access # => true
#
+ # User.allowed_access # => false
+ #
+ # The attribute name must be a valid method name in Ruby.
+ #
+ # class User
+ # include ActiveSupport::Configurable
+ # config_accessor :"1_Badname"
+ # end
+ # # => NameError: invalid config attribute name
+ #
+ # 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 User
+ # include ActiveSupport::Configurable
+ # config_accessor :allowed_access, instance_reader: false, instance_writer: false
+ # end
+ #
+ # User.allowed_access = false
+ # User.allowed_access # => false
+ #
+ # User.new.allowed_access = true # => NoMethodError
+ # User.new.allowed_access # => NoMethodError
+ #
+ # Or pass <tt>instance_accessor: false</tt>, to opt out both instance methods.
+ #
+ # class User
+ # include ActiveSupport::Configurable
+ # config_accessor :allowed_access, instance_accessor: false
+ # end
+ #
+ # User.allowed_access = false
+ # User.allowed_access # => false
+ #
+ # User.new.allowed_access = true # => NoMethodError
+ # User.new.allowed_access # => NoMethodError
+ #
+ # Also you can pass a block to set up the attribute with a default value.
+ #
+ # class User
+ # include ActiveSupport::Configurable
+ # config_accessor :hair_colors do
+ # [:brown, :black, :blonde, :red]
+ # end
+ # end
+ #
+ # User.hair_colors # => [:brown, :black, :blonde, :red]
def config_accessor(*names)
options = names.extract_options!
names.each do |name|
- reader, line = "def #{name}; config.#{name}; end", __LINE__
- writer, line = "def #{name}=(value); config.#{name} = value; end", __LINE__
+ raise NameError.new('invalid config attribute name') unless name =~ /^[_A-Za-z]\w*$/
- singleton_class.class_eval reader, __FILE__, line
- singleton_class.class_eval writer, __FILE__, line
- class_eval reader, __FILE__, line unless options[:instance_reader] == false
- class_eval writer, __FILE__, line unless options[:instance_writer] == false
+ reader, reader_line = "def #{name}; config.#{name}; end", __LINE__
+ writer, writer_line = "def #{name}=(value); config.#{name} = value; end", __LINE__
+
+ singleton_class.class_eval reader, __FILE__, reader_line
+ singleton_class.class_eval writer, __FILE__, writer_line
+
+ unless options[:instance_accessor] == false
+ class_eval reader, __FILE__, reader_line unless options[:instance_reader] == false
+ class_eval writer, __FILE__, writer_line unless options[:instance_writer] == false
+ end
+ send("#{name}=", yield) if block_given?
end
end
end
@@ -81,7 +139,6 @@ module ActiveSupport
#
# user.config.allowed_access # => true
# user.config.level # => 1
- #
def config
@_config ||= self.class.config.inheritable_copy
end
diff --git a/lib/active_support/core_ext.rb b/lib/active_support/core_ext.rb
index 46a8609..998a59c 100644
--- a/lib/active_support/core_ext.rb
+++ b/lib/active_support/core_ext.rb
@@ -1,3 +1,4 @@
-Dir["#{File.dirname(__FILE__)}/core_ext/*.rb"].sort.each do |path|
- require "active_support/core_ext/#{File.basename(path, '.rb')}"
+Dir["#{File.dirname(__FILE__)}/core_ext/*.rb"].each do |path|
+ next if File.basename(path, '.rb') == 'logger'
+ require path
end
diff --git a/lib/active_support/core_ext/array.rb b/lib/active_support/core_ext/array.rb
index 268c9be..79ba791 100644
--- a/lib/active_support/core_ext/array.rb
+++ b/lib/active_support/core_ext/array.rb
@@ -4,5 +4,4 @@ require 'active_support/core_ext/array/uniq_by'
require 'active_support/core_ext/array/conversions'
require 'active_support/core_ext/array/extract_options'
require 'active_support/core_ext/array/grouping'
-require 'active_support/core_ext/array/random_access'
require 'active_support/core_ext/array/prepend_and_append'
diff --git a/lib/active_support/core_ext/array/access.rb b/lib/active_support/core_ext/array/access.rb
index 6162f7a..4f1e432 100644
--- a/lib/active_support/core_ext/array/access.rb
+++ b/lib/active_support/core_ext/array/access.rb
@@ -1,40 +1,48 @@
class Array
# Returns the tail of the array from +position+.
#
- # %w( a b c d ).from(0) # => %w( a b c d )
- # %w( a b c d ).from(2) # => %w( c d )
- # %w( a b c d ).from(10) # => %w()
- # %w().from(0) # => %w()
+ # %w( a b c d ).from(0) # => ["a", "b", "c", "d"]
+ # %w( a b c d ).from(2) # => ["c", "d"]
+ # %w( a b c d ).from(10) # => []
+ # %w().from(0) # => []
def from(position)
self[position, length] || []
end
# Returns the beginning of the array up to +position+.
#
- # %w( a b c d ).to(0) # => %w( a )
- # %w( a b c d ).to(2) # => %w( a b c )
- # %w( a b c d ).to(10) # => %w( a b c d )
- # %w().to(0) # => %w()
+ # %w( a b c d ).to(0) # => ["a"]
+ # %w( a b c d ).to(2) # => ["a", "b", "c"]
+ # %w( a b c d ).to(10) # => ["a", "b", "c", "d"]
+ # %w().to(0) # => []
def to(position)
- self.first position + 1
+ first position + 1
end
# Equal to <tt>self[1]</tt>.
+ #
+ # %w( a b c d e ).second # => "b"
def second
self[1]
end
# Equal to <tt>self[2]</tt>.
+ #
+ # %w( a b c d e ).third # => "c"
def third
self[2]
end
# Equal to <tt>self[3]</tt>.
+ #
+ # %w( a b c d e ).fourth # => "d"
def fourth
self[3]
end
# Equal to <tt>self[4]</tt>.
+ #
+ # %w( a b c d e ).fifth # => "e"
def fifth
self[4]
end
diff --git a/lib/active_support/core_ext/array/conversions.rb b/lib/active_support/core_ext/array/conversions.rb
index f3d06ec..3807ee6 100644
--- a/lib/active_support/core_ext/array/conversions.rb
+++ b/lib/active_support/core_ext/array/conversions.rb
@@ -1,41 +1,99 @@
require 'active_support/xml_mini'
require 'active_support/core_ext/hash/keys'
-require 'active_support/core_ext/hash/reverse_merge'
require 'active_support/core_ext/string/inflections'
+require 'active_support/core_ext/object/to_param'
+require 'active_support/core_ext/object/to_query'
class Array
- # Converts the array to a comma-separated sentence where the last element is joined by the connector word. Options:
- # * <tt>:words_connector</tt> - The sign or word used to join the elements in arrays with two or more elements (default: ", ")
- # * <tt>:two_words_connector</tt> - The sign or word used to join the elements in arrays with two elements (default: " and ")
- # * <tt>:last_word_connector</tt> - The sign or word used to join the last element in arrays with three or more elements (default: ", and ")
+ # Converts the array to a comma-separated sentence where the last element is
+ # joined by the connector word.
+ #
+ # You can pass the following options to change the default behavior. If you
+ # pass an option key that doesn't exist in the list below, it will raise an
+ # <tt>ArgumentError</tt>.
+ #
+ # ==== Options
+ #
+ # * <tt>:words_connector</tt> - The sign or word used to join the elements
+ # in arrays with two or more elements (default: ", ").
+ # * <tt>:two_words_connector</tt> - The sign or word used to join the elements
+ # in arrays with two elements (default: " and ").
+ # * <tt>:last_word_connector</tt> - The sign or word used to join the last element
+ # in arrays with three or more elements (default: ", and ").
+ # * <tt>:locale</tt> - If +i18n+ is available, you can set a locale and use
+ # the connector options defined on the 'support.array' namespace in the
+ # corresponding dictionary file.
+ #
+ # ==== Examples
+ #
+ # [].to_sentence # => ""
+ # ['one'].to_sentence # => "one"
+ # ['one', 'two'].to_sentence # => "one and two"
+ # ['one', 'two', 'three'].to_sentence # => "one, two, and three"
+ #
+ # ['one', 'two'].to_sentence(passing: 'invalid option')
+ # # => ArgumentError: Unknown key :passing
+ #
+ # ['one', 'two'].to_sentence(two_words_connector: '-')
+ # # => "one-two"
+ #
+ # ['one', 'two', 'three'].to_sentence(words_connector: ' or ', last_word_connector: ' or at least ')
+ # # => "one or two or at least three"
+ #
+ # Using <tt>:locale</tt> option:
+ #
+ # # Given this locale dictionary:
+ # #
+ # # es:
+ # # support:
+ # # array:
+ # # words_connector: " o "
+ # # two_words_connector: " y "
+ # # last_word_connector: " o al menos "
+ #
+ # ['uno', 'dos'].to_sentence(locale: :es)
+ # # => "uno y dos"
+ #
+ # ['uno', 'dos', 'tres'].to_sentence(locale: :es)
+ # # => "uno o dos o al menos tres"
def to_sentence(options = {})
+ options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale)
+
+ default_connectors = {
+ :words_connector => ', ',
+ :two_words_connector => ' and ',
+ :last_word_connector => ', and '
+ }
if defined?(I18n)
- default_words_connector = I18n.translate(:'support.array.words_connector', :locale => options[:locale])
- default_two_words_connector = I18n.translate(:'support.array.two_words_connector', :locale => options[:locale])
- default_last_word_connector = I18n.translate(:'support.array.last_word_connector', :locale => options[:locale])
- else
- default_words_connector = ", "
- default_two_words_connector = " and "
- default_last_word_connector = ", and "
+ i18n_connectors = I18n.translate(:'support.array', locale: options[:locale], default: {})
+ default_connectors.merge!(i18n_connectors)
end
-
- options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale)
- options.reverse_merge! :words_connector => default_words_connector, :two_words_connector => default_two_words_connector, :last_word_connector => default_last_word_connector
+ options = default_connectors.merge!(options)
case length
- when 0
- ""
- when 1
- self[0].to_s.dup
- when 2
- "#{self[0]}#{options[:two_words_connector]}#{self[1]}"
- else
- "#{self[0...-1].join(options[:words_connector])}#{options[:last_word_connector]}#{self[-1]}"
+ when 0
+ ''
+ when 1
+ self[0].to_s.dup
+ when 2
+ "#{self[0]}#{options[:two_words_connector]}#{self[1]}"
+ else
+ "#{self[0...-1].join(options[:words_connector])}#{options[:last_word_connector]}#{self[-1]}"
end
end
# Converts a collection of elements into a formatted string by calling
- # <tt>to_s</tt> on all elements and joining them:
+ # <tt>to_s</tt> on all elements and joining them. Having this model:
+ #
+ # class Blog < ActiveRecord::Base
+ # def to_s
+ # title
+ # end
+ # end
+ #
+ # Blog.all.map(&:title) #=> ["First Post", "Second Post", "Third post"]
+ #
+ # <tt>to_formatted_s</tt> shows us:
#
# Blog.all.to_formatted_s # => "First PostSecond PostThird Post"
#
@@ -45,14 +103,14 @@ class Array
# Blog.all.to_formatted_s(:db) # => "1,2,3"
def to_formatted_s(format = :default)
case format
- when :db
- if respond_to?(:empty?) && self.empty?
- "null"
- else
- collect { |element| element.id }.join(",")
- end
+ when :db
+ if empty?
+ 'null'
else
- to_default_s
+ collect { |element| element.id }.join(',')
+ end
+ else
+ to_default_s
end
end
alias_method :to_default_s, :to_s
@@ -86,20 +144,20 @@ class Array
# </project>
# </projects>
#
- # Otherwise the root element is "records":
+ # Otherwise the root element is "objects":
#
- # [{:foo => 1, :bar => 2}, {:baz => 3}].to_xml
+ # [{ foo: 1, bar: 2}, { baz: 3}].to_xml
#
# <?xml version="1.0" encoding="UTF-8"?>
- # <records type="array">
- # <record>
+ # <objects type="array">
+ # <object>
# <bar type="integer">2</bar>
# <foo type="integer">1</foo>
- # </record>
- # <record>
+ # </object>
+ # <object>
# <baz type="integer">3</baz>
- # </record>
- # </records>
+ # </object>
+ # </objects>
#
# If the collection is empty the root element is "nil-classes" by default:
#
@@ -110,7 +168,7 @@ class Array
#
# To ensure a meaningful root element use the <tt>:root</tt> option:
#
- # customer_with_no_projects.projects.to_xml(:root => "projects")
+ # customer_with_no_projects.projects.to_xml(root: 'projects')
#
# <?xml version="1.0" encoding="UTF-8"?>
# <projects type="array"/>
@@ -120,7 +178,7 @@ class Array
#
# The +options+ hash is passed downwards:
#
- # Message.all.to_xml(:skip_types => true)
+ # Message.all.to_xml(skip_types: true)
#
# <?xml version="1.0" encoding="UTF-8"?>
# <messages>
@@ -138,27 +196,29 @@ class Array
options = options.dup
options[:indent] ||= 2
- options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
- options[:root] ||= if first.class.to_s != "Hash" && all? { |e| e.is_a?(first.class) }
- underscored = ActiveSupport::Inflector.underscore(first.class.name)
- ActiveSupport::Inflector.pluralize(underscored).tr('/', '_')
- else
- "objects"
- end
+ options[:builder] ||= Builder::XmlMarkup.new(indent: options[:indent])
+ options[:root] ||= \
+ if first.class != Hash && all? { |e| e.is_a?(first.class) }
+ underscored = ActiveSupport::Inflector.underscore(first.class.name)
+ ActiveSupport::Inflector.pluralize(underscored).tr('/', '_')
+ else
+ 'objects'
+ end
builder = options[:builder]
builder.instruct! unless options.delete(:skip_instruct)
root = ActiveSupport::XmlMini.rename_key(options[:root].to_s, options)
children = options.delete(:children) || root.singularize
+ attributes = options[:skip_types] ? {} : { type: 'array' }
- attributes = options[:skip_types] ? {} : {:type => "array"}
- return builder.tag!(root, attributes) if empty?
-
- builder.__send__(:method_missing, root, attributes) do
- each { |value| ActiveSupport::XmlMini.to_tag(children, value, options) }
- yield builder if block_given?
+ if empty?
+ builder.tag!(root, attributes)
+ else
+ builder.tag!(root, attributes) do
+ each { |value| ActiveSupport::XmlMini.to_tag(children, value, options) }
+ yield builder if block_given?
+ end
end
end
-
end
diff --git a/lib/active_support/core_ext/array/extract_options.rb b/lib/active_support/core_ext/array/extract_options.rb
index 40ceb3e..9008a0d 100644
--- a/lib/active_support/core_ext/array/extract_options.rb
+++ b/lib/active_support/core_ext/array/extract_options.rb
@@ -17,8 +17,8 @@ class Array
# args.extract_options!
# end
#
- # options(1, 2) # => {}
- # options(1, 2, :a => :b) # => {:a=>:b}
+ # options(1, 2) # => {}
+ # options(1, 2, a: :b) # => {:a=>:b}
def extract_options!
if last.is_a?(Hash) && last.extractable_options?
pop
diff --git a/lib/active_support/core_ext/array/grouping.rb b/lib/active_support/core_ext/array/grouping.rb
index 4cd9bfa..640e6e9 100644
--- a/lib/active_support/core_ext/array/grouping.rb
+++ b/lib/active_support/core_ext/array/grouping.rb
@@ -1,21 +1,22 @@
-require 'enumerator'
-
class Array
# Splits or iterates over the array in groups of size +number+,
# padding any remaining slots with +fill_with+ unless it is +false+.
#
- # %w(1 2 3 4 5 6 7).in_groups_of(3) {|group| p group}
+ # %w(1 2 3 4 5 6 7 8 9 10).in_groups_of(3) {|group| p group}
# ["1", "2", "3"]
# ["4", "5", "6"]
- # ["7", nil, nil]
+ # ["7", "8", "9"]
+ # ["10", nil, nil]
#
- # %w(1 2 3).in_groups_of(2, ' ') {|group| p group}
+ # %w(1 2 3 4 5).in_groups_of(2, ' ') {|group| p group}
# ["1", "2"]
- # ["3", " "]
+ # ["3", "4"]
+ # ["5", " "]
#
- # %w(1 2 3).in_groups_of(2, false) {|group| p group}
+ # %w(1 2 3 4 5).in_groups_of(2, false) {|group| p group}
# ["1", "2"]
- # ["3"]
+ # ["3", "4"]
+ # ["5"]
def in_groups_of(number, fill_with = nil)
if fill_with == false
collection = self
@@ -44,10 +45,10 @@ class Array
# ["5", "6", "7", nil]
# ["8", "9", "10", nil]
#
- # %w(1 2 3 4 5 6 7).in_groups(3, ' ') {|group| p group}
- # ["1", "2", "3"]
- # ["4", "5", " "]
- # ["6", "7", " "]
+ # %w(1 2 3 4 5 6 7 8 9 10).in_groups(3, ' ') {|group| p group}
+ # ["1", "2", "3", "4"]
+ # ["5", "6", "7", " "]
+ # ["8", "9", "10", " "]
#
# %w(1 2 3 4 5 6 7).in_groups(3, false) {|group| p group}
# ["1", "2", "3"]
@@ -57,7 +58,7 @@ class Array
# size / 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 / number
+ division = size.div number
modulo = size % number
# create a new array avoiding dup
@@ -66,9 +67,9 @@ class Array
number.times do |index|
length = division + (modulo > 0 && modulo > index ? 1 : 0)
- padding = fill_with != false &&
- modulo > 0 && length == division ? 1 : 0
- groups << slice(start, length).concat([fill_with] * padding)
+ groups << last_group = slice(start, length)
+ last_group << fill_with if fill_with != false &&
+ modulo > 0 && length == division
start += length
end
@@ -82,13 +83,11 @@ class Array
# Divides the array into one or more subarrays based on a delimiting +value+
# or the result of an optional block.
#
- # [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)
- using_block = block_given?
-
+ # [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)
inject([[]]) do |results, element|
- if (using_block && yield(element)) || (value == element)
+ if block && block.call(element) || value == element
results << []
else
results.last << element
diff --git a/lib/active_support/core_ext/array/random_access.rb b/lib/active_support/core_ext/array/random_access.rb
deleted file mode 100644
index bb1807a..0000000
--- a/lib/active_support/core_ext/array/random_access.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-class Array
- # Backport of Array#sample based on Marc-Andre Lafortune's https://github.com/marcandre/backports/
- # Returns a random element or +n+ random elements from the array.
- # If the array is empty and +n+ is nil, returns <tt>nil</tt>.
- # If +n+ is passed and its value is less than 0, it raises an +ArgumentError+ exception.
- # If the value of +n+ is equal or greater than 0 it returns <tt>[]</tt>.
- #
- # [1,2,3,4,5,6].sample # => 4
- # [1,2,3,4,5,6].sample(3) # => [2, 4, 5]
- # [1,2,3,4,5,6].sample(-3) # => ArgumentError: negative array size
- # [].sample # => nil
- # [].sample(3) # => []
- def sample(n=nil)
- return self[Kernel.rand(size)] if n.nil?
- n = n.to_int
- rescue Exception => e
- raise TypeError, "Coercion error: #{n.inspect}.to_int => Integer failed:\n(#{e.message})"
- else
- raise TypeError, "Coercion error: obj.to_int did NOT return an Integer (was #{n.class})" unless n.kind_of? Integer
- raise ArgumentError, "negative array size" if n < 0
- n = size if n > size
- result = Array.new(self)
- n.times do |i|
- r = i + Kernel.rand(size - i)
- result[i], result[r] = result[r], result[i]
- end
- result[n..size] = []
- result
- end unless method_defined? :sample
-end
diff --git a/lib/active_support/core_ext/array/uniq_by.rb b/lib/active_support/core_ext/array/uniq_by.rb
index 9c5f97b..23573c9 100644
--- a/lib/active_support/core_ext/array/uniq_by.rb
+++ b/lib/active_support/core_ext/array/uniq_by.rb
@@ -1,16 +1,19 @@
class Array
- # Returns an unique array based on the criteria given as a +Proc+.
+ # *DEPRECATED*: Use <tt>Array#uniq</tt> instead.
#
- # [1, 2, 3, 4].uniq_by { |i| i.odd? } # => [1, 2]
+ # Returns a unique array based on the criteria in the block.
#
- def uniq_by
- hash, array = {}, []
- each { |i| hash[yield(i)] ||= (array << i) }
- array
+ # [1, 2, 3, 4].uniq_by { |i| i.odd? } # => [1, 2]
+ def uniq_by(&block)
+ ActiveSupport::Deprecation.warn 'uniq_by is deprecated. Use Array#uniq instead'
+ uniq(&block)
end
- # Same as uniq_by, but modifies self.
- def uniq_by!
- replace(uniq_by{ |i| yield(i) })
+ # *DEPRECATED*: Use <tt>Array#uniq!</tt> instead.
+ #
+ # Same as +uniq_by+, but modifies +self+.
+ def uniq_by!(&block)
+ ActiveSupport::Deprecation.warn 'uniq_by! is deprecated. Use Array#uniq! instead'
+ uniq!(&block)
end
end
diff --git a/lib/active_support/core_ext/array/wrap.rb b/lib/active_support/core_ext/array/wrap.rb
index 4834eca..152eb02 100644
--- a/lib/active_support/core_ext/array/wrap.rb
+++ b/lib/active_support/core_ext/array/wrap.rb
@@ -7,35 +7,32 @@ class Array
# * Otherwise, if the argument responds to +to_ary+ it is invoked, and its result returned.
# * Otherwise, returns an array with the argument as its single element.
#
- # Array.wrap(nil) # => []
- # Array.wrap([1, 2, 3]) # => [1, 2, 3]
- # Array.wrap(0) # => [0]
+ # Array.wrap(nil) # => []
+ # Array.wrap([1, 2, 3]) # => [1, 2, 3]
+ # Array.wrap(0) # => [0]
#
# This method is similar in purpose to <tt>Kernel#Array</tt>, but there are some differences:
#
# * If the argument responds to +to_ary+ the method is invoked. <tt>Kernel#Array</tt>
- # moves on to try +to_a+ if the returned value is +nil+, but <tt>Array.wrap</tt> returns
- # such a +nil+ right away.
+ # moves on to try +to_a+ if the returned value is +nil+, but <tt>Array.wrap</tt> returns
+ # +nil+ right away.
# * If the returned value from +to_ary+ is neither +nil+ nor an +Array+ object, <tt>Kernel#Array</tt>
- # raises an exception, while <tt>Array.wrap</tt> does not, it just returns the value.
- # * It does not call +to_a+ on the argument, though special-cases +nil+ to return an empty array.
+ # raises an exception, while <tt>Array.wrap</tt> does not, it just returns the value.
+ # * It does not call +to_a+ on the argument, but returns an empty array if argument is +nil+.
#
- # The last point is particularly worth comparing for some enumerables:
+ # The second point is easily explained with some enumerables:
#
- # Array(:foo => :bar) # => [[:foo, :bar]]
- # Array.wrap(:foo => :bar) # => [{:foo => :bar}]
- #
- # Array("foo\nbar") # => ["foo\n", "bar"], in Ruby 1.8
- # Array.wrap("foo\nbar") # => ["foo\nbar"]
+ # Array(foo: :bar) # => [[:foo, :bar]]
+ # Array.wrap(foo: :bar) # => [{:foo=>:bar}]
#
# There's also a related idiom that uses the splat operator:
#
# [*object]
#
- # which returns <tt>[nil]</tt> for +nil+, and calls to <tt>Array(object)</tt> otherwise.
+ # which returns <tt>[]</tt> for +nil+, but calls to <tt>Array(object)</tt> otherwise.
#
- # Thus, in this case the behavior is different for +nil+, and the differences with
- # <tt>Kernel#Array</tt> explained above apply to the rest of +object+s.
+ # The differences with <tt>Kernel#Array</tt> explained above
+ # apply to the rest of <tt>object</tt>s.
def self.wrap(object)
if object.nil?
[]
diff --git a/lib/active_support/core_ext/benchmark.rb b/lib/active_support/core_ext/benchmark.rb
index 2d11015..eb25b2b 100644
--- a/lib/active_support/core_ext/benchmark.rb
+++ b/lib/active_support/core_ext/benchmark.rb
@@ -1,6 +1,13 @@
require 'benchmark'
class << Benchmark
+ # Benchmark realtime in milliseconds.
+ #
+ # Benchmark.realtime { User.all }
+ # # => 8.0e-05
+ #
+ # Benchmark.ms { User.all }
+ # # => 0.074
def ms
1000 * realtime { yield }
end
diff --git a/lib/active_support/core_ext/big_decimal/conversions.rb b/lib/active_support/core_ext/big_decimal/conversions.rb
index 391bdc9..39b8cea 100644
--- a/lib/active_support/core_ext/big_decimal/conversions.rb
+++ b/lib/active_support/core_ext/big_decimal/conversions.rb
@@ -1,29 +1,10 @@
require 'bigdecimal'
-
-begin
- require 'psych'
-rescue LoadError
-end
-
+require 'bigdecimal/util'
require 'yaml'
class BigDecimal
- YAML_TAG = 'tag:yaml.org,2002:float'
YAML_MAPPING = { 'Infinity' => '.Inf', '-Infinity' => '-.Inf', 'NaN' => '.NaN' }
- # This emits the number without any scientific notation.
- # This is better than self.to_f.to_s since it doesn't lose precision.
- #
- # Note that reconstituting YAML floats to native floats may lose precision.
- def to_yaml(opts = {})
- return super if defined?(YAML::ENGINE) && !YAML::ENGINE.syck?
-
- YAML.quick_emit(nil, opts) do |out|
- string = to_s
- out.scalar(YAML_TAG, YAML_MAPPING[string] || string, :plain)
- end
- end
-
def encode_with(coder)
string = to_s
coder.represent_scalar(nil, YAML_MAPPING[string] || string)
@@ -37,8 +18,13 @@ class BigDecimal
end
DEFAULT_STRING_FORMAT = 'F'
- def to_formatted_s(format = DEFAULT_STRING_FORMAT)
- _original_to_s(format)
+ def to_formatted_s(*args)
+ if args[0].is_a?(Symbol)
+ super
+ else
+ format = args[0] || DEFAULT_STRING_FORMAT
+ _original_to_s(format)
+ end
end
alias_method :_original_to_s, :to_s
alias_method :to_s, :to_formatted_s
diff --git a/lib/active_support/core_ext/class/attribute.rb b/lib/active_support/core_ext/class/attribute.rb
index 305ed49..6d49b7b 100644
--- a/lib/active_support/core_ext/class/attribute.rb
+++ b/lib/active_support/core_ext/class/attribute.rb
@@ -44,7 +44,8 @@ class Class
# Base.setting # => []
# Subclass.setting # => [:foo]
#
- # For convenience, a query method is defined as well:
+ # For convenience, an instance predicate method is defined as well.
+ # To skip it, pass <tt>instance_predicate: false</tt>.
#
# Subclass.setting? # => false
#
@@ -57,59 +58,68 @@ class Class
# object.setting # => false
# Base.setting # => true
#
- # To opt out of the instance reader method, pass :instance_reader => false.
+ # To opt out of the instance reader method, pass <tt>instance_reader: false</tt>.
#
# object.setting # => NoMethodError
# object.setting? # => NoMethodError
#
- # To opt out of the instance writer method, pass :instance_writer => false.
+ # To opt out of the instance writer method, pass <tt>instance_writer: false</tt>.
#
# object.setting = false # => NoMethodError
+ #
+ # To opt out of both instance methods, pass <tt>instance_accessor: false</tt>.
def class_attribute(*attrs)
options = attrs.extract_options!
- instance_reader = options.fetch(:instance_reader, true)
- instance_writer = options.fetch(:instance_writer, true)
+ # double assignment is used to avoid "assigned but unused variable" warning
+ instance_reader = instance_reader = options.fetch(:instance_accessor, true) && options.fetch(:instance_reader, true)
+ instance_writer = options.fetch(:instance_accessor, true) && options.fetch(:instance_writer, true)
+ instance_predicate = options.fetch(:instance_predicate, true)
attrs.each do |name|
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
- def self.#{name}() nil end
- def self.#{name}?() !!#{name} end
+ define_singleton_method(name) { nil }
+ define_singleton_method("#{name}?") { !!public_send(name) } if instance_predicate
- def self.#{name}=(val)
- singleton_class.class_eval do
- remove_possible_method(:#{name})
- define_method(:#{name}) { val }
- end
+ ivar = "@#{name}"
+
+ define_singleton_method("#{name}=") do |val|
+ singleton_class.class_eval do
+ remove_possible_method(name)
+ define_method(name) { val }
+ end
- if singleton_class?
- class_eval do
- remove_possible_method(:#{name})
- def #{name}
- defined?(@#{name}) ? @#{name} : singleton_class.#{name}
+ if singleton_class?
+ class_eval do
+ remove_possible_method(name)
+ define_method(name) do
+ if instance_variable_defined? ivar
+ instance_variable_get ivar
+ else
+ singleton_class.send name
end
end
end
- val
end
+ val
+ end
- if instance_reader
- remove_possible_method :#{name}
- def #{name}
- defined?(@#{name}) ? @#{name} : self.class.#{name}
- end
-
- def #{name}?
- !!#{name}
+ if instance_reader
+ remove_possible_method name
+ define_method(name) do
+ if instance_variable_defined?(ivar)
+ instance_variable_get ivar
+ else
+ self.class.public_send name
end
end
- RUBY
+ define_method("#{name}?") { !!public_send(name) } if instance_predicate
+ end
attr_writer name if instance_writer
end
end
private
- def singleton_class?
- ancestors.first != self
- end
+ def singleton_class?
+ ancestors.first != self
+ end
end
diff --git a/lib/active_support/core_ext/class/attribute_accessors.rb b/lib/active_support/core_ext/class/attribute_accessors.rb
index 5369348..3485961 100644
--- a/lib/active_support/core_ext/class/attribute_accessors.rb
+++ b/lib/active_support/core_ext/class/attribute_accessors.rb
@@ -21,17 +21,18 @@ class Class
# 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>.
+ # 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
+ # 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
@@ -71,11 +72,11 @@ class Class
# 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>.
+ # 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
+ # cattr_writer :hair_colors, instance_writer: false
# end
#
# Person.new.hair_colors = [:blonde, :red] # => NoMethodError
@@ -92,6 +93,7 @@ class Class
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
@@ -109,7 +111,7 @@ class Class
end
EOS
end
- self.send("#{sym}=", yield) if block_given?
+ send("#{sym}=", yield) if block_given?
end
end
@@ -133,20 +135,20 @@ class Class
# 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 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
+ # 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.
+ # Or pass <tt>instance_accessor: false</tt>, to opt out both instance methods.
#
# class Person
- # cattr_accessor :hair_colors, :instance_accessor => false
+ # cattr_accessor :hair_colors, instance_accessor: false
# end
#
# Person.new.hair_colors = [:brown] # => NoMethodError
diff --git a/lib/active_support/core_ext/class/delegating_attributes.rb b/lib/active_support/core_ext/class/delegating_attributes.rb
index 29bf7c0..ff870f5 100644
--- a/lib/active_support/core_ext/class/delegating_attributes.rb
+++ b/lib/active_support/core_ext/class/delegating_attributes.rb
@@ -1,5 +1,3 @@
-require 'active_support/core_ext/object/blank'
-require 'active_support/core_ext/array/extract_options'
require 'active_support/core_ext/kernel/singleton_class'
require 'active_support/core_ext/module/remove_method'
@@ -22,23 +20,21 @@ class Class
define_method("#{name}?") { !!send("#{name}") } if options[:instance_reader] != false
end
-private
-
- # Take the object being set and store it in a method. This gives us automatic
- # inheritance behavior, without having to store the object in an instance
- # variable and look up the superclass chain manually.
- def _stash_object_in_method(object, method, instance_reader = true)
- singleton_class.remove_possible_method(method)
- singleton_class.send(:define_method, method) { object }
- remove_possible_method(method)
- define_method(method) { object } if instance_reader
- end
-
- def _superclass_delegating_accessor(name, options = {})
- singleton_class.send(:define_method, "#{name}=") do |value|
- _stash_object_in_method(value, name, options[:instance_reader] != false)
+ private
+ # Take the object being set and store it in a method. This gives us automatic
+ # inheritance behavior, without having to store the object in an instance
+ # variable and look up the superclass chain manually.
+ def _stash_object_in_method(object, method, instance_reader = true)
+ singleton_class.remove_possible_method(method)
+ singleton_class.send(:define_method, method) { object }
+ remove_possible_method(method)
+ define_method(method) { object } if instance_reader
end
- send("#{name}=", nil)
- end
+ def _superclass_delegating_accessor(name, options = {})
+ singleton_class.send(:define_method, "#{name}=") do |value|
+ _stash_object_in_method(value, name, options[:instance_reader] != false)
+ end
+ send("#{name}=", nil)
+ end
end
diff --git a/lib/active_support/core_ext/class/subclasses.rb b/lib/active_support/core_ext/class/subclasses.rb
index 46e9daa..3c4bfc5 100644
--- a/lib/active_support/core_ext/class/subclasses.rb
+++ b/lib/active_support/core_ext/class/subclasses.rb
@@ -1,19 +1,19 @@
require 'active_support/core_ext/module/anonymous'
require 'active_support/core_ext/module/reachable'
-class Class #:nodoc:
+class Class
begin
ObjectSpace.each_object(Class.new) {}
- def descendants
+ def descendants # :nodoc:
descendants = []
- ObjectSpace.each_object(class << self; self; end) do |k|
+ ObjectSpace.each_object(singleton_class) do |k|
descendants.unshift k unless k == self
end
descendants
end
rescue StandardError # JRuby
- def descendants
+ def descendants # :nodoc:
descendants = []
ObjectSpace.each_object(Class) do |k|
descendants.unshift k if k < self
@@ -25,7 +25,13 @@ class Class #:nodoc:
# Returns an array with the direct children of +self+.
#
- # Integer.subclasses # => [Bignum, Fixnum]
+ # Integer.subclasses # => [Fixnum, Bignum]
+ #
+ # class Foo; end
+ # class Bar < Foo; end
+ # class Baz < Bar; end
+ #
+ # Foo.subclasses # => [Bar]
def subclasses
subclasses, chain = [], descendants
chain.each do |k|
diff --git a/lib/active_support/core_ext/date.rb b/lib/active_support/core_ext/date.rb
new file mode 100644
index 0000000..465fedd
--- /dev/null
+++ b/lib/active_support/core_ext/date.rb
@@ -0,0 +1,5 @@
+require 'active_support/core_ext/date/acts_like'
+require 'active_support/core_ext/date/calculations'
+require 'active_support/core_ext/date/conversions'
+require 'active_support/core_ext/date/zones'
+
diff --git a/lib/active_support/core_ext/date/calculations.rb b/lib/active_support/core_ext/date/calculations.rb
index f0f6776..06e4847 100644
--- a/lib/active_support/core_ext/date/calculations.rb
+++ b/lib/active_support/core_ext/date/calculations.rb
@@ -3,29 +3,35 @@ require 'active_support/duration'
require 'active_support/core_ext/object/acts_like'
require 'active_support/core_ext/date/zones'
require 'active_support/core_ext/time/zones'
+require 'active_support/core_ext/date_and_time/calculations'
class Date
- DAYS_INTO_WEEK = { :monday => 0, :tuesday => 1, :wednesday => 2, :thursday => 3, :friday => 4, :saturday => 5, :sunday => 6 }
+ include DateAndTime::Calculations
- if RUBY_VERSION < '1.9'
- undef :>>
+ class << self
+ attr_accessor :beginning_of_week_default
- # Backported from 1.9. The one in 1.8 leads to incorrect next_month and
- # friends for dates where the calendar reform is involved. It additionally
- # prevents an infinite loop fixed in r27013.
- def >>(n)
- y, m = (year * 12 + (mon - 1) + n).divmod(12)
- m, = (m + 1) .divmod(1)
- d = mday
- until jd2 = self.class.valid_civil?(y, m, d, start)
- d -= 1
- raise ArgumentError, 'invalid date' unless d > 0
- end
- self + (jd2 - jd)
+ # Returns the week start (e.g. :monday) for the current request, if this has been set (via Date.beginning_of_week=).
+ # If <tt>Date.beginning_of_week</tt> has not been set for the current request, returns the week start specified in <tt>config.beginning_of_week</tt>.
+ # If no config.beginning_of_week was specified, returns :monday.
+ def beginning_of_week
+ Thread.current[:beginning_of_week] || beginning_of_week_default || :monday
+ end
+
+ # Sets <tt>Date.beginning_of_week</tt> to a week start (e.g. :monday) for current request/thread.
+ #
+ # This method accepts any of the following day symbols:
+ # :monday, :tuesday, :wednesday, :thursday, :friday, :saturday, :sunday
+ def beginning_of_week=(week_start)
+ Thread.current[:beginning_of_week] = find_beginning_of_week!(week_start)
+ end
+
+ # Returns week start day symbol (e.g. :monday), or raises an ArgumentError for invalid day symbol.
+ def find_beginning_of_week!(week_start)
+ raise ArgumentError, "Invalid beginning of week: #{week_start}" unless ::Date::DAYS_INTO_WEEK.key?(week_start)
+ week_start
end
- end
- class << self
# Returns a new Date representing the date 1 day ago (i.e. yesterday's date).
def yesterday
::Date.current.yesterday
@@ -42,37 +48,22 @@ class Date
end
end
- # Returns true if the Date object's date lies in the past. Otherwise returns false.
- def past?
- self < ::Date.current
- end
-
- # Returns true if the Date object's date is today.
- def today?
- self.to_date == ::Date.current # we need the to_date because of DateTime
- end
-
- # Returns true if the Date object's date lies in the future.
- def future?
- self > ::Date.current
- end
-
# Converts Date to a Time (or DateTime if necessary) with the time portion set to the beginning of the day (0:00)
# and then subtracts the specified number of seconds.
def ago(seconds)
- to_time_in_current_zone.since(-seconds)
+ in_time_zone.since(-seconds)
end
# Converts Date to a Time (or DateTime if necessary) with the time portion set to the beginning of the day (0:00)
# and then adds the specified number of seconds
def since(seconds)
- to_time_in_current_zone.since(seconds)
+ in_time_zone.since(seconds)
end
alias :in :since
# Converts Date to a Time (or DateTime if necessary) with the time portion set to the beginning of the day (0:00)
def beginning_of_day
- to_time_in_current_zone
+ in_time_zone
end
alias :midnight :beginning_of_day
alias :at_midnight :beginning_of_day
@@ -80,8 +71,9 @@ class Date
# Converts Date to a Time (or DateTime if necessary) with the time portion set to the end of the day (23:59:59)
def end_of_day
- to_time_in_current_zone.end_of_day
+ in_time_zone.end_of_day
end
+ alias :at_end_of_day :end_of_day
def plus_with_duration(other) #:nodoc:
if ActiveSupport::Duration === other
@@ -116,161 +108,26 @@ class Date
end
# Returns a new Date where one or more of the elements have been changed according to the +options+ parameter.
+ # The +options+ parameter is a hash with a combination of these keys: <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>.
#
- # Examples:
- #
- # Date.new(2007, 5, 12).change(:day => 1) # => Date.new(2007, 5, 1)
- # Date.new(2007, 5, 12).change(:year => 2005, :month => 1) # => Date.new(2005, 1, 12)
+ # Date.new(2007, 5, 12).change(day: 1) # => Date.new(2007, 5, 1)
+ # Date.new(2007, 5, 12).change(year: 2005, month: 1) # => Date.new(2005, 1, 12)
def change(options)
::Date.new(
- options[:year] || self.year,
- options[:month] || self.month,
- options[:day] || self.day
+ options.fetch(:year, year),
+ options.fetch(:month, month),
+ options.fetch(:day, day)
)
end
-
- # Returns a new Date/DateTime representing the time a number of specified weeks ago.
- def weeks_ago(weeks)
- advance(:weeks => -weeks)
- end
-
- # Returns a new Date/DateTime representing the time a number of specified months ago.
- def months_ago(months)
- advance(:months => -months)
- end
-
- # Returns a new Date/DateTime representing the time a number of specified months in the future.
- def months_since(months)
- advance(:months => months)
- end
-
- # Returns a new Date/DateTime representing the time a number of specified years ago.
- def years_ago(years)
- advance(:years => -years)
- end
-
- # Returns a new Date/DateTime representing the time a number of specified years in the future.
- def years_since(years)
- advance(:years => years)
- end
-
- # Shorthand for years_ago(1)
- def prev_year
- years_ago(1)
- end unless method_defined?(:prev_year)
-
- # Shorthand for years_since(1)
- def next_year
- years_since(1)
- end unless method_defined?(:next_year)
-
- # Shorthand for months_ago(1)
- def prev_month
- months_ago(1)
- end unless method_defined?(:prev_month)
-
- # Shorthand for months_since(1)
- def next_month
- months_since(1)
- end unless method_defined?(:next_month)
-
- # Returns number of days to start of this week. Week is assumed to start on
- # +start_day+, default is +:monday+.
- def days_to_week_start(start_day = :monday)
- start_day_number = DAYS_INTO_WEEK[start_day]
- current_day_number = wday != 0 ? wday - 1 : 6
- (current_day_number - start_day_number) % 7
- end
-
- # Returns a new +Date+/+DateTime+ representing the start of this week. Week is
- # assumed to start on +start_day+, default is +:monday+. +DateTime+ objects
- # have their time set to 0:00.
- def beginning_of_week(start_day = :monday)
- days_to_start = days_to_week_start(start_day)
- result = self - days_to_start
- acts_like?(:time) ? result.midnight : result
- end
- alias :at_beginning_of_week :beginning_of_week
-
- # Returns a new +Date+/+DateTime+ representing the start of this week. Week is
- # assumed to start on a Monday. +DateTime+ objects have their time set to 0:00.
- def monday
- beginning_of_week
- end
-
- # Returns a new +Date+/+DateTime+ representing the end of this week. Week is
- # assumed to start on +start_day+, default is +:monday+. +DateTime+ objects
- # have their time set to 23:59:59.
- def end_of_week(start_day = :monday)
- days_to_end = 6 - days_to_week_start(start_day)
- result = self + days_to_end.days
- self.acts_like?(:time) ? result.end_of_day : result
- end
- alias :at_end_of_week :end_of_week
-
- # Returns a new +Date+/+DateTime+ representing the end of this week. Week is
- # assumed to start on a Monday. +DateTime+ objects have their time set to 23:59:59.
- def sunday
- end_of_week
- end
-
- # Returns a new +Date+/+DateTime+ representing the given +day+ in the previous
- # week. Default is +:monday+. +DateTime+ objects have their time set to 0:00.
- def prev_week(day = :monday)
- result = (self - 7).beginning_of_week + DAYS_INTO_WEEK[day]
- self.acts_like?(:time) ? result.change(:hour => 0) : result
- end
-
- # Returns a new Date/DateTime representing the start of the given day in next week (default is :monday).
- def next_week(day = :monday)
- result = (self + 7).beginning_of_week + DAYS_INTO_WEEK[day]
- self.acts_like?(:time) ? result.change(:hour => 0) : result
- end
-
- # Returns a new ; DateTime objects will have time set to 0:00DateTime representing the start of the month (1st of the month; DateTime objects will have time set to 0:00)
- def beginning_of_month
- self.acts_like?(:time) ? change(:day => 1, :hour => 0) : change(:day => 1)
- end
- alias :at_beginning_of_month :beginning_of_month
-
- # Returns a new Date/DateTime representing the end of the month (last day of the month; DateTime objects will have time set to 0:00)
- def end_of_month
- last_day = ::Time.days_in_month( self.month, self.year )
- self.acts_like?(:time) ? change(:day => last_day, :hour => 23, :min => 59, :sec => 59) : change(:day => last_day)
- end
- alias :at_end_of_month :end_of_month
-
- # Returns a new Date/DateTime representing the start of the quarter (1st of january, april, july, october; DateTime objects will have time set to 0:00)
- def beginning_of_quarter
- beginning_of_month.change(:month => [10, 7, 4, 1].detect { |m| m <= self.month })
- end
- alias :at_beginning_of_quarter :beginning_of_quarter
-
- # Returns a new Date/DateTime representing the end of the quarter (last day of march, june, september, december; DateTime objects will have time set to 23:59:59)
- def end_of_quarter
- beginning_of_month.change(:month => [3, 6, 9, 12].detect { |m| m >= self.month }).end_of_month
- end
- alias :at_end_of_quarter :end_of_quarter
-
- # Returns a new Date/DateTime representing the start of the year (1st of january; DateTime objects will have time set to 0:00)
- def beginning_of_year
- self.acts_like?(:time) ? change(:month => 1, :day => 1, :hour => 0) : change(:month => 1, :day => 1)
- end
- alias :at_beginning_of_year :beginning_of_year
-
- # Returns a new Time representing the end of the year (31st of december; DateTime objects will have time set to 23:59:59)
- def end_of_year
- self.acts_like?(:time) ? change(:month => 12, :day => 31, :hour => 23, :min => 59, :sec => 59) : change(:month => 12, :day => 31)
- end
- alias :at_end_of_year :end_of_year
-
- # Convenience method which returns a new Date/DateTime representing the time 1 day ago
- def yesterday
- self - 1
- end
-
- # Convenience method which returns a new Date/DateTime representing the time 1 day since the instance time
- def tomorrow
- self + 1
+
+ # 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)
+ self.to_datetime <=> other
+ else
+ compare_without_coercion(other)
+ end
end
+ alias_method :compare_without_coercion, :<=>
+ alias_method :<=>, :compare_with_coercion
end
diff --git a/lib/active_support/core_ext/date/conversions.rb b/lib/active_support/core_ext/date/conversions.rb
index 338104f..cdf606f 100644
--- a/lib/active_support/core_ext/date/conversions.rb
+++ b/lib/active_support/core_ext/date/conversions.rb
@@ -5,25 +5,27 @@ require 'active_support/core_ext/module/remove_method'
class Date
DATE_FORMATS = {
- :short => "%e %b",
- :long => "%B %e, %Y",
- :db => "%Y-%m-%d",
- :number => "%Y%m%d",
- :long_ordinal => lambda { |date| date.strftime("%B #{ActiveSupport::Inflector.ordinalize(date.day)}, %Y") }, # => "April 25th, 2007"
- :rfc822 => "%e %b %Y"
+ :short => '%e %b',
+ :long => '%B %e, %Y',
+ :db => '%Y-%m-%d',
+ :number => '%Y%m%d',
+ :long_ordinal => lambda { |date|
+ day_format = ActiveSupport::Inflector.ordinalize(date.day)
+ date.strftime("%B #{day_format}, %Y") # => "April 25th, 2007"
+ },
+ :rfc822 => '%e %b %Y'
}
# Ruby 1.9 has Date#to_time which converts to localtime only.
- remove_possible_method :to_time
+ remove_method :to_time
# Ruby 1.9 has Date#xmlschema which converts to a string without the time component.
- remove_possible_method :xmlschema
+ remove_method :xmlschema
# Convert to a formatted string. See DATE_FORMATS for predefined formats.
#
# This method is aliased to <tt>to_s</tt>.
#
- # ==== Examples
# date = Date.new(2007, 11, 10) # => Sat, 10 Nov 2007
#
# date.to_formatted_s(:db) # => "2007-11-10"
@@ -40,8 +42,8 @@ class Date
# or Proc instance that takes a date argument as the value.
#
# # config/initializers/time_formats.rb
- # Date::DATE_FORMATS[:month_and_year] = "%B %Y"
- # Date::DATE_FORMATS[:short_ordinal] = lambda { |date| date.strftime("%B #{date.day.ordinalize}") }
+ # 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)
if formatter = DATE_FORMATS[format]
if formatter.respond_to?(:call)
@@ -58,21 +60,14 @@ class Date
# Overrides the default inspect method with a human readable one, e.g., "Mon, 21 Feb 2005"
def readable_inspect
- strftime("%a, %d %b %Y")
+ strftime('%a, %d %b %Y')
end
alias_method :default_inspect, :inspect
alias_method :inspect, :readable_inspect
- # A method to keep Time, Date and DateTime instances interchangeable on conversions.
- # In this case, it simply returns +self+.
- def to_date
- self
- end if RUBY_VERSION < '1.9'
-
# Converts a Date instance to a Time, where the time is set to the beginning of the day.
# The timezone can be either :local or :utc (default :local).
#
- # ==== Examples
# date = Date.new(2007, 11, 10) # => Sat, 10 Nov 2007
#
# date.to_time # => Sat Nov 10 00:00:00 0800 2007
@@ -80,27 +75,10 @@ class Date
#
# date.to_time(:utc) # => Sat Nov 10 00:00:00 UTC 2007
def to_time(form = :local)
- ::Time.send("#{form}_time", year, month, day)
+ ::Time.send(form, year, month, day)
end
- # Converts a Date instance to a DateTime, where the time is set to the beginning of the day
- # and UTC offset is set to 0.
- #
- # ==== Examples
- # date = Date.new(2007, 11, 10) # => Sat, 10 Nov 2007
- #
- # date.to_datetime # => Sat, 10 Nov 2007 00:00:00 0000
- def to_datetime
- ::DateTime.civil(year, month, day, 0, 0, 0, 0)
- end if RUBY_VERSION < '1.9'
-
- def iso8601
- strftime('%F')
- end if RUBY_VERSION < '1.9'
-
- alias_method :rfc3339, :iso8601 if RUBY_VERSION < '1.9'
-
def xmlschema
- to_time_in_current_zone.xmlschema
+ in_time_zone.xmlschema
end
end
diff --git a/lib/active_support/core_ext/date/freeze.rb b/lib/active_support/core_ext/date/freeze.rb
deleted file mode 100644
index a731f83..0000000
--- a/lib/active_support/core_ext/date/freeze.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-# Date memoizes some instance methods using metaprogramming to wrap
-# the methods with one that caches the result in an instance variable.
-#
-# If a Date is frozen but the memoized method hasn't been called, the
-# first call will result in a frozen object error since the memo
-# instance variable is uninitialized.
-#
-# Work around by eagerly memoizing before the first freeze.
-#
-# Ruby 1.9 uses a preinitialized instance variable so it's unaffected.
-# This hack is as close as we can get to feature detection:
-if RUBY_VERSION < '1.9'
- require 'date'
- begin
- ::Date.today.freeze.jd
- rescue => frozen_object_error
- if frozen_object_error.message =~ /frozen/
- class Date #:nodoc:
- def freeze
- unless frozen?
- self.class.private_instance_methods(false).each do |m|
- if m.to_s =~ /\A__\d+__\Z/
- instance_variable_set(:"@#{m}", [send(m)])
- end
- end
- end
-
- super
- end
- end
- end
- end
-end
diff --git a/lib/active_support/core_ext/date/zones.rb b/lib/active_support/core_ext/date/zones.rb
index a70b47b..b454867 100644
--- a/lib/active_support/core_ext/date/zones.rb
+++ b/lib/active_support/core_ext/date/zones.rb
@@ -2,13 +2,36 @@ require 'date'
require 'active_support/core_ext/time/zones'
class Date
- # Converts Date to a TimeWithZone in the current zone if Time.zone or Time.zone_default
- # is set, otherwise converts Date to a Time via Date#to_time
+ # *DEPRECATED*: Use +Date#in_time_zone+ instead.
+ #
+ # Converts Date to a TimeWithZone in the current zone if <tt>Time.zone</tt> or
+ # <tt>Time.zone_default</tt> is set, otherwise converts Date to a Time via
+ # Date#to_time.
def to_time_in_current_zone
+ ActiveSupport::Deprecation.warn 'Date#to_time_in_current_zone is deprecated. Use Date#in_time_zone instead', caller
+
if ::Time.zone
::Time.zone.local(year, month, day)
else
to_time
end
end
+
+ # Converts Date to a TimeWithZone in the current zone if Time.zone or Time.zone_default
+ # is set, otherwise converts Date to a Time via Date#to_time
+ #
+ # Time.zone = 'Hawaii' # => 'Hawaii'
+ # Date.new(2000).in_time_zone # => Sat, 01 Jan 2000 00:00:00 HST -10:00
+ #
+ # You can also pass in a TimeZone instance or string that identifies a TimeZone as an argument,
+ # and the conversion will be based on that zone instead of <tt>Time.zone</tt>.
+ #
+ # Date.new(2000).in_time_zone('Alaska') # => Sat, 01 Jan 2000 00:00:00 AKST -09:00
+ def in_time_zone(zone = ::Time.zone)
+ if zone
+ ::Time.find_zone!(zone).local(year, month, day)
+ else
+ to_time
+ end
+ end
end
diff --git a/lib/active_support/core_ext/date_and_time/calculations.rb b/lib/active_support/core_ext/date_and_time/calculations.rb
new file mode 100644
index 0000000..0d14cba
--- /dev/null
+++ b/lib/active_support/core_ext/date_and_time/calculations.rb
@@ -0,0 +1,232 @@
+module DateAndTime
+ module Calculations
+ DAYS_INTO_WEEK = {
+ :monday => 0,
+ :tuesday => 1,
+ :wednesday => 2,
+ :thursday => 3,
+ :friday => 4,
+ :saturday => 5,
+ :sunday => 6
+ }
+
+ # Returns a new date/time representing yesterday.
+ def yesterday
+ advance(:days => -1)
+ end
+
+ # Returns a new date/time representing tomorrow.
+ def tomorrow
+ advance(:days => 1)
+ end
+
+ # Returns true if the date/time is today.
+ def today?
+ to_date == ::Date.current
+ end
+
+ # Returns true if the date/time is in the past.
+ def past?
+ self < self.class.current
+ end
+
+ # Returns true if the date/time is in the future.
+ def future?
+ self > self.class.current
+ end
+
+ # Returns a new date/time the specified number of days ago.
+ def days_ago(days)
+ advance(:days => -days)
+ end
+
+ # Returns a new date/time the specified number of days in the future.
+ def days_since(days)
+ advance(:days => days)
+ end
+
+ # Returns a new date/time the specified number of weeks ago.
+ def weeks_ago(weeks)
+ advance(:weeks => -weeks)
+ end
+
+ # Returns a new date/time the specified number of weeks in the future.
+ def weeks_since(weeks)
+ advance(:weeks => weeks)
+ end
+
+ # Returns a new date/time the specified number of months ago.
+ def months_ago(months)
+ advance(:months => -months)
+ end
+
+ # Returns a new date/time the specified number of months in the future.
+ def months_since(months)
+ advance(:months => months)
+ end
+
+ # Returns a new date/time the specified number of years ago.
+ def years_ago(years)
+ advance(:years => -years)
+ end
+
+ # Returns a new date/time the specified number of years in the future.
+ def years_since(years)
+ advance(:years => years)
+ end
+
+ # Returns a new date/time at the start of the month.
+ # DateTime objects will have a time set to 0:00.
+ def beginning_of_month
+ first_hour{ change(:day => 1) }
+ end
+ alias :at_beginning_of_month :beginning_of_month
+
+ # Returns a new date/time at the start of the quarter.
+ # Example: 1st January, 1st July, 1st October.
+ # DateTime objects will have a time set to 0:00.
+ def beginning_of_quarter
+ first_quarter_month = [10, 7, 4, 1].detect { |m| m <= month }
+ beginning_of_month.change(:month => first_quarter_month)
+ end
+ alias :at_beginning_of_quarter :beginning_of_quarter
+
+ # Returns a new date/time at the end of the quarter.
+ # Example: 31st March, 30th June, 30th September.
+ # DateTime objects will have a time set to 23:59:59.
+ def end_of_quarter
+ last_quarter_month = [3, 6, 9, 12].detect { |m| m >= month }
+ beginning_of_month.change(:month => last_quarter_month).end_of_month
+ end
+ alias :at_end_of_quarter :end_of_quarter
+
+ # Return a new date/time at the beginning of the year.
+ # Example: 1st January.
+ # DateTime objects will have a time set to 0:00.
+ def beginning_of_year
+ change(:month => 1).beginning_of_month
+ end
+ alias :at_beginning_of_year :beginning_of_year
+
+ # Returns a new date/time representing the given day in the next week.
+ # The +given_day_in_next_week+ defaults to the beginning of the week
+ # which is determined by +Date.beginning_of_week+ or +config.beginning_of_week+
+ # when set. +DateTime+ objects have their time set to 0:00.
+ def next_week(given_day_in_next_week = Date.beginning_of_week)
+ first_hour{ weeks_since(1).beginning_of_week.days_since(days_span(given_day_in_next_week)) }
+ end
+
+ # Short-hand for months_since(1).
+ def next_month
+ months_since(1)
+ end
+
+ # Short-hand for months_since(3)
+ def next_quarter
+ months_since(3)
+ end
+
+ # Short-hand for years_since(1).
+ def next_year
+ years_since(1)
+ end
+
+ # Returns a new date/time representing the given day in the previous week.
+ # Week is assumed to start on +start_day+, default is
+ # +Date.beginning_of_week+ or +config.beginning_of_week+ when set.
+ # DateTime objects have their time set to 0:00.
+ def prev_week(start_day = Date.beginning_of_week)
+ first_hour{ weeks_ago(1).beginning_of_week.days_since(days_span(start_day)) }
+ end
+ alias_method :last_week, :prev_week
+
+ # Short-hand for months_ago(1).
+ def prev_month
+ months_ago(1)
+ end
+ alias_method :last_month, :prev_month
+
+ # Short-hand for months_ago(3).
+ def prev_quarter
+ months_ago(3)
+ end
+ alias_method :last_quarter, :prev_quarter
+
+ # Short-hand for years_ago(1).
+ def prev_year
+ years_ago(1)
+ end
+ alias_method :last_year, :prev_year
+
+ # Returns the number of days to the start of the week on the given day.
+ # Week is assumed to start on +start_day+, default is
+ # +Date.beginning_of_week+ or +config.beginning_of_week+ when set.
+ def days_to_week_start(start_day = Date.beginning_of_week)
+ start_day_number = DAYS_INTO_WEEK[start_day]
+ current_day_number = wday != 0 ? wday - 1 : 6
+ (current_day_number - start_day_number) % 7
+ end
+
+ # Returns a new date/time representing the start of this week on the given day.
+ # Week is assumed to start on +start_day+, default is
+ # +Date.beginning_of_week+ or +config.beginning_of_week+ when set.
+ # +DateTime+ objects have their time set to 0:00.
+ def beginning_of_week(start_day = Date.beginning_of_week)
+ result = days_ago(days_to_week_start(start_day))
+ acts_like?(:time) ? result.midnight : result
+ end
+ alias :at_beginning_of_week :beginning_of_week
+
+ # Returns Monday of this week assuming that week starts on Monday.
+ # +DateTime+ objects have their time set to 0:00.
+ def monday
+ beginning_of_week(:monday)
+ end
+
+ # Returns a new date/time representing the end of this week on the given day.
+ # Week is assumed to start on +start_day+, default is
+ # +Date.beginning_of_week+ or +config.beginning_of_week+ when set.
+ # DateTime objects have their time set to 23:59:59.
+ def end_of_week(start_day = Date.beginning_of_week)
+ last_hour{ days_since(6 - days_to_week_start(start_day)) }
+ end
+ alias :at_end_of_week :end_of_week
+
+ # Returns Sunday of this week assuming that week starts on Monday.
+ # +DateTime+ objects have their time set to 23:59:59.
+ def sunday
+ end_of_week(:monday)
+ end
+
+ # Returns a new date/time representing the end of the month.
+ # DateTime objects will have a time set to 23:59:59.
+ def end_of_month
+ last_day = ::Time.days_in_month(month, year)
+ last_hour{ days_since(last_day - day) }
+ end
+ alias :at_end_of_month :end_of_month
+
+ # Returns a new date/time representing the end of the year.
+ # DateTime objects will have a time set to 23:59:59.
+ def end_of_year
+ change(:month => 12).end_of_month
+ end
+ alias :at_end_of_year :end_of_year
+
+ private
+
+ def first_hour
+ result = yield
+ acts_like?(:time) ? result.change(:hour => 0) : result
+ end
+
+ def last_hour
+ result = yield
+ acts_like?(:time) ? result.end_of_day : result
+ end
+
+ def days_span(day)
+ (DAYS_INTO_WEEK[day] - DAYS_INTO_WEEK[Date.beginning_of_week]) % 7
+ end
+ end
+end
diff --git a/lib/active_support/core_ext/date_time.rb b/lib/active_support/core_ext/date_time.rb
new file mode 100644
index 0000000..e8a27b9
--- /dev/null
+++ b/lib/active_support/core_ext/date_time.rb
@@ -0,0 +1,4 @@
+require 'active_support/core_ext/date_time/acts_like'
+require 'active_support/core_ext/date_time/calculations'
+require 'active_support/core_ext/date_time/conversions'
+require 'active_support/core_ext/date_time/zones'
diff --git a/lib/active_support/core_ext/date_time/acts_like.rb b/lib/active_support/core_ext/date_time/acts_like.rb
index c79745c..8fbbe0d 100644
--- a/lib/active_support/core_ext/date_time/acts_like.rb
+++ b/lib/active_support/core_ext/date_time/acts_like.rb
@@ -1,3 +1,4 @@
+require 'date'
require 'active_support/core_ext/object/acts_like'
class DateTime
diff --git a/lib/active_support/core_ext/date_time/calculations.rb b/lib/active_support/core_ext/date_time/calculations.rb
index 0481bd2..3e22f19 100644
--- a/lib/active_support/core_ext/date_time/calculations.rb
+++ b/lib/active_support/core_ext/date_time/calculations.rb
@@ -1,46 +1,67 @@
-require 'rational' unless RUBY_VERSION >= '1.9.2'
+require 'active_support/deprecation'
class DateTime
class << self
- # DateTimes aren't aware of DST rules, so use a consistent non-DST offset when creating a DateTime with an offset in the local zone
+ # *DEPRECATED*: Use +DateTime.civil_from_format+ directly.
def local_offset
+ ActiveSupport::Deprecation.warn 'DateTime.local_offset is deprecated. Use DateTime.civil_from_format directly.'
+
::Time.local(2012).utc_offset.to_r / 86400
end
- # Returns <tt>Time.zone.now.to_datetime</tt> when <tt>Time.zone</tt> or <tt>config.time_zone</tt> are set, otherwise returns <tt>Time.now.to_datetime</tt>.
+ # Returns <tt>Time.zone.now.to_datetime</tt> when <tt>Time.zone</tt> or
+ # <tt>config.time_zone</tt> are set, otherwise returns
+ # <tt>Time.now.to_datetime</tt>.
def current
::Time.zone ? ::Time.zone.now.to_datetime : ::Time.now.to_datetime
end
end
- # Tells whether the DateTime object's datetime lies in the past
+ # Tells whether the DateTime object's datetime lies in the past.
def past?
self < ::DateTime.current
end
- # Tells whether the DateTime object's datetime lies in the future
+ # Tells whether the DateTime object's datetime lies in the future.
def future?
self > ::DateTime.current
end
- # Seconds since midnight: DateTime.now.seconds_since_midnight
+ # Seconds since midnight: DateTime.now.seconds_since_midnight.
def seconds_since_midnight
sec + (min * 60) + (hour * 3600)
end
- # Returns a new DateTime where one or more of the elements have been changed according to the +options+ parameter. The time options
- # (hour, minute, sec) reset cascadingly, so if only the hour is passed, then minute and sec is set to 0. If the hour and
- # minute is passed, then sec is set to 0.
+ # Returns the number of seconds until 23:59:59.
+ #
+ # DateTime.new(2012, 8, 29, 0, 0, 0).seconds_until_end_of_day # => 86399
+ # DateTime.new(2012, 8, 29, 12, 34, 56).seconds_until_end_of_day # => 41103
+ # DateTime.new(2012, 8, 29, 23, 59, 59).seconds_until_end_of_day # => 0
+ def seconds_until_end_of_day
+ end_of_day.to_i - to_i
+ end
+
+ # Returns a new DateTime where one or more of the elements have been changed
+ # according to the +options+ parameter. The time options (<tt>:hour</tt>,
+ # <tt>:min</tt>, <tt>:sec</tt>) reset cascadingly, so if only the hour is
+ # passed, then minute and sec is set to 0. If the hour and minute is passed,
+ # then sec is set to 0. The +options+ parameter takes a hash with any of these
+ # keys: <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>, <tt>:hour</tt>,
+ # <tt>:min</tt>, <tt>:sec</tt>, <tt>:offset</tt>, <tt>:start</tt>.
+ #
+ # DateTime.new(2012, 8, 29, 22, 35, 0).change(day: 1) # => DateTime.new(2012, 8, 1, 22, 35, 0)
+ # DateTime.new(2012, 8, 29, 22, 35, 0).change(year: 1981, day: 1) # => DateTime.new(1981, 8, 1, 22, 35, 0)
+ # DateTime.new(2012, 8, 29, 22, 35, 0).change(year: 1981, hour: 0) # => DateTime.new(1981, 8, 29, 0, 0, 0)
def change(options)
::DateTime.civil(
- options[:year] || year,
- options[:month] || month,
- options[:day] || day,
- options[:hour] || hour,
- options[:min] || (options[:hour] ? 0 : min),
- options[:sec] || ((options[:hour] || options[:min]) ? 0 : sec),
- options[:offset] || offset,
- options[:start] || start
+ options.fetch(:year, year),
+ options.fetch(:month, month),
+ options.fetch(:day, day),
+ options.fetch(:hour, hour),
+ options.fetch(:min, options[:hour] ? 0 : min),
+ options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec + sec_fraction),
+ options.fetch(:offset, offset),
+ options.fetch(:start, start)
)
end
@@ -51,24 +72,33 @@ class DateTime
def advance(options)
d = to_date.advance(options)
datetime_advanced_by_date = change(:year => d.year, :month => d.month, :day => d.day)
- seconds_to_advance = (options[:seconds] || 0) + (options[:minutes] || 0) * 60 + (options[:hours] || 0) * 3600
- seconds_to_advance == 0 ? datetime_advanced_by_date : datetime_advanced_by_date.since(seconds_to_advance)
+ seconds_to_advance = \
+ options.fetch(:seconds, 0) +
+ options.fetch(:minutes, 0) * 60 +
+ options.fetch(:hours, 0) * 3600
+
+ if seconds_to_advance.zero?
+ datetime_advanced_by_date
+ else
+ datetime_advanced_by_date.since seconds_to_advance
+ end
end
- # Returns a new DateTime representing the time a number of seconds ago
+ # Returns a new DateTime representing the time a number of seconds ago.
# Do not use this method in combination with x.months, use months_ago instead!
def ago(seconds)
since(-seconds)
end
- # Returns a new DateTime representing the time a number of seconds since the instance time
- # Do not use this method in combination with x.months, use months_since instead!
+ # Returns a new DateTime representing the time a number of seconds since the
+ # instance time. Do not use this method in combination with x.months, use
+ # months_since instead!
def since(seconds)
self + Rational(seconds.round, 86400)
end
alias :in :since
- # Returns a new DateTime representing the start of the day (0:00)
+ # Returns a new DateTime representing the start of the day (0:00).
def beginning_of_day
change(:hour => 0)
end
@@ -76,68 +106,59 @@ class DateTime
alias :at_midnight :beginning_of_day
alias :at_beginning_of_day :beginning_of_day
- # Returns a new DateTime representing the end of the day (23:59:59)
+ # Returns a new DateTime representing the end of the day (23:59:59).
def end_of_day
change(:hour => 23, :min => 59, :sec => 59)
end
+ alias :at_end_of_day :end_of_day
- # Returns a new DateTime representing the start of the hour (hh:00:00)
+ # Returns a new DateTime representing the start of the hour (hh:00:00).
def beginning_of_hour
change(:min => 0)
end
alias :at_beginning_of_hour :beginning_of_hour
- # Returns a new DateTime representing the end of the hour (hh:59:59)
+ # Returns a new DateTime representing the end of the hour (hh:59:59).
def end_of_hour
change(:min => 59, :sec => 59)
end
+ alias :at_end_of_hour :end_of_hour
- # 1.9.3 defines + and - on DateTime, < 1.9.3 do not.
- if DateTime.public_instance_methods(false).include?(:+)
- def plus_with_duration(other) #:nodoc:
- if ActiveSupport::Duration === other
- other.since(self)
- else
- plus_without_duration(other)
- end
- end
- alias_method :plus_without_duration, :+
- alias_method :+, :plus_with_duration
-
- def minus_with_duration(other) #:nodoc:
- if ActiveSupport::Duration === other
- plus_with_duration(-other)
- else
- minus_without_duration(other)
- end
- end
- alias_method :minus_without_duration, :-
- alias_method :-, :minus_with_duration
+ # Returns a new DateTime representing the start of the minute (hh:mm:00).
+ def beginning_of_minute
+ change(:sec => 0)
end
+ alias :at_beginning_of_minute :beginning_of_minute
- # Adjusts DateTime to UTC by adding its offset value; offset is set to 0
- #
- # Example:
+ # Returns a new DateTime representing the end of the minute (hh:mm:59).
+ def end_of_minute
+ change(:sec => 59)
+ end
+ alias :at_end_of_minute :end_of_minute
+
+ # Adjusts DateTime to UTC by adding its offset value; offset is set to 0.
#
- # DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)) # => Mon, 21 Feb 2005 10:11:12 -0600
- # DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)).utc # => Mon, 21 Feb 2005 16:11:12 +0000
+ # DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)) # => Mon, 21 Feb 2005 10:11:12 -0600
+ # DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)).utc # => Mon, 21 Feb 2005 16:11:12 +0000
def utc
new_offset(0)
end
alias_method :getutc, :utc
- # Returns true if offset == 0
+ # Returns +true+ if <tt>offset == 0</tt>.
def utc?
offset == 0
end
- # Returns the offset value in seconds
+ # Returns the offset value in seconds.
def utc_offset
(offset * 86400).to_i
end
- # Layers additional behavior on DateTime#<=> so that Time and ActiveSupport::TimeWithZone instances can be compared with a DateTime
+ # Layers additional behavior on DateTime#<=> so that Time and
+ # ActiveSupport::TimeWithZone instances can be compared with a DateTime.
def <=>(other)
- super other.kind_of?(Infinity) ? other : other.to_datetime
+ super other.to_datetime
end
+
end
diff --git a/lib/active_support/core_ext/date_time/conversions.rb b/lib/active_support/core_ext/date_time/conversions.rb
index ca899c7..ccc27b4 100644
--- a/lib/active_support/core_ext/date_time/conversions.rb
+++ b/lib/active_support/core_ext/date_time/conversions.rb
@@ -4,10 +4,6 @@ require 'active_support/core_ext/date_time/calculations'
require 'active_support/values/time_zone'
class DateTime
- # Ruby 1.9 has DateTime#to_time which internally relies on Time. We define our own #to_time which allows
- # DateTimes outside the range of what can be created with Time.
- remove_method :to_time if instance_methods.include?(:to_time)
-
# Convert to a formatted string. See Time::DATE_FORMATS for predefined formats.
#
# This method is aliased to <tt>to_s</tt>.
@@ -30,7 +26,7 @@ class DateTime
# datetime argument as the value.
#
# # config/initializers/time_formats.rb
- # Time::DATE_FORMATS[:month_and_year] = "%B %Y"
+ # Time::DATE_FORMATS[:month_and_year] = '%B %Y'
# Time::DATE_FORMATS[:short_ordinal] = lambda { |time| time.strftime("%B #{time.day.ordinalize}") }
def to_formatted_s(format = :default)
if formatter = ::Time::DATE_FORMATS[format]
@@ -39,10 +35,9 @@ class DateTime
to_default_s
end
end
- alias_method :to_default_s, :to_s unless (instance_methods(false) & [:to_s, 'to_s']).empty?
+ alias_method :to_default_s, :to_s if instance_methods(false).include?(:to_s)
alias_method :to_s, :to_formatted_s
- # Returns the +utc_offset+ as an +HH:MM formatted string. Examples:
#
# datetime = DateTime.civil(2000, 1, 1, 0, 0, 0, Rational(-6, 24))
# datetime.formatted_offset # => "-06:00"
@@ -58,46 +53,49 @@ class DateTime
alias_method :default_inspect, :inspect
alias_method :inspect, :readable_inspect
- # Converts self to a Ruby Date object; time portion is discarded.
- def to_date
- ::Date.new(year, month, day)
- end unless instance_methods(false).include?(:to_date)
-
- # Attempts to convert self to a Ruby Time object; returns self if out of range of Ruby Time class.
- # If self has an offset other than 0, self will just be returned unaltered, since there's no clean way to map it to a Time.
- def to_time
- self.offset == 0 ? ::Time.utc_time(year, month, day, hour, min, sec, sec_fraction * (RUBY_VERSION < '1.9' ? 86400000000 : 1000000)) : self
- end
-
- # To be able to keep Times, Dates and DateTimes interchangeable on conversions.
- def to_datetime
- self
- end unless instance_methods(false).include?(:to_datetime)
-
+ # Returns DateTime with local offset for given year if format is local else
+ # offset is zero.
+ #
+ # DateTime.civil_from_format :local, 2012
+ # # => Sun, 01 Jan 2012 00:00:00 +0300
+ # DateTime.civil_from_format :local, 2012, 12, 17
+ # # => Mon, 17 Dec 2012 00:00:00 +0000
def self.civil_from_format(utc_or_local, year, month=1, day=1, hour=0, min=0, sec=0)
- offset = utc_or_local.to_sym == :local ? local_offset : 0
+ if utc_or_local.to_sym == :local
+ offset = ::Time.local(year, month, day).utc_offset.to_r / 86400
+ else
+ offset = 0
+ end
civil(year, month, day, hour, min, sec, offset)
end
- # Converts datetime to an appropriate format for use in XML.
- def xmlschema
- strftime("%Y-%m-%dT%H:%M:%S%Z")
- end unless instance_methods(false).include?(:xmlschema)
-
- # Converts self to a floating-point number of seconds since the Unix epoch.
+ # Converts +self+ to a floating-point number of seconds since the Unix epoch.
def to_f
seconds_since_unix_epoch.to_f
end
- # Converts self to an integer number of seconds since the Unix epoch.
+ # Converts +self+ to an integer number of seconds since the Unix epoch.
def to_i
seconds_since_unix_epoch.to_i
end
+ # Returns the fraction of a second as microseconds
+ def usec
+ (sec_fraction * 1_000_000).to_i
+ end
+
+ # Returns the fraction of a second as nanoseconds
+ def nsec
+ (sec_fraction * 1_000_000_000).to_i
+ end
+
private
+ def offset_in_seconds
+ (offset * 86400).to_i
+ end
+
def seconds_since_unix_epoch
- seconds_per_day = 86_400
- (self - ::DateTime.civil(1970)) * seconds_per_day
+ (jd - 2440588) * 86400 - offset_in_seconds + seconds_since_midnight
end
end
diff --git a/lib/active_support/core_ext/date_time/zones.rb b/lib/active_support/core_ext/date_time/zones.rb
index 6fa55a9..6457ffb 100644
--- a/lib/active_support/core_ext/date_time/zones.rb
+++ b/lib/active_support/core_ext/date_time/zones.rb
@@ -6,16 +6,19 @@ class DateTime
# Time.zone = 'Hawaii' # => 'Hawaii'
# DateTime.new(2000).in_time_zone # => Fri, 31 Dec 1999 14:00:00 HST -10:00
#
- # This method is similar to Time#localtime, except that it uses <tt>Time.zone</tt> as the local zone
- # instead of the operating system's time zone.
+ # This method is similar to Time#localtime, except that it uses <tt>Time.zone</tt>
+ # as the local zone instead of the operating system's time zone.
#
- # You can also pass in a TimeZone instance or string that identifies a TimeZone as an argument,
- # and the conversion will be based on that zone instead of <tt>Time.zone</tt>.
+ # You can also pass in a TimeZone instance or string that identifies a TimeZone
+ # as an argument, and the conversion will be based on that zone instead of
+ # <tt>Time.zone</tt>.
#
- # DateTime.new(2000).in_time_zone('Alaska') # => Fri, 31 Dec 1999 15:00:00 AKST -09:00
+ # DateTime.new(2000).in_time_zone('Alaska') # => Fri, 31 Dec 1999 15:00:00 AKST -09:00
def in_time_zone(zone = ::Time.zone)
- return self unless zone
-
- ActiveSupport::TimeWithZone.new(utc? ? self : getutc, ::Time.find_zone!(zone))
+ if zone
+ ActiveSupport::TimeWithZone.new(utc? ? self : getutc, ::Time.find_zone!(zone))
+ else
+ self
+ end
end
end
diff --git a/lib/active_support/core_ext/enumerable.rb b/lib/active_support/core_ext/enumerable.rb
index ae8de94..4501b7f 100644
--- a/lib/active_support/core_ext/enumerable.rb
+++ b/lib/active_support/core_ext/enumerable.rb
@@ -1,42 +1,5 @@
-require 'active_support/ordered_hash'
-
module Enumerable
- # Ruby 1.8.7 introduces group_by, but the result isn't ordered. Override it.
- remove_method(:group_by) if [].respond_to?(:group_by) && RUBY_VERSION < '1.9'
-
- # Collect an enumerable into sets, grouped by the result of a block. Useful,
- # for example, for grouping records by date.
- #
- # Example:
- #
- # latest_transcripts.group_by(&:day).each do |day, transcripts|
- # p "#{day} -> #{transcripts.map(&:class).join(', ')}"
- # end
- # "2006-03-01 -> Transcript"
- # "2006-02-28 -> Transcript"
- # "2006-02-27 -> Transcript, Transcript"
- # "2006-02-26 -> Transcript, Transcript"
- # "2006-02-25 -> Transcript"
- # "2006-02-24 -> Transcript, Transcript"
- # "2006-02-23 -> Transcript"
- def group_by
- return to_enum :group_by unless block_given?
- assoc = ActiveSupport::OrderedHash.new
-
- each do |element|
- key = yield(element)
-
- if assoc.has_key?(key)
- assoc[key] << element
- else
- assoc[key] = [element]
- end
- end
-
- assoc
- end unless [].respond_to?(:group_by)
-
- # Calculates a sum from the elements. Examples:
+ # Calculates a sum from the elements.
#
# payments.sum { |p| p.price * p.tax_rate }
# payments.sum(&:price)
@@ -48,56 +11,38 @@ module Enumerable
# It can also calculate the sum without the use of a block.
#
# [5, 15, 10].sum # => 30
- # ["foo", "bar"].sum # => "foobar"
+ # ['foo', 'bar'].sum # => "foobar"
# [[1, 2], [3, 1, 5]].sum => [1, 2, 3, 1, 5]
#
# The default sum of an empty list is zero. You can override this default:
#
# [].sum(Payment.new(0)) { |i| i.amount } # => Payment.new(0)
- #
def sum(identity = 0, &block)
if block_given?
map(&block).sum(identity)
else
- inject(:+) || identity
+ inject { |sum, element| sum + element } || identity
end
end
- # Iterates over a collection, passing the current element *and* the
- # +memo+ to the block. Handy for building up hashes or
- # reducing collections down to one object. Examples:
- #
- # %w(foo bar).each_with_object({}) { |str, hsh| hsh[str] = str.upcase }
- # # => {'foo' => 'FOO', 'bar' => 'BAR'}
- #
- # *Note* that you can't use immutable objects like numbers, true or false as
- # the memo. You would think the following returns 120, but since the memo is
- # never changed, it does not.
- #
- # (1..5).each_with_object(1) { |value, memo| memo *= value } # => 1
- #
- def each_with_object(memo)
- return to_enum :each_with_object, memo unless block_given?
- each do |element|
- yield element, memo
- end
- memo
- end unless [].respond_to?(:each_with_object)
-
- # Convert an enumerable to a hash. Examples:
+ # Convert an enumerable to a hash.
#
# people.index_by(&:login)
# => { "nextangle" => <Person ...>, "chade-" => <Person ...>, ...}
# people.index_by { |person| "#{person.first_name} #{person.last_name}" }
# => { "Chade- Fowlersburg-e" => <Person ...>, "David Heinemeier Hansson" => <Person ...>, ...}
- #
def index_by
- return to_enum :index_by unless block_given?
- Hash[map { |elem| [yield(elem), elem] }]
+ if block_given?
+ Hash[map { |elem| [yield(elem), elem] }]
+ else
+ to_enum :index_by
+ end
end
- # Returns true if the enumerable has more than 1 element. Functionally equivalent to enum.to_a.size > 1.
- # Can be called with a block too, much like any?, so <tt>people.many? { |p| p.age > 26 }</tt> returns true if more than one person is over 26.
+ # Returns +true+ if the enumerable has more than 1 element. Functionally
+ # equivalent to <tt>enum.to_a.size > 1</tt>. Can be called with a block too,
+ # much like any?, so <tt>people.many? { |p| p.age > 26 }</tt> returns +true+
+ # if more than one person is over 26.
def many?
cnt = 0
if block_given?
@@ -106,11 +51,12 @@ module Enumerable
cnt > 1
end
else
- any?{ (cnt += 1) > 1 }
+ any? { (cnt += 1) > 1 }
end
end
- # The negative of the <tt>Enumerable#include?</tt>. Returns true if the collection does not include the object.
+ # The negative of the <tt>Enumerable#include?</tt>. Returns +true+ if the
+ # collection does not include the object.
def exclude?(object)
!include?(object)
end
@@ -120,8 +66,15 @@ class Range #:nodoc:
# Optimize range sum to use arithmetic progression if a block is not given and
# we have a range of numeric values.
def sum(identity = 0)
- return super if block_given? || !(first.instance_of?(Integer) && last.instance_of?(Integer))
- actual_last = exclude_end? ? (last - 1) : last
- (actual_last - first + 1) * (actual_last + first) / 2
+ if block_given? || !(first.is_a?(Integer) && last.is_a?(Integer))
+ super
+ else
+ actual_last = exclude_end? ? (last - 1) : last
+ if actual_last >= first
+ (actual_last - first + 1) * (actual_last + first) / 2
+ else
+ identity
+ end
+ end
end
end
diff --git a/lib/active_support/core_ext/exception.rb b/lib/active_support/core_ext/exception.rb
deleted file mode 100644
index ef801e7..0000000
--- a/lib/active_support/core_ext/exception.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-module ActiveSupport
- FrozenObjectError = RUBY_VERSION < '1.9' ? TypeError : RuntimeError
-end
diff --git a/lib/active_support/core_ext/file.rb b/lib/active_support/core_ext/file.rb
index a763447..dc24afb 100644
--- a/lib/active_support/core_ext/file.rb
+++ b/lib/active_support/core_ext/file.rb
@@ -1,2 +1 @@
require 'active_support/core_ext/file/atomic'
-require 'active_support/core_ext/file/path'
diff --git a/lib/active_support/core_ext/file/atomic.rb b/lib/active_support/core_ext/file/atomic.rb
index b0daf6d..c3e6124 100644
--- a/lib/active_support/core_ext/file/atomic.rb
+++ b/lib/active_support/core_ext/file/atomic.rb
@@ -1,16 +1,18 @@
+require 'fileutils'
+
class File
# Write to a file atomically. Useful for situations where you don't
# want other processes or threads to see half-written files.
#
- # File.atomic_write("important.file") do |file|
- # file.write("hello")
+ # File.atomic_write('important.file') do |file|
+ # file.write('hello')
# end
#
# If your temp directory is not on the same filesystem as the file you're
# trying to write, you can provide a different temporary directory.
#
- # File.atomic_write("/data/something.important", "/data/tmp") do |file|
- # file.write("hello")
+ # File.atomic_write('/data/something.important', '/data/tmp') do |file|
+ # file.write('hello')
# end
def self.atomic_write(file_name, temp_dir = Dir.tmpdir)
require 'tempfile' unless defined?(Tempfile)
@@ -21,15 +23,13 @@ class File
yield temp_file
temp_file.close
- begin
+ if File.exists?(file_name)
# Get original file permissions
old_stat = stat(file_name)
- rescue Errno::ENOENT
- # No old permissions, write a temp file to determine the defaults
- check_name = join(dirname(file_name), ".permissions_check.#{Thread.current.object_id}.#{Process.pid}.#{rand(1000000)}")
- open(check_name, "w") { }
- old_stat = stat(check_name)
- unlink(check_name)
+ else
+ # If not possible, probe which are the default permissions in the
+ # destination directory.
+ old_stat = probe_stat_in(dirname(file_name))
end
# Overwrite original file with temp file
@@ -44,4 +44,20 @@ class File
# Changing file ownership failed, moving on.
end
end
+
+ # Private utility method.
+ def self.probe_stat_in(dir) #:nodoc:
+ basename = [
+ '.permissions_check',
+ Thread.current.object_id,
+ Process.pid,
+ rand(1000000)
+ ].join('.')
+
+ file_name = join(dir, basename)
+ FileUtils.touch(file_name)
+ stat(file_name)
+ ensure
+ FileUtils.rm_f(file_name) if file_name
+ end
end
diff --git a/lib/active_support/core_ext/file/path.rb b/lib/active_support/core_ext/file/path.rb
deleted file mode 100644
index b5feab8..0000000
--- a/lib/active_support/core_ext/file/path.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-class File
- unless File.allocate.respond_to?(:to_path)
- alias to_path path
- end
-end
\ No newline at end of file
diff --git a/lib/active_support/core_ext/float.rb b/lib/active_support/core_ext/float.rb
deleted file mode 100644
index 7570471..0000000
--- a/lib/active_support/core_ext/float.rb
+++ /dev/null
@@ -1 +0,0 @@
-require 'active_support/core_ext/float/rounding'
diff --git a/lib/active_support/core_ext/float/rounding.rb b/lib/active_support/core_ext/float/rounding.rb
deleted file mode 100644
index 0d4fb87..0000000
--- a/lib/active_support/core_ext/float/rounding.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-class Float
- alias precisionless_round round
- private :precisionless_round
-
- # Rounds the float with the specified precision.
- #
- # x = 1.337
- # x.round # => 1
- # x.round(1) # => 1.3
- # x.round(2) # => 1.34
- def round(precision = nil)
- if precision
- magnitude = 10.0 ** precision
- (self * magnitude).round / magnitude
- else
- precisionless_round
- end
- end
-end if RUBY_VERSION < '1.9'
diff --git a/lib/active_support/core_ext/hash.rb b/lib/active_support/core_ext/hash.rb
index fd1cda9..5014834 100644
--- a/lib/active_support/core_ext/hash.rb
+++ b/lib/active_support/core_ext/hash.rb
@@ -1,6 +1,5 @@
require 'active_support/core_ext/hash/conversions'
require 'active_support/core_ext/hash/deep_merge'
-require 'active_support/core_ext/hash/deep_dup'
require 'active_support/core_ext/hash/diff'
require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/hash/indifferent_access'
diff --git a/lib/active_support/core_ext/hash/conversions.rb b/lib/active_support/core_ext/hash/conversions.rb
index b820a16..8930376 100644
--- a/lib/active_support/core_ext/hash/conversions.rb
+++ b/lib/active_support/core_ext/hash/conversions.rb
@@ -1,14 +1,16 @@
require 'active_support/xml_mini'
require 'active_support/time'
+require 'active_support/core_ext/object/blank'
+require 'active_support/core_ext/object/to_param'
+require 'active_support/core_ext/object/to_query'
require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/hash/reverse_merge'
-require 'active_support/core_ext/object/blank'
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>
@@ -26,21 +28,21 @@ class Hash
#
# * If +value+ is a callable object it must expect one or two arguments. Depending
# on the arity, the callable is invoked with the +options+ hash as first argument
- # with +key+ as <tt>:root</tt>, and +key+ singularized as second argument. The
+ # with +key+ as <tt>:root</tt>, and +key+ singularized as second argument. The
# callable can add nodes by using <tt>options[:builder]</tt>.
#
- # "foo".to_xml(lambda { |options, key| options[:builder].b(key) })
+ # 'foo'.to_xml(lambda { |options, key| options[:builder].b(key) })
# # => "<b>foo</b>"
#
# * If +value+ responds to +to_xml+ the method is invoked with +key+ as <tt>:root</tt>.
- #
+ #
# class Foo
# def to_xml(options)
- # options[:builder].bar "fooing!"
+ # options[:builder].bar 'fooing!'
# end
# end
- #
- # {:foo => Foo.new}.to_xml(:skip_instruct => true)
+ #
+ # { foo: Foo.new }.to_xml(skip_instruct: true)
# # => "<hash><bar>fooing!</bar></hash>"
#
# * Otherwise, a node with +key+ as tag is created with a string representation of
@@ -57,8 +59,8 @@ class Hash
# "TrueClass" => "boolean",
# "FalseClass" => "boolean",
# "Date" => "date",
- # "DateTime" => "datetime",
- # "Time" => "datetime"
+ # "DateTime" => "dateTime",
+ # "Time" => "dateTime"
# }
#
# By default the root node is "hash", but that's configurable via the <tt>:root</tt> option.
@@ -71,105 +73,169 @@ class Hash
options = options.dup
options[:indent] ||= 2
- options[:root] ||= "hash"
- options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
+ options[:root] ||= 'hash'
+ options[:builder] ||= Builder::XmlMarkup.new(indent: options[:indent])
builder = options[:builder]
builder.instruct! unless options.delete(:skip_instruct)
root = ActiveSupport::XmlMini.rename_key(options[:root].to_s, options)
- builder.__send__(:method_missing, root) do
+ builder.tag!(root) do
each { |key, value| ActiveSupport::XmlMini.to_tag(key, value, options) }
yield builder if block_given?
end
end
- class DisallowedType < StandardError #:nodoc:
- def initialize(type)
- super "Disallowed type attribute: #{type.inspect}"
- end
- end
-
- DISALLOWED_XML_TYPES = %w(symbol yaml)
-
class << self
+ # Returns a Hash containing a collection of pairs when the key is the node name and the value is
+ # its content
+ #
+ # xml = <<-XML
+ # <?xml version="1.0" encoding="UTF-8"?>
+ # <hash>
+ # <foo type="integer">1</foo>
+ # <bar type="integer">2</bar>
+ # </hash>
+ # XML
+ #
+ # hash = Hash.from_xml(xml)
+ # # => {"hash"=>{"foo"=>1, "bar"=>2}}
+ #
+ # DisallowedType is raise 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)
- typecast_xml_value(unrename_keys(ActiveSupport::XmlMini.parse(xml)), disallowed_types)
+ ActiveSupport::XMLConverter.new(xml, disallowed_types).to_h
end
+ # Builds a Hash from XML just like <tt>Hash.from_xml</tt>, but also allows Symbol and YAML.
def from_trusted_xml(xml)
from_xml xml, []
end
+ end
+end
- private
- def typecast_xml_value(value, disallowed_types = nil)
- disallowed_types ||= DISALLOWED_XML_TYPES
+module ActiveSupport
+ class XMLConverter # :nodoc:
+ class DisallowedType < StandardError
+ def initialize(type)
+ super "Disallowed type attribute: #{type.inspect}"
+ end
+ end
- case value.class.to_s
- when 'Hash'
- if value.include?('type') && !value['type'].is_a?(Hash) && disallowed_types.include?(value['type'])
- raise DisallowedType, value['type']
- end
+ DISALLOWED_TYPES = %w(symbol yaml)
- if value['type'] == 'array'
- _, entries = Array.wrap(value.detect { |k,v| not v.is_a?(String) })
- if entries.nil? || (c = value['__content__'] && c.blank?)
- []
- else
- case entries.class.to_s # something weird with classes not matching here. maybe singleton methods breaking is_a?
- when "Array"
- entries.collect { |v| typecast_xml_value(v, disallowed_types) }
- when "Hash"
- [typecast_xml_value(entries, disallowed_types)]
- else
- raise "can't typecast #{entries.inspect}"
- end
- end
- elsif value['type'] == 'file' ||
- (value["__content__"] && (value.keys.size == 1 || value["__content__"].present?))
- content = value["__content__"]
- if parser = ActiveSupport::XmlMini::PARSING[value["type"]]
- parser.arity == 1 ? parser.call(content) : parser.call(content, value)
- else
- content
- end
- elsif value['type'] == 'string' && value['nil'] != 'true'
- ""
- # blank or nil parsed values are represented by nil
- elsif value.blank? || value['nil'] == 'true'
- nil
- # If the type is the only element which makes it then
- # this still makes the value nil, except if type is
- # a XML node(where type['value'] is a Hash)
- elsif value['type'] && value.size == 1 && !value['type'].is_a?(::Hash)
- nil
- else
- xml_value = Hash[value.map { |k,v| [k, typecast_xml_value(v, disallowed_types)] }]
+ def initialize(xml, disallowed_types = nil)
+ @xml = normalize_keys(XmlMini.parse(xml))
+ @disallowed_types = disallowed_types || DISALLOWED_TYPES
+ end
- # Turn { :files => { :file => #<StringIO> } into { :files => #<StringIO> } so it is compatible with
- # how multipart uploaded files from HTML appear
- xml_value["file"].is_a?(StringIO) ? xml_value["file"] : xml_value
- end
- when 'Array'
- value.map! { |i| typecast_xml_value(i, disallowed_types) }
- value.length > 1 ? value : value.first
- when 'String'
+ def to_h
+ deep_to_h(@xml)
+ end
+
+ private
+ def normalize_keys(params)
+ case params
+ when Hash
+ Hash[params.map { |k,v| [k.to_s.tr('-', '_'), normalize_keys(v)] } ]
+ when Array
+ params.map { |v| normalize_keys(v) }
+ else
+ params
+ end
+ end
+
+ def deep_to_h(value)
+ case value
+ when Hash
+ process_hash(value)
+ when Array
+ process_array(value)
+ when String
value
else
raise "can't typecast #{value.class.name} - #{value.inspect}"
end
end
- def unrename_keys(params)
- case params.class.to_s
- when "Hash"
- Hash[params.map { |k,v| [k.to_s.tr("-", "_"), unrename_keys(v)] } ]
- when "Array"
- params.map { |v| unrename_keys(v) }
+ def process_hash(value)
+ if value.include?('type') && !value['type'].is_a?(Hash) && @disallowed_types.include?(value['type'])
+ raise DisallowedType, value['type']
+ end
+
+ if become_array?(value)
+ _, entries = Array.wrap(value.detect { |k,v| not v.is_a?(String) })
+ if entries.nil? || value['__content__'].try(:empty?)
+ []
else
- params
+ case entries
+ when Array
+ entries.collect { |v| deep_to_h(v) }
+ when Hash
+ [deep_to_h(entries)]
+ else
+ raise "can't typecast #{entries.inspect}"
+ end
+ end
+ elsif become_content?(value)
+ process_content(value)
+
+ elsif become_empty_string?(value)
+ ''
+ elsif become_hash?(value)
+ xml_value = Hash[value.map { |k,v| [k, deep_to_h(v)] }]
+
+ # Turn { files: { file: #<StringIO> } } into { files: #<StringIO> } so it is compatible with
+ # how multipart uploaded files from HTML appear
+ xml_value['file'].is_a?(StringIO) ? xml_value['file'] : xml_value
end
end
+
+ def become_content?(value)
+ value['type'] == 'file' || (value['__content__'] && (value.keys.size == 1 || value['__content__'].present?))
+ end
+
+ def become_array?(value)
+ value['type'] == 'array'
+ end
+
+ def become_empty_string?(value)
+ # {"string" => true}
+ # No tests fail when the second term is removed.
+ value['type'] == 'string' && value['nil'] != 'true'
+ end
+
+ def become_hash?(value)
+ !nothing?(value) && !garbage?(value)
+ end
+
+ def nothing?(value)
+ # blank or nil parsed values are represented by nil
+ value.blank? || value['nil'] == 'true'
+ end
+
+ def garbage?(value)
+ # If the type is the only element which makes it then
+ # this still makes the value nil, except if type is
+ # a XML node(where type['value'] is a Hash)
+ value['type'] && !value['type'].is_a?(::Hash) && value.size == 1
+ end
+
+ def process_content(value)
+ content = value['__content__']
+ if parser = ActiveSupport::XmlMini::PARSING[value['type']]
+ parser.arity == 1 ? parser.call(content) : parser.call(content, value)
+ else
+ content
+ end
+ end
+
+ def process_array(value)
+ value.map! { |i| deep_to_h(i) }
+ value.length > 1 ? value : value.first
+ end
+
end
end
+
diff --git a/lib/active_support/core_ext/hash/deep_dup.rb b/lib/active_support/core_ext/hash/deep_dup.rb
deleted file mode 100644
index c668678..0000000
--- a/lib/active_support/core_ext/hash/deep_dup.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-class Hash
- # Returns a deep copy of hash.
- #
- # hash = { :a => { :b => 'b' } }
- # dup = hash.deep_dup
- # dup[:a][:c] = 'c'
- #
- # hash[:a][:c] #=> nil
- # dup[:a][:c] #=> "c"
- def deep_dup
- duplicate = self.dup
- duplicate.each_pair do |k,v|
- tv = duplicate[k]
- duplicate[k] = tv.is_a?(Hash) && v.is_a?(Hash) ? tv.deep_dup : v
- end
- duplicate
- end
-end
diff --git a/lib/active_support/core_ext/hash/deep_merge.rb b/lib/active_support/core_ext/hash/deep_merge.rb
index 4f61c38..e07db50 100644
--- a/lib/active_support/core_ext/hash/deep_merge.rb
+++ b/lib/active_support/core_ext/hash/deep_merge.rb
@@ -1,20 +1,26 @@
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] }
- def deep_merge(other_hash)
- dup.deep_merge!(other_hash)
+ # 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"]}
+ def deep_merge(other_hash, &block)
+ dup.deep_merge!(other_hash, &block)
end
# Same as +deep_merge+, but modifies +self+.
- def deep_merge!(other_hash)
+ def deep_merge!(other_hash, &block)
other_hash.each_pair do |k,v|
tv = self[k]
- self[k] = tv.is_a?(Hash) && v.is_a?(Hash) ? tv.deep_merge(v) : v
+ if tv.is_a?(Hash) && v.is_a?(Hash)
+ self[k] = tv.deep_merge(v, &block)
+ else
+ self[k] = block && tv ? block.call(k, tv, v) : v
+ end
end
self
end
diff --git a/lib/active_support/core_ext/hash/diff.rb b/lib/active_support/core_ext/hash/diff.rb
index b904f49..5f3868b 100644
--- a/lib/active_support/core_ext/hash/diff.rb
+++ b/lib/active_support/core_ext/hash/diff.rb
@@ -1,13 +1,14 @@
class Hash
# Returns a hash that represents the difference between two hashes.
#
- # Examples:
- #
# {1 => 2}.diff(1 => 2) # => {}
# {1 => 2}.diff(1 => 3) # => {1 => 2}
# {}.diff(1 => 2) # => {1 => 2}
# {1 => 2, 3 => 4}.diff(1 => 2) # => {3 => 4}
- def diff(h2)
- dup.delete_if { |k, v| h2[k] == v }.merge!(h2.dup.delete_if { |k, v| has_key?(k) })
+ def diff(other)
+ ActiveSupport::Deprecation.warn "Hash#diff is no longer used inside of Rails, and is being deprecated with no replacement. If you're using it to compare hashes for the purpose of testing, please use MiniTest's assert_equal instead."
+ dup.
+ delete_if { |k, v| other[k] == v }.
+ merge!(other.dup.delete_if { |k, v| has_key?(k) })
end
end
diff --git a/lib/active_support/core_ext/hash/except.rb b/lib/active_support/core_ext/hash/except.rb
index 89729df..d90e996 100644
--- a/lib/active_support/core_ext/hash/except.rb
+++ b/lib/active_support/core_ext/hash/except.rb
@@ -2,15 +2,7 @@ class Hash
# Return a hash that includes everything but the given keys. This is useful for
# limiting a set of parameters to everything but a few known toggles:
#
- # @person.update_attributes(params[:person].except(:admin))
- #
- # If the receiver responds to +convert_key+, the method is called on each of the
- # arguments. This allows +except+ to play nice with hashes with indifferent access
- # for instance:
- #
- # {:a => 1}.with_indifferent_access.except(:a) # => {}
- # {:a => 1}.with_indifferent_access.except("a") # => {}
- #
+ # @person.update(params[:person].except(:admin))
def except(*keys)
dup.except!(*keys)
end
diff --git a/lib/active_support/core_ext/hash/indifferent_access.rb b/lib/active_support/core_ext/hash/indifferent_access.rb
index e5042c6..981e843 100644
--- a/lib/active_support/core_ext/hash/indifferent_access.rb
+++ b/lib/active_support/core_ext/hash/indifferent_access.rb
@@ -1,10 +1,10 @@
require 'active_support/hash_with_indifferent_access'
class Hash
+
# Returns an <tt>ActiveSupport::HashWithIndifferentAccess</tt> out of its receiver:
#
- # {:a => 1}.with_indifferent_access["a"] # => 1
- #
+ # { a: 1 }.with_indifferent_access['a'] # => 1
def with_indifferent_access
ActiveSupport::HashWithIndifferentAccess.new_from_hash_copying_default(self)
end
@@ -16,8 +16,7 @@ class Hash
# converting to an <tt>ActiveSupport::HashWithIndifferentAccess</tt> would not be
# desirable.
#
- # b = {:b => 1}
- # {:a => b}.with_indifferent_access["a"] # calls b.nested_under_indifferent_access
- #
+ # b = { b: 1 }
+ # { a: b }.with_indifferent_access['a'] # calls b.nested_under_indifferent_access
alias nested_under_indifferent_access with_indifferent_access
end
diff --git a/lib/active_support/core_ext/hash/keys.rb b/lib/active_support/core_ext/hash/keys.rb
index 6b7f24e..b4c451a 100644
--- a/lib/active_support/core_ext/hash/keys.rb
+++ b/lib/active_support/core_ext/hash/keys.rb
@@ -1,54 +1,138 @@
class Hash
+ # Return 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" }
+ def transform_keys
+ result = {}
+ each_key do |key|
+ result[yield(key)] = self[key]
+ end
+ result
+ end
+
+ # Destructively convert all keys using the block operations.
+ # Same as transform_keys but modifies +self+.
+ def transform_keys!
+ keys.each do |key|
+ self[yield(key)] = delete(key)
+ end
+ self
+ end
+
# Return a new hash with all keys converted to strings.
#
- # { :name => 'Rob', :years => '28' }.stringify_keys
- # #=> { "name" => "Rob", "years" => "28" }
+ # hash = { name: 'Rob', age: '28' }
+ #
+ # hash.stringify_keys
+ # #=> { "name" => "Rob", "age" => "28" }
def stringify_keys
- dup.stringify_keys!
+ transform_keys{ |key| key.to_s }
end
# Destructively convert all keys to strings. Same as
# +stringify_keys+, but modifies +self+.
def stringify_keys!
- keys.each do |key|
- self[key.to_s] = delete(key)
- end
- self
+ transform_keys!{ |key| key.to_s }
end
# Return a new hash with all keys converted to symbols, as long as
# they respond to +to_sym+.
#
- # { 'name' => 'Rob', 'years' => '28' }.symbolize_keys
- # #=> { :name => "Rob", :years => "28" }
+ # hash = { 'name' => 'Rob', 'age' => '28' }
+ #
+ # hash.symbolize_keys
+ # #=> { name: "Rob", age: "28" }
def symbolize_keys
- dup.symbolize_keys!
+ transform_keys{ |key| key.to_sym rescue key }
end
+ alias_method :to_options, :symbolize_keys
# Destructively convert all keys to symbols, as long as they respond
# to +to_sym+. Same as +symbolize_keys+, but modifies +self+.
def symbolize_keys!
- keys.each do |key|
- self[(key.to_sym rescue key) || key] = delete(key)
- end
- self
+ transform_keys!{ |key| key.to_sym rescue key }
end
-
- alias_method :to_options, :symbolize_keys
alias_method :to_options!, :symbolize_keys!
- # Validate all keys in a hash match *valid keys, raising ArgumentError on a mismatch.
- # Note that keys are NOT treated indifferently, meaning if you use strings for keys but assert symbols
- # as keys, this will fail.
+ # Validate all keys in a hash match <tt>*valid_keys</tt>, raising ArgumentError
+ # on a mismatch. Note that keys are NOT treated indifferently, meaning if you
+ # use strings for keys but assert symbols as keys, this will fail.
#
- # ==== Examples
- # { :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", :age => "28" }.assert_valid_keys(:name, :age) # => passes, raises nothing
+ # { 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', 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, "Unknown key: #{k}") unless valid_keys.include?(k)
+ raise ArgumentError.new("Unknown key: #{k}") unless valid_keys.include?(k)
+ end
+ end
+
+ # Return 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" } }
+ def deep_transform_keys(&block)
+ result = {}
+ each do |key, value|
+ result[yield(key)] = value.is_a?(Hash) ? value.deep_transform_keys(&block) : value
+ end
+ result
+ end
+
+ # Destructively convert all keys by using the block operation.
+ # This includes the keys from the root hash and from all
+ # nested hashes.
+ def deep_transform_keys!(&block)
+ keys.each do |key|
+ value = delete(key)
+ self[yield(key)] = value.is_a?(Hash) ? value.deep_transform_keys!(&block) : value
end
+ self
+ end
+
+ # Return 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" } }
+ def deep_stringify_keys
+ deep_transform_keys{ |key| key.to_s }
+ end
+
+ # Destructively convert all keys to strings.
+ # This includes the keys from the root hash and from all
+ # nested hashes.
+ def deep_stringify_keys!
+ deep_transform_keys!{ |key| key.to_s }
+ end
+
+ # Return 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" } }
+ def deep_symbolize_keys
+ deep_transform_keys{ |key| key.to_sym rescue key }
+ end
+
+ # Destructively convert all keys to symbols, as long as they respond
+ # to +to_sym+. This includes the keys from the root hash and from all
+ # nested hashes.
+ def deep_symbolize_keys!
+ deep_transform_keys!{ |key| key.to_sym rescue key }
end
end
diff --git a/lib/active_support/core_ext/hash/reverse_merge.rb b/lib/active_support/core_ext/hash/reverse_merge.rb
index 01863a1..fbb4824 100644
--- a/lib/active_support/core_ext/hash/reverse_merge.rb
+++ b/lib/active_support/core_ext/hash/reverse_merge.rb
@@ -1,11 +1,11 @@
class Hash
# Merges the caller into +other_hash+. For example,
#
- # options = options.reverse_merge(:size => 25, :velocity => 10)
+ # options = options.reverse_merge(size: 25, velocity: 10)
#
# is equivalent to
#
- # options = {:size => 25, :velocity => 10}.merge(options)
+ # options = { size: 25, velocity: 10 }.merge(options)
#
# This is particularly useful for initializing an options hash
# with default values.
@@ -18,6 +18,5 @@ class Hash
# right wins if there is no left
merge!( other_hash ){|key,left,right| left }
end
-
alias_method :reverse_update, :reverse_merge!
end
diff --git a/lib/active_support/core_ext/hash/slice.rb b/lib/active_support/core_ext/hash/slice.rb
index a983cae..9fa9b3d 100644
--- a/lib/active_support/core_ext/hash/slice.rb
+++ b/lib/active_support/core_ext/hash/slice.rb
@@ -3,7 +3,7 @@ class Hash
# limiting an options hash to valid keys before passing to a method:
#
# def search(criteria = {})
- # assert_valid_keys(:mass, :velocity, :time)
+ # criteria.assert_valid_keys(:mass, :velocity, :time)
# end
#
# search(options.slice(:mass, :velocity, :time))
@@ -13,17 +13,17 @@ class Hash
# valid_keys = [:mass, :velocity, :time]
# search(options.slice(*valid_keys))
def slice(*keys)
- keys = keys.map! { |key| convert_key(key) } if respond_to?(:convert_key, true)
- hash = self.class.new
- keys.each { |k| hash[k] = self[k] if has_key?(k) }
- hash
+ keys.map! { |key| convert_key(key) } if respond_to?(:convert_key, true)
+ keys.each_with_object(self.class.new) { |k, hash| hash[k] = self[k] if has_key?(k) }
end
# Replaces the hash with only the given keys.
- # Returns a hash contained the removed key/value pairs
- # {:a => 1, :b => 2, :c => 3, :d => 4}.slice!(:a, :b) # => {:c => 3, :d => 4}
+ # Returns a hash containing the removed key/value pairs.
+ #
+ # { a: 1, b: 2, c: 3, d: 4 }.slice!(:a, :b)
+ # # => {:c=>3, :d=>4}
def slice!(*keys)
- keys = keys.map! { |key| convert_key(key) } if respond_to?(:convert_key, true)
+ keys.map! { |key| convert_key(key) } if respond_to?(:convert_key, true)
omit = slice(*self.keys - keys)
hash = slice(*keys)
replace(hash)
@@ -31,10 +31,10 @@ class Hash
end
# Removes and returns the key/value pairs matching the given keys.
- # {:a => 1, :b => 2, :c => 3, :d => 4}.extract!(:a, :b) # => {:a => 1, :b => 2}
+ #
+ # { a: 1, b: 2, c: 3, d: 4 }.extract!(:a, :b) # => {:a=>1, :b=>2}
+ # { a: 1, b: 2 }.extract!(:a, :x) # => {:a=>1}
def extract!(*keys)
- result = {}
- keys.each {|key| result[key] = delete(key) }
- result
+ keys.each_with_object(self.class.new) { |key, result| result[key] = delete(key) if has_key?(key) }
end
end
diff --git a/lib/active_support/core_ext/integer/inflections.rb b/lib/active_support/core_ext/integer/inflections.rb
index 0e60605..56f2ed5 100644
--- a/lib/active_support/core_ext/integer/inflections.rb
+++ b/lib/active_support/core_ext/integer/inflections.rb
@@ -10,8 +10,20 @@ class Integer
# 1003.ordinalize # => "1003rd"
# -11.ordinalize # => "-11th"
# -1001.ordinalize # => "-1001st"
- #
def ordinalize
ActiveSupport::Inflector.ordinalize(self)
end
+
+ # Ordinal returns the suffix used to denote the position
+ # in an ordered sequence such as 1st, 2nd, 3rd, 4th.
+ #
+ # 1.ordinal # => "st"
+ # 2.ordinal # => "nd"
+ # 1002.ordinal # => "nd"
+ # 1003.ordinal # => "rd"
+ # -11.ordinal # => "th"
+ # -1001.ordinal # => "st"
+ def ordinal
+ ActiveSupport::Inflector.ordinal(self)
+ end
end
diff --git a/lib/active_support/core_ext/integer/time.rb b/lib/active_support/core_ext/integer/time.rb
index c677400..82080ff 100644
--- a/lib/active_support/core_ext/integer/time.rb
+++ b/lib/active_support/core_ext/integer/time.rb
@@ -1,21 +1,26 @@
+require 'active_support/duration'
+require 'active_support/core_ext/numeric/time'
+
class Integer
- # Enables the use of time calculations and declarations, like 45.minutes + 2.hours + 4.years.
+ # Enables the use of time calculations and declarations, like <tt>45.minutes +
+ # 2.hours + 4.years</tt>.
#
- # These methods use Time#advance for precise date calculations when using from_now, ago, etc.
- # as well as adding or subtracting their results from a Time object. For example:
+ # These methods use Time#advance for precise date calculations when using
+ # <tt>from_now</tt>, +ago+, etc. as well as adding or subtracting their
+ # results from a Time object.
#
- # # equivalent to Time.now.advance(:months => 1)
+ # # equivalent to Time.now.advance(months: 1)
# 1.month.from_now
#
- # # equivalent to Time.now.advance(:years => 2)
+ # # equivalent to Time.now.advance(years: 2)
# 2.years.from_now
#
- # # equivalent to Time.now.advance(:months => 4, :years => 5)
+ # # equivalent to Time.now.advance(months: 4, years: 5)
# (4.months + 5.years).from_now
#
- # While these methods provide precise calculation when used as in the examples above, care
- # should be taken to note that this is not true if the result of `months', `years', etc is
- # converted before use:
+ # While these methods provide precise calculation when used as in the examples
+ # above, care should be taken to note that this is not true if the result of
+ # +months+, +years+, etc is converted before use:
#
# # equivalent to 30.days.to_i.from_now
# 1.month.to_i.from_now
@@ -24,9 +29,9 @@ class Integer
# 1.year.to_f.from_now
#
# In such cases, Ruby's core
- # Date[http://stdlib.rubyonrails.org/libdoc/date/rdoc/index.html] and
- # Time[http://stdlib.rubyonrails.org/libdoc/time/rdoc/index.html] should be used for precision
- # date and time arithmetic
+ # Date[http://ruby-doc.org/stdlib/libdoc/date/rdoc/Date.html] and
+ # Time[http://ruby-doc.org/stdlib/libdoc/time/rdoc/Time.html] should be used for precision
+ # date and time arithmetic.
def months
ActiveSupport::Duration.new(self * 30.days, [[:months, self]])
end
diff --git a/lib/active_support/core_ext/io.rb b/lib/active_support/core_ext/io.rb
deleted file mode 100644
index 75f1055..0000000
--- a/lib/active_support/core_ext/io.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-if RUBY_VERSION < '1.9.2'
-
-# :stopdoc:
-class IO
- def self.binread(name, length = nil, offset = nil)
- return File.read name unless length || offset
- File.open(name, 'rb') { |f|
- f.seek offset if offset
- f.read length
- }
- end
-end
-# :startdoc:
-
-end
diff --git a/lib/active_support/core_ext/kernel/debugger.rb b/lib/active_support/core_ext/kernel/debugger.rb
index 7516f41..2073cac 100644
--- a/lib/active_support/core_ext/kernel/debugger.rb
+++ b/lib/active_support/core_ext/kernel/debugger.rb
@@ -1,8 +1,8 @@
module Kernel
unless respond_to?(:debugger)
- # Starts a debugging session if ruby-debug has been loaded (call rails server --debugger to do load it).
+ # Starts a debugging session if the +debugger+ gem has been loaded (call rails server --debugger to do load it).
def debugger
- message = "\n***** Debugger requested, but was not available (ensure ruby-debug is listed in Gemfile/installed as gem): Start server with --debugger to enable *****\n"
+ message = "\n***** Debugger requested, but was not available (ensure the debugger gem is listed in Gemfile/installed as gem): Start server with --debugger to enable *****\n"
defined?(Rails) ? Rails.logger.info(message) : $stderr.puts(message)
end
alias breakpoint debugger unless respond_to?(:breakpoint)
diff --git a/lib/active_support/core_ext/kernel/reporting.rb b/lib/active_support/core_ext/kernel/reporting.rb
index 84986df..79d3303 100644
--- a/lib/active_support/core_ext/kernel/reporting.rb
+++ b/lib/active_support/core_ext/kernel/reporting.rb
@@ -1,8 +1,9 @@
require 'rbconfig'
-require 'stringio'
+require 'tempfile'
module Kernel
- # Sets $VERBOSE to nil for the duration of the block and back to its original value afterwards.
+ # Sets $VERBOSE to nil for the duration of the block and back to its original
+ # value afterwards.
#
# silence_warnings do
# value = noisy_call # no warning voiced
@@ -13,12 +14,14 @@ module Kernel
with_warnings(nil) { yield }
end
- # Sets $VERBOSE to true for the duration of the block and back to its original value afterwards.
+ # Sets $VERBOSE to +true+ for the duration of the block and back to its
+ # original value afterwards.
def enable_warnings
with_warnings(true) { yield }
end
- # Sets $VERBOSE for the duration of the block and back to its original value afterwards.
+ # Sets $VERBOSE for the duration of the block and back to its original
+ # value afterwards.
def with_warnings(flag)
old_verbose, $VERBOSE = $VERBOSE, flag
yield
@@ -51,40 +54,51 @@ module Kernel
#
# suppress(ZeroDivisionError) do
# 1/0
- # puts "This code is NOT reached"
+ # puts 'This code is NOT reached'
# end
#
- # puts "This code gets executed and nothing related to ZeroDivisionError was seen"
+ # puts 'This code gets executed and nothing related to ZeroDivisionError was seen'
def suppress(*exception_classes)
- begin yield
- rescue Exception => e
- raise unless exception_classes.any? { |cls| e.kind_of?(cls) }
- end
+ yield
+ rescue Exception => e
+ raise unless exception_classes.any? { |cls| e.kind_of?(cls) }
end
# Captures the given stream and returns it:
#
- # stream = capture(:stdout) { puts "Cool" }
- # stream # => "Cool\n"
+ # stream = capture(:stdout) { puts 'notice' }
+ # stream # => "notice\n"
+ #
+ # stream = capture(:stderr) { warn 'error' }
+ # stream # => "error\n"
+ #
+ # even for subprocesses:
+ #
+ # stream = capture(:stdout) { system('echo notice') }
+ # stream # => "notice\n"
#
+ # stream = capture(:stderr) { system('echo error 1>&2') }
+ # stream # => "error\n"
def capture(stream)
- begin
- stream = stream.to_s
- eval "$#{stream} = StringIO.new"
- yield
- result = eval("$#{stream}").string
- ensure
- eval("$#{stream} = #{stream.upcase}")
- end
+ stream = stream.to_s
+ captured_stream = Tempfile.new(stream)
+ stream_io = eval("$#{stream}")
+ origin_stream = stream_io.dup
+ stream_io.reopen(captured_stream)
+
+ yield
- result
+ stream_io.rewind
+ return captured_stream.read
+ ensure
+ captured_stream.unlink
+ stream_io.reopen(origin_stream)
end
alias :silence :capture
# Silences both STDOUT and STDERR, even for subprocesses.
#
# quietly { system 'bundle install' }
- #
def quietly
silence_stream(STDOUT) do
silence_stream(STDERR) do
diff --git a/lib/active_support/core_ext/kernel/singleton_class.rb b/lib/active_support/core_ext/kernel/singleton_class.rb
index 3361215..9bbf1bb 100644
--- a/lib/active_support/core_ext/kernel/singleton_class.rb
+++ b/lib/active_support/core_ext/kernel/singleton_class.rb
@@ -1,11 +1,4 @@
module Kernel
- # Returns the object's singleton class.
- def singleton_class
- class << self
- self
- end
- end unless respond_to?(:singleton_class) # exists in 1.9.2
-
# class_eval on an object acts like singleton_class.class_eval.
def class_eval(*args, &block)
singleton_class.class_eval(*args, &block)
diff --git a/lib/active_support/core_ext/load_error.rb b/lib/active_support/core_ext/load_error.rb
index 8bdfa0c..fe24f37 100644
--- a/lib/active_support/core_ext/load_error.rb
+++ b/lib/active_support/core_ext/load_error.rb
@@ -6,12 +6,14 @@ class LoadError
/^cannot load such file -- (.+)$/i,
]
- def path
- @path ||= begin
- REGEXPS.find do |regex|
- message =~ regex
+ unless method_defined?(:path)
+ def path
+ @path ||= begin
+ REGEXPS.find do |regex|
+ message =~ regex
+ end
+ $1
end
- $1
end
end
diff --git a/lib/active_support/core_ext/logger.rb b/lib/active_support/core_ext/logger.rb
index ca0f382..34de766 100644
--- a/lib/active_support/core_ext/logger.rb
+++ b/lib/active_support/core_ext/logger.rb
@@ -1,5 +1,8 @@
require 'active_support/core_ext/class/attribute_accessors'
require 'active_support/deprecation'
+require 'active_support/logger_silence'
+
+ActiveSupport::Deprecation.warn 'this file is deprecated and will be removed'
# Adds the 'around_level' method to Logger.
class Logger #:nodoc:
@@ -29,34 +32,15 @@ require 'logger'
#
# logger.datetime_format = "%Y-%m-%d"
#
-# Note: This logger is deprecated in favor of ActiveSupport::BufferedLogger
+# Note: This logger is deprecated in favor of ActiveSupport::Logger
class Logger
- ##
- # :singleton-method:
- # Set to false to disable the silencer
- cattr_accessor :silencer
- self.silencer = true
-
- # Silences the logger for the duration of the block.
- def silence(temporary_level = Logger::ERROR)
- if silencer
- begin
- old_logger_level, self.level = level, temporary_level
- yield self
- ensure
- self.level = old_logger_level
- end
- else
- yield self
- end
- end
- deprecate :silence
+ include LoggerSilence
alias :old_datetime_format= :datetime_format=
# Logging date-time format (string passed to +strftime+). Ignored if the formatter
# does not respond to datetime_format=.
- def datetime_format=(datetime_format)
- formatter.datetime_format = datetime_format if formatter.respond_to?(:datetime_format=)
+ def datetime_format=(format)
+ formatter.datetime_format = format if formatter.respond_to?(:datetime_format=)
end
alias :old_datetime_format :datetime_format
diff --git a/lib/active_support/core_ext/marshal.rb b/lib/active_support/core_ext/marshal.rb
new file mode 100644
index 0000000..56c79c0
--- /dev/null
+++ b/lib/active_support/core_ext/marshal.rb
@@ -0,0 +1,21 @@
+require 'active_support/core_ext/module/aliasing'
+
+module Marshal
+ class << self
+ def load_with_autoloading(source)
+ load_without_autoloading(source)
+ rescue ArgumentError, NameError => exc
+ if exc.message.match(%r|undefined class/module (.+)|)
+ # try loading the class/module
+ $1.constantize
+ # if it is a IO we need to go back to read the object
+ source.rewind if source.respond_to?(:rewind)
+ retry
+ else
+ raise exc
+ end
+ end
+
+ alias_method_chain :load, :autoloading
+ end
+end
diff --git a/lib/active_support/core_ext/module.rb b/lib/active_support/core_ext/module.rb
index f399fce..f2d4887 100644
--- a/lib/active_support/core_ext/module.rb
+++ b/lib/active_support/core_ext/module.rb
@@ -5,8 +5,6 @@ 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/delegation'
-require 'active_support/core_ext/module/synchronization'
require 'active_support/core_ext/module/deprecation'
require 'active_support/core_ext/module/remove_method'
-require 'active_support/core_ext/module/method_names'
-require 'active_support/core_ext/module/qualified_const'
\ No newline at end of file
+require 'active_support/core_ext/module/qualified_const'
diff --git a/lib/active_support/core_ext/module/aliasing.rb b/lib/active_support/core_ext/module/aliasing.rb
index ce481f0..580cb80 100644
--- a/lib/active_support/core_ext/module/aliasing.rb
+++ b/lib/active_support/core_ext/module/aliasing.rb
@@ -26,26 +26,25 @@ class Module
aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1
yield(aliased_target, punctuation) if block_given?
- with_method, without_method = "#{aliased_target}_with_#{feature}#{punctuation}", "#{aliased_target}_without_#{feature}#{punctuation}"
+ with_method = "#{aliased_target}_with_#{feature}#{punctuation}"
+ without_method = "#{aliased_target}_without_#{feature}#{punctuation}"
alias_method without_method, target
alias_method target, with_method
case
- when public_method_defined?(without_method)
- public target
- when protected_method_defined?(without_method)
- protected target
- when private_method_defined?(without_method)
- private target
+ when public_method_defined?(without_method)
+ public target
+ when protected_method_defined?(without_method)
+ protected target
+ when private_method_defined?(without_method)
+ private target
end
end
# Allows you to make aliases for attributes, which includes
# getter, setter, and query methods.
#
- # Example:
- #
# class Content < ActiveRecord::Base
# # has a title attribute
# end
diff --git a/lib/active_support/core_ext/module/anonymous.rb b/lib/active_support/core_ext/module/anonymous.rb
index 3982c9c..b0c7b02 100644
--- a/lib/active_support/core_ext/module/anonymous.rb
+++ b/lib/active_support/core_ext/module/anonymous.rb
@@ -1,5 +1,3 @@
-require 'active_support/core_ext/object/blank'
-
class Module
# A module may or may not have a name.
#
@@ -7,7 +5,7 @@ class Module
# M.name # => "M"
#
# m = Module.new
- # m.name # => ""
+ # m.name # => nil
#
# A module gets a name when it is first assigned to a constant. Either
# via the +module+ or +class+ keyword or by an explicit assignment:
@@ -15,10 +13,7 @@ class Module
# m = Module.new # creates an anonymous module
# M = m # => m gets a name here as a side-effect
# m.name # => "M"
- #
def anonymous?
- # Uses blank? because the name of an anonymous class is an empty
- # string in 1.8, and nil in 1.9.
- name.blank?
+ name.nil?
end
end
diff --git a/lib/active_support/core_ext/module/attr_internal.rb b/lib/active_support/core_ext/module/attr_internal.rb
index 00db75b..db07d54 100644
--- a/lib/active_support/core_ext/module/attr_internal.rb
+++ b/lib/active_support/core_ext/module/attr_internal.rb
@@ -15,7 +15,6 @@ class Module
attr_internal_reader(*attrs)
attr_internal_writer(*attrs)
end
-
alias_method :attr_internal, :attr_internal_accessor
class << self; attr_accessor :attr_internal_naming_format end
diff --git a/lib/active_support/core_ext/module/attribute_accessors.rb b/lib/active_support/core_ext/module/attribute_accessors.rb
index be94ae1..672cc02 100644
--- a/lib/active_support/core_ext/module/attribute_accessors.rb
+++ b/lib/active_support/core_ext/module/attribute_accessors.rb
@@ -4,6 +4,7 @@ class Module
def mattr_reader(*syms)
options = syms.extract_options!
syms.each do |sym|
+ raise NameError.new('invalid attribute name') unless sym =~ /^[_A-Za-z]\w*$/
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
@@#{sym} = nil unless defined? @@#{sym}
@@ -25,6 +26,7 @@ class Module
def mattr_writer(*syms)
options = syms.extract_options!
syms.each do |sym|
+ raise NameError.new('invalid attribute name') unless sym =~ /^[_A-Za-z]\w*$/
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
def self.#{sym}=(obj)
@@#{sym} = obj
@@ -44,19 +46,19 @@ class Module
# Extends the module object with module and instance accessors for class attributes,
# just like the native attr* accessors for instance attributes.
#
- # module AppConfiguration
- # mattr_accessor :google_api_key
- # self.google_api_key = "123456789"
+ # module AppConfiguration
+ # mattr_accessor :google_api_key
#
- # mattr_accessor :paypal_url
- # self.paypal_url = "www.sandbox.paypal.com"
- # end
+ # self.google_api_key = "123456789"
+ # end
#
- # AppConfiguration.google_api_key = "overriding the api key!"
+ # AppConfiguration.google_api_key # => "123456789"
+ # AppConfiguration.google_api_key = "overriding the api key!"
+ # AppConfiguration.google_api_key # => "overriding the api key!"
#
- # To opt out of the instance writer method, pass :instance_writer => false.
- # To opt out of the instance reader method, pass :instance_reader => false.
- # To opt out of both instance methods, pass :instance_accessor => false.
+ # 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)
diff --git a/lib/active_support/core_ext/module/delegation.rb b/lib/active_support/core_ext/module/delegation.rb
index 7de824a..eebf7d3 100644
--- a/lib/active_support/core_ext/module/delegation.rb
+++ b/lib/active_support/core_ext/module/delegation.rb
@@ -1,24 +1,26 @@
class Module
- # Provides a delegate class method to easily expose contained objects' methods
- # as your own. Pass one or more methods (specified as symbols or strings)
- # and the name of the target object via the <tt>:to</tt> option (also a symbol
- # or string). At least one method and the <tt>:to</tt> option are required.
+ # Provides a +delegate+ class method to easily expose contained objects'
+ # public methods as your own.
+ #
+ # The macro receives one or more method names (specified as symbols or
+ # strings) and the name of the target object via the <tt>:to</tt> option
+ # (also a symbol or string).
#
# Delegation is particularly useful with Active Record associations:
#
# class Greeter < ActiveRecord::Base
# def hello
- # "hello"
+ # 'hello'
# end
#
# def goodbye
- # "goodbye"
+ # 'goodbye'
# end
# end
#
# class Foo < ActiveRecord::Base
# belongs_to :greeter
- # delegate :hello, :to => :greeter
+ # delegate :hello, to: :greeter
# end
#
# Foo.new.hello # => "hello"
@@ -28,7 +30,7 @@ class Module
#
# class Foo < ActiveRecord::Base
# belongs_to :greeter
- # delegate :hello, :goodbye, :to => :greeter
+ # delegate :hello, :goodbye, to: :greeter
# end
#
# Foo.new.goodbye # => "goodbye"
@@ -43,15 +45,27 @@ class Module
# def initialize
# @instance_array = [8,9,10,11]
# end
- # delegate :sum, :to => :CONSTANT_ARRAY
- # delegate :min, :to => :@@class_array
- # delegate :max, :to => :@instance_array
+ # delegate :sum, to: :CONSTANT_ARRAY
+ # delegate :min, to: :@@class_array
+ # delegate :max, to: :@instance_array
# end
#
# Foo.new.sum # => 6
# Foo.new.min # => 4
# Foo.new.max # => 11
#
+ # It's also possible to delegate a method to the class by using +:class+:
+ #
+ # class Foo
+ # def self.hello
+ # "world"
+ # end
+ #
+ # delegate :hello, to: :class
+ # end
+ #
+ # Foo.new.hello # => "world"
+ #
# Delegates can optionally be prefixed using the <tt>:prefix</tt> option. If the value
# is <tt>true</tt>, the delegate methods are prefixed with the name of the object being
# delegated to.
@@ -59,10 +73,10 @@ class Module
# Person = Struct.new(:name, :address)
#
# class Invoice < Struct.new(:client)
- # delegate :name, :address, :to => :client, :prefix => true
+ # delegate :name, :address, to: :client, prefix: true
# end
#
- # john_doe = Person.new("John Doe", "Vimmersvej 13")
+ # john_doe = Person.new('John Doe', 'Vimmersvej 13')
# invoice = Invoice.new(john_doe)
# invoice.client_name # => "John Doe"
# invoice.client_address # => "Vimmersvej 13"
@@ -70,49 +84,64 @@ class Module
# It is also possible to supply a custom prefix.
#
# class Invoice < Struct.new(:client)
- # delegate :name, :address, :to => :client, :prefix => :customer
+ # delegate :name, :address, to: :client, prefix: :customer
# end
#
# invoice = Invoice.new(john_doe)
- # invoice.customer_name # => "John Doe"
- # invoice.customer_address # => "Vimmersvej 13"
+ # invoice.customer_name # => 'John Doe'
+ # invoice.customer_address # => 'Vimmersvej 13'
+ #
+ # If the target is +nil+ and does not respond to the delegated method a
+ # +NoMethodError+ is raised, as with any other value. Sometimes, however, it
+ # makes sense to be robust to that situation and that is the purpose of the
+ # <tt>:allow_nil</tt> option: If the target is not +nil+, or it is and
+ # responds to the method, everything works as usual. But if it is +nil+ and
+ # does not respond to the delegated method, +nil+ is returned.
+ #
+ # class User < ActiveRecord::Base
+ # has_one :profile
+ # delegate :age, to: :profile
+ # end
+ #
+ # User.new.age # raises NoMethodError: undefined method `age'
+ #
+ # But if not having a profile yet is fine and should not be an error
+ # condition:
#
- # If the delegate object is +nil+ an exception is raised, and that happens
- # no matter whether +nil+ responds to the delegated method. You can get a
- # +nil+ instead with the +:allow_nil+ option.
+ # class User < ActiveRecord::Base
+ # has_one :profile
+ # delegate :age, to: :profile, allow_nil: true
+ # end
+ #
+ # User.new.age # nil
#
- # class Foo
- # attr_accessor :bar
- # def initialize(bar = nil)
- # @bar = bar
- # end
- # delegate :zoo, :to => :bar
- # end
+ # Note that if the target is not +nil+ then the call is attempted regardless of the
+ # <tt>:allow_nil</tt> option, and thus an exception is still raised if said object
+ # does not respond to the method:
#
- # Foo.new.zoo # raises NoMethodError exception (you called nil.zoo)
+ # class Foo
+ # def initialize(bar)
+ # @bar = bar
+ # end
#
- # class Foo
- # attr_accessor :bar
- # def initialize(bar = nil)
- # @bar = bar
- # end
- # delegate :zoo, :to => :bar, :allow_nil => true
- # end
+ # delegate :name, to: :@bar, allow_nil: true
+ # end
#
- # Foo.new.zoo # returns nil
+ # Foo.new("Bar").name # raises NoMethodError: undefined method `name'
#
def delegate(*methods)
options = methods.pop
unless options.is_a?(Hash) && to = options[:to]
- raise ArgumentError, "Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, :to => :greeter)."
+ raise ArgumentError, 'Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, to: :greeter).'
end
- prefix, to, allow_nil = options[:prefix], options[:to], options[:allow_nil]
- if prefix == true && to.to_s =~ /^[^a-z_]/
- raise ArgumentError, "Can only automatically set the delegation prefix when delegating to a method."
+ prefix, allow_nil = options.values_at(:prefix, :allow_nil)
+
+ if prefix == true && to =~ /^[^a-z_]/
+ raise ArgumentError, 'Can only automatically set the delegation prefix when delegating to a method.'
end
- method_prefix =
+ method_prefix = \
if prefix
"#{prefix == true ? to : prefix}_"
else
@@ -122,30 +151,45 @@ class Module
file, line = caller.first.split(':', 2)
line = line.to_i
+ to = to.to_s
+ to = 'self.class' if to == 'class'
+
methods.each do |method|
- method = method.to_s
+ # Attribute writer methods only accept one argument. Makes sure []=
+ # methods still accept two arguments.
+ definition = (method =~ /[^\]]=$/) ? 'arg' : '*args, &block'
+ # The following generated methods call the target exactly once, storing
+ # the returned value in a dummy variable.
+ #
+ # Reason is twofold: On one hand doing less calls is in general better.
+ # On the other hand it could be that the target has side-effects,
+ # whereas conceptualy, from the user point of view, the delegator should
+ # be doing one call.
if allow_nil
- module_eval(<<-EOS, file, line - 2)
- def #{method_prefix}#{method}(*args, &block) # def customer_name(*args, &block)
- if #{to} || #{to}.respond_to?(:#{method}) # if client || client.respond_to?(:name)
- #{to}.__send__(:#{method}, *args, &block) # client.__send__(:name, *args, &block)
+ module_eval(<<-EOS, file, line - 3)
+ def #{method_prefix}#{method}(#{definition}) # def customer_name(*args, &block)
+ _ = #{to} # _ = client
+ if !_.nil? || nil.respond_to?(:#{method}) # if !_.nil? || nil.respond_to?(:name)
+ _.#{method}(#{definition}) # _.name(*args, &block)
end # end
end # end
EOS
else
exception = %(raise "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}")
- module_eval(<<-EOS, file, line - 1)
- def #{method_prefix}#{method}(*args, &block) # def customer_name(*args, &block)
- #{to}.__send__(:#{method}, *args, &block) # client.__send__(:name, *args, &block)
- rescue NoMethodError # rescue NoMethodError
- if #{to}.nil? # if client.nil?
- #{exception} # # add helpful message to the exception
- else # else
- raise # raise
- end # end
- end # end
+ module_eval(<<-EOS, file, line - 2)
+ def #{method_prefix}#{method}(#{definition}) # def customer_name(*args, &block)
+ _ = #{to} # _ = client
+ _.#{method}(#{definition}) # _.name(*args, &block)
+ rescue NoMethodError => e # rescue NoMethodError => e
+ location = "%s:%d:in `%s'" % [__FILE__, __LINE__ - 2, '#{method_prefix}#{method}'] # location = "%s:%d:in `%s'" % [__FILE__, __LINE__ - 2, 'customer_name']
+ if _.nil? && e.backtrace.first == location # if _.nil? && e.backtrace.first == location
+ #{exception} # # add helpful message to the exception
+ else # else
+ raise # raise
+ end # end
+ end # end
EOS
end
end
diff --git a/lib/active_support/core_ext/module/deprecation.rb b/lib/active_support/core_ext/module/deprecation.rb
index 5a5b4e3..d873de1 100644
--- a/lib/active_support/core_ext/module/deprecation.rb
+++ b/lib/active_support/core_ext/module/deprecation.rb
@@ -1,8 +1,24 @@
+require 'active_support/deprecation/method_wrappers'
+
class Module
- # Declare that a method has been deprecated.
# deprecate :foo
- # deprecate :bar => 'message'
- # deprecate :foo, :bar, :baz => 'warning!', :qux => 'gone!'
+ # deprecate bar: 'message'
+ # deprecate :foo, :bar, baz: 'warning!', qux: 'gone!'
+ #
+ # You can also use custom deprecator instance:
+ #
+ # deprecate :foo, deprecator: MyLib::Deprecator.new
+ # deprecate :foo, bar: "warning!", deprecator: MyLib::Deprecator.new
+ #
+ # \Custom deprecators must respond to <tt>deprecation_warning(deprecated_method_name, message, caller_backtrace)</tt>
+ # method where you can implement your custom warning behavior.
+ #
+ # class MyLib::Deprecator
+ # def deprecation_warning(deprecated_method_name, message, caller_backtrace = nil)
+ # message = "#{deprecated_method_name} is deprecated and will be removed from MyLibrary | #{message}"
+ # Kernel.warn message
+ # end
+ # end
def deprecate(*method_names)
ActiveSupport::Deprecation.deprecate_methods(self, *method_names)
end
diff --git a/lib/active_support/core_ext/module/introspection.rb b/lib/active_support/core_ext/module/introspection.rb
index c08ad25..08e5f8a 100644
--- a/lib/active_support/core_ext/module/introspection.rb
+++ b/lib/active_support/core_ext/module/introspection.rb
@@ -5,10 +5,11 @@ class Module
#
# M::N.parent_name # => "M"
def parent_name
- unless defined? @parent_name
+ if defined? @parent_name
+ @parent_name
+ else
@parent_name = name =~ /::[^:]+\Z/ ? $`.freeze : nil
end
- @parent_name
end
# Returns the module which contains this one according to its name.
@@ -26,7 +27,6 @@ class Module
#
# M.parent # => Object
# Module.new.parent # => Object
- #
def parent
parent_name ? ActiveSupport::Inflector.constantize(parent_name) : Object
end
@@ -43,7 +43,6 @@ class Module
# M.parents # => [Object]
# M::N.parents # => [M, Object]
# X.parents # => [M, Object]
- #
def parents
parents = []
if parent_name
@@ -57,32 +56,23 @@ class Module
parents
end
- if RUBY_VERSION < '1.9'
- # Returns the constants that have been defined locally by this object and
- # not in an ancestor. This method is exact if running under Ruby 1.9. In
- # previous versions it may miss some constants if their definition in some
- # ancestor is identical to their definition in the receiver.
- def local_constants
- inherited = {}
-
- ancestors.each do |anc|
- next if anc == self
- anc.constants.each { |const| inherited[const] = anc.const_get(const) }
- end
-
- constants.select do |const|
- !inherited.key?(const) || inherited[const].object_id != const_get(const).object_id
- end
- end
- else
- def local_constants #:nodoc:
- constants(false)
- end
+ def local_constants #:nodoc:
+ constants(false)
end
- # Returns the names of the constants defined locally rather than the
- # constants themselves. See <tt>local_constants</tt>.
+ # *DEPRECATED*: Use +local_constants+ instead.
+ #
+ # Returns the names of the constants defined locally as strings.
+ #
+ # module M
+ # X = 1
+ # end
+ # M.local_constant_names # => ["X"]
+ #
+ # This method is useful for forward compatibility, since Ruby 1.8 returns
+ # constant names as strings, whereas 1.9 returns them as symbols.
def local_constant_names
+ ActiveSupport::Deprecation.warn 'Module#local_constant_names is deprecated, use Module#local_constants instead'
local_constants.map { |c| c.to_s }
end
end
diff --git a/lib/active_support/core_ext/module/method_names.rb b/lib/active_support/core_ext/module/method_names.rb
deleted file mode 100644
index 2eb40a8..0000000
--- a/lib/active_support/core_ext/module/method_names.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-class Module
- if instance_methods[0].is_a?(Symbol)
- def instance_method_names(*args)
- instance_methods(*args).map(&:to_s)
- end
-
- def method_names(*args)
- methods(*args).map(&:to_s)
- end
- else
- alias_method :instance_method_names, :instance_methods
- alias_method :method_names, :methods
- end
-end
\ No newline at end of file
diff --git a/lib/active_support/core_ext/module/qualified_const.rb b/lib/active_support/core_ext/module/qualified_const.rb
index d1a0ee2..6552501 100644
--- a/lib/active_support/core_ext/module/qualified_const.rb
+++ b/lib/active_support/core_ext/module/qualified_const.rb
@@ -5,7 +5,7 @@ require 'active_support/core_ext/string/inflections'
#++
module QualifiedConstUtils
def self.raise_if_absolute(path)
- raise NameError, "wrong constant name #$&" if path =~ /\A::[^:]+/
+ raise NameError.new("wrong constant name #$&") if path =~ /\A::[^:]+/
end
def self.names(path)
@@ -20,29 +20,17 @@ end
#--
# Qualified names are required to be relative because we are extending existing
# methods that expect constant names, ie, relative paths of length 1. For example,
-# Object.const_get("::String") raises NameError and so does qualified_const_get.
+# Object.const_get('::String') raises NameError and so does qualified_const_get.
#++
class Module
- if method(:const_defined?).arity == 1
- def qualified_const_defined?(path)
- QualifiedConstUtils.raise_if_absolute(path)
-
- QualifiedConstUtils.names(path).inject(self) do |mod, name|
- return unless mod.const_defined?(name)
- mod.const_get(name)
- end
- return true
- end
- else
- def qualified_const_defined?(path, search_parents=true)
- QualifiedConstUtils.raise_if_absolute(path)
+ def qualified_const_defined?(path, search_parents=true)
+ QualifiedConstUtils.raise_if_absolute(path)
- QualifiedConstUtils.names(path).inject(self) do |mod, name|
- return unless mod.const_defined?(name, search_parents)
- mod.const_get(name)
- end
- return true
+ QualifiedConstUtils.names(path).inject(self) do |mod, name|
+ return unless mod.const_defined?(name, search_parents)
+ mod.const_get(name)
end
+ return true
end
def qualified_const_get(path)
diff --git a/lib/active_support/core_ext/module/remove_method.rb b/lib/active_support/core_ext/module/remove_method.rb
index b76bc16..719071d 100644
--- a/lib/active_support/core_ext/module/remove_method.rb
+++ b/lib/active_support/core_ext/module/remove_method.rb
@@ -1,12 +1,8 @@
class Module
def remove_possible_method(method)
if method_defined?(method) || private_method_defined?(method)
- remove_method(method)
+ undef_method(method)
end
- rescue NameError
- # If the requested method is defined on a superclass or included module,
- # method_defined? returns true but remove_method throws a NameError.
- # Ignore this.
end
def redefine_method(method, &block)
diff --git a/lib/active_support/core_ext/module/synchronization.rb b/lib/active_support/core_ext/module/synchronization.rb
deleted file mode 100644
index 061621c..0000000
--- a/lib/active_support/core_ext/module/synchronization.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-require 'thread'
-require 'active_support/core_ext/module/aliasing'
-require 'active_support/core_ext/array/extract_options'
-require 'active_support/core_ext/module/deprecation'
-
-class Module
- # Synchronize access around a method, delegating synchronization to a
- # particular mutex. A mutex (either a Mutex, or any object that responds to
- # #synchronize and yields to a block) must be provided as a final :with option.
- # The :with option should be a symbol or string, and can represent a method,
- # constant, or instance or class variable.
- # Example:
- # class SharedCache
- # @@lock = Mutex.new
- # def expire
- # ...
- # end
- # synchronize :expire, :with => :@@lock
- # end
- def synchronize(*methods)
- options = methods.extract_options!
- unless options.is_a?(Hash) && with = options[:with]
- raise ArgumentError, "Synchronization needs a mutex. Supply an options hash with a :with key as the last argument (e.g. synchronize :hello, :with => :@mutex)."
- end
-
- methods.each do |method|
- aliased_method, punctuation = method.to_s.sub(/([?!=])$/, ''), $1
-
- if method_defined?("#{aliased_method}_without_synchronization#{punctuation}")
- raise ArgumentError, "#{method} is already synchronized. Double synchronization is not currently supported."
- end
-
- module_eval(<<-EOS, __FILE__, __LINE__ + 1)
- def #{aliased_method}_with_synchronization#{punctuation}(*args, &block) # def expire_with_synchronization(*args, &block)
- #{with}.synchronize do # @@lock.synchronize do
- #{aliased_method}_without_synchronization#{punctuation}(*args, &block) # expire_without_synchronization(*args, &block)
- end # end
- end # end
- EOS
-
- alias_method_chain method, :synchronization
- end
- end
- deprecate :synchronize
-end
diff --git a/lib/active_support/core_ext/numeric.rb b/lib/active_support/core_ext/numeric.rb
index 3805cf7..a6bc062 100644
--- a/lib/active_support/core_ext/numeric.rb
+++ b/lib/active_support/core_ext/numeric.rb
@@ -1,2 +1,3 @@
require 'active_support/core_ext/numeric/bytes'
require 'active_support/core_ext/numeric/time'
+require 'active_support/core_ext/numeric/conversions'
diff --git a/lib/active_support/core_ext/numeric/conversions.rb b/lib/active_support/core_ext/numeric/conversions.rb
new file mode 100644
index 0000000..6d3635c
--- /dev/null
+++ b/lib/active_support/core_ext/numeric/conversions.rb
@@ -0,0 +1,135 @@
+require 'active_support/core_ext/big_decimal/conversions'
+require 'active_support/number_helper'
+
+class Numeric
+
+ # Provides options for converting numbers into formatted strings.
+ # Options are provided for phone numbers, currency, percentage,
+ # precision, positional notation, file size and pretty printing.
+ #
+ # ==== Options
+ #
+ # For details on which formats use which options, see ActiveSupport::NumberHelper
+ #
+ # ==== Examples
+ #
+ # Phone Numbers:
+ # 5551234.to_s(:phone) # => 555-1234
+ # 1235551234.to_s(:phone) # => 123-555-1234
+ # 1235551234.to_s(:phone, area_code: true) # => (123) 555-1234
+ # 1235551234.to_s(:phone, delimiter: ' ') # => 123 555 1234
+ # 1235551234.to_s(:phone, area_code: true, extension: 555) # => (123) 555-1234 x 555
+ # 1235551234.to_s(:phone, country_code: 1) # => +1-123-555-1234
+ # 1235551234.to_s(:phone, country_code: 1, extension: 1343, delimiter: '.')
+ # # => +1.123.555.1234 x 1343
+ #
+ # Currency:
+ # 1234567890.50.to_s(:currency) # => $1,234,567,890.50
+ # 1234567890.506.to_s(:currency) # => $1,234,567,890.51
+ # 1234567890.506.to_s(:currency, precision: 3) # => $1,234,567,890.506
+ # 1234567890.506.to_s(:currency, locale: :fr) # => 1 234 567 890,51 €
+ # -1234567890.50.to_s(:currency, negative_format: '(%u%n)')
+ # # => ($1,234,567,890.50)
+ # 1234567890.50.to_s(:currency, unit: '£', separator: ',', delimiter: '')
+ # # => £1234567890,50
+ # 1234567890.50.to_s(:currency, unit: '£', separator: ',', delimiter: '', format: '%n %u')
+ # # => 1234567890,50 £
+ #
+ # Percentage:
+ # 100.to_s(:percentage) # => 100.000%
+ # 100.to_s(:percentage, precision: 0) # => 100%
+ # 1000.to_s(:percentage, delimiter: '.', separator: ',') # => 1.000,000%
+ # 302.24398923423.to_s(:percentage, precision: 5) # => 302.24399%
+ # 1000.to_s(:percentage, locale: :fr) # => 1 000,000%
+ # 100.to_s(:percentage, format: '%n %') # => 100 %
+ #
+ # Delimited:
+ # 12345678.to_s(:delimited) # => 12,345,678
+ # 12345678.05.to_s(:delimited) # => 12,345,678.05
+ # 12345678.to_s(:delimited, delimiter: '.') # => 12.345.678
+ # 12345678.to_s(:delimited, delimiter: ',') # => 12,345,678
+ # 12345678.05.to_s(:delimited, separator: ' ') # => 12,345,678 05
+ # 12345678.05.to_s(:delimited, locale: :fr) # => 12 345 678,05
+ # 98765432.98.to_s(:delimited, delimiter: ' ', separator: ',')
+ # # => 98 765 432,98
+ #
+ # Rounded:
+ # 111.2345.to_s(:rounded) # => 111.235
+ # 111.2345.to_s(:rounded, precision: 2) # => 111.23
+ # 13.to_s(:rounded, precision: 5) # => 13.00000
+ # 389.32314.to_s(:rounded, precision: 0) # => 389
+ # 111.2345.to_s(:rounded, significant: true) # => 111
+ # 111.2345.to_s(:rounded, precision: 1, significant: true) # => 100
+ # 13.to_s(:rounded, precision: 5, significant: true) # => 13.000
+ # 111.234.to_s(:rounded, locale: :fr) # => 111,234
+ # 13.to_s(:rounded, precision: 5, significant: true, strip_insignificant_zeros: true)
+ # # => 13
+ # 389.32314.to_s(:rounded, precision: 4, significant: true) # => 389.3
+ # 1111.2345.to_s(:rounded, precision: 2, separator: ',', delimiter: '.')
+ # # => 1.111,23
+ #
+ # Human-friendly size in Bytes:
+ # 123.to_s(:human_size) # => 123 Bytes
+ # 1234.to_s(:human_size) # => 1.21 KB
+ # 12345.to_s(:human_size) # => 12.1 KB
+ # 1234567.to_s(:human_size) # => 1.18 MB
+ # 1234567890.to_s(:human_size) # => 1.15 GB
+ # 1234567890123.to_s(:human_size) # => 1.12 TB
+ # 1234567.to_s(:human_size, precision: 2) # => 1.2 MB
+ # 483989.to_s(:human_size, precision: 2) # => 470 KB
+ # 1234567.to_s(:human_size, precision: 2, separator: ',') # => 1,2 MB
+ # 1234567890123.to_s(:human_size, precision: 5) # => "1.1229 TB"
+ # 524288000.to_s(:human_size, precision: 5) # => "500 MB"
+ #
+ # Human-friendly format:
+ # 123.to_s(:human) # => "123"
+ # 1234.to_s(:human) # => "1.23 Thousand"
+ # 12345.to_s(:human) # => "12.3 Thousand"
+ # 1234567.to_s(:human) # => "1.23 Million"
+ # 1234567890.to_s(:human) # => "1.23 Billion"
+ # 1234567890123.to_s(:human) # => "1.23 Trillion"
+ # 1234567890123456.to_s(:human) # => "1.23 Quadrillion"
+ # 1234567890123456789.to_s(:human) # => "1230 Quadrillion"
+ # 489939.to_s(:human, precision: 2) # => "490 Thousand"
+ # 489939.to_s(:human, precision: 4) # => "489.9 Thousand"
+ # 1234567.to_s(:human, precision: 4,
+ # significant: false) # => "1.2346 Million"
+ # 1234567.to_s(:human, precision: 1,
+ # separator: ',',
+ # significant: false) # => "1,2 Million"
+ def to_formatted_s(format = :default, options = {})
+ case format
+ when :phone
+ return ActiveSupport::NumberHelper.number_to_phone(self, options)
+ when :currency
+ return ActiveSupport::NumberHelper.number_to_currency(self, options)
+ when :percentage
+ return ActiveSupport::NumberHelper.number_to_percentage(self, options)
+ when :delimited
+ return ActiveSupport::NumberHelper.number_to_delimited(self, options)
+ when :rounded
+ return ActiveSupport::NumberHelper.number_to_rounded(self, options)
+ when :human
+ return ActiveSupport::NumberHelper.number_to_human(self, options)
+ when :human_size
+ return ActiveSupport::NumberHelper.number_to_human_size(self, options)
+ else
+ self.to_default_s
+ end
+ end
+
+ [Float, Fixnum, Bignum, BigDecimal].each do |klass|
+ klass.send(:alias_method, :to_default_s, :to_s)
+
+ klass.send(:define_method, :to_s) do |*args|
+ if args[0].is_a?(Symbol)
+ format = args[0]
+ options = args[1] || {}
+
+ self.to_formatted_s(format, options)
+ else
+ to_default_s(*args)
+ end
+ end
+ end
+end
diff --git a/lib/active_support/core_ext/numeric/time.rb b/lib/active_support/core_ext/numeric/time.rb
index 58a03d5..87b9a23 100644
--- a/lib/active_support/core_ext/numeric/time.rb
+++ b/lib/active_support/core_ext/numeric/time.rb
@@ -8,13 +8,13 @@ class Numeric
# These methods use Time#advance for precise date calculations when using from_now, ago, etc.
# as well as adding or subtracting their results from a Time object. For example:
#
- # # equivalent to Time.now.advance(:months => 1)
+ # # equivalent to Time.current.advance(months: 1)
# 1.month.from_now
#
- # # equivalent to Time.now.advance(:years => 2)
+ # # equivalent to Time.current.advance(years: 2)
# 2.years.from_now
#
- # # equivalent to Time.now.advance(:months => 4, :years => 5)
+ # # equivalent to Time.current.advance(months: 4, years: 5)
# (4.months + 5.years).from_now
#
# While these methods provide precise calculation when used as in the examples above, care
@@ -28,9 +28,9 @@ class Numeric
# 1.year.to_f.from_now
#
# In such cases, Ruby's core
- # Date[http://stdlib.rubyonrails.org/libdoc/date/rdoc/index.html] and
- # Time[http://stdlib.rubyonrails.org/libdoc/time/rdoc/index.html] should be used for precision
- # date and time arithmetic
+ # Date[http://ruby-doc.org/stdlib/libdoc/date/rdoc/Date.html] and
+ # Time[http://ruby-doc.org/stdlib/libdoc/time/rdoc/Time.html] should be used for precision
+ # date and time arithmetic.
def seconds
ActiveSupport::Duration.new(self, [[:seconds, self]])
end
diff --git a/lib/active_support/core_ext/object.rb b/lib/active_support/core_ext/object.rb
index 9ad1e12..ec21572 100644
--- a/lib/active_support/core_ext/object.rb
+++ b/lib/active_support/core_ext/object.rb
@@ -1,6 +1,7 @@
require 'active_support/core_ext/object/acts_like'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/object/duplicable'
+require 'active_support/core_ext/object/deep_dup'
require 'active_support/core_ext/object/try'
require 'active_support/core_ext/object/inclusion'
diff --git a/lib/active_support/core_ext/object/acts_like.rb b/lib/active_support/core_ext/object/acts_like.rb
index fcc8e50..3912cc5 100644
--- a/lib/active_support/core_ext/object/acts_like.rb
+++ b/lib/active_support/core_ext/object/acts_like.rb
@@ -1,9 +1,9 @@
class Object
# A duck-type assistant method. For example, Active Support extends Date
- # to define an acts_like_date? method, and extends Time to define
- # acts_like_time?. As a result, we can do "x.acts_like?(:time)" and
- # "x.acts_like?(:date)" to do duck-type-safe comparisons, since classes that
- # we want to act like Time simply need to define an acts_like_time? method.
+ # to define an <tt>acts_like_date?</tt> method, and extends Time to define
+ # <tt>acts_like_time?</tt>. As a result, we can do <tt>x.acts_like?(:time)</tt> and
+ # <tt>x.acts_like?(:date)</tt> to do duck-type-safe comparisons, since classes that
+ # we want to act like Time simply need to define an <tt>acts_like_time?</tt> method.
def acts_like?(duck)
respond_to? :"acts_like_#{duck}?"
end
diff --git a/lib/active_support/core_ext/object/blank.rb b/lib/active_support/core_ext/object/blank.rb
index fe27f45..8a5eb4b 100644
--- a/lib/active_support/core_ext/object/blank.rb
+++ b/lib/active_support/core_ext/object/blank.rb
@@ -1,9 +1,8 @@
# encoding: utf-8
-require 'active_support/core_ext/string/encoding'
class Object
# An object is blank if it's false, empty, or a whitespace string.
- # For example, "", " ", +nil+, [], and {} are all blank.
+ # For example, '', ' ', +nil+, [], and {} are all blank.
#
# This simplifies:
#
@@ -44,7 +43,6 @@ class NilClass
# +nil+ is blank:
#
# nil.blank? # => true
- #
def blank?
true
end
@@ -54,7 +52,6 @@ class FalseClass
# +false+ is blank:
#
# false.blank? # => true
- #
def blank?
true
end
@@ -64,7 +61,6 @@ class TrueClass
# +true+ is not blank:
#
# true.blank? # => false
- #
def blank?
false
end
@@ -75,7 +71,6 @@ class Array
#
# [].blank? # => true
# [1,2,3].blank? # => false
- #
alias_method :blank?, :empty?
end
@@ -83,29 +78,19 @@ class Hash
# A hash is blank if it's empty:
#
# {}.blank? # => true
- # {:key => 'value'}.blank? # => false
- #
+ # { key: 'value' }.blank? # => false
alias_method :blank?, :empty?
end
class String
- # 0x3000: fullwidth whitespace
- NON_WHITESPACE_REGEXP = %r![^\s#{[0x3000].pack("U")}]!
-
# 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
+ # ' '.blank? # => true
+ # ' something here '.blank? # => false
def blank?
- # 1.8 does not takes [:space:] properly
- if encoding_aware?
- self !~ /[^[:space:]]/
- else
- self !~ NON_WHITESPACE_REGEXP
- end
+ self !~ /[^[:space:]]/
end
end
@@ -114,7 +99,6 @@ class Numeric #:nodoc:
#
# 1.blank? # => false
# 0.blank? # => false
- #
def blank?
false
end
diff --git a/lib/active_support/core_ext/object/deep_dup.rb b/lib/active_support/core_ext/object/deep_dup.rb
new file mode 100644
index 0000000..1d639f3
--- /dev/null
+++ b/lib/active_support/core_ext/object/deep_dup.rb
@@ -0,0 +1,46 @@
+require 'active_support/core_ext/object/duplicable'
+
+class Object
+ # Returns a deep copy of object if it's duplicable. If it's
+ # not duplicable, returns +self+.
+ #
+ # object = Object.new
+ # dup = object.deep_dup
+ # dup.instance_variable_set(:@a, 1)
+ #
+ # object.instance_variable_defined?(:@a) #=> false
+ # dup.instance_variable_defined?(:@a) #=> true
+ def deep_dup
+ duplicable? ? dup : self
+ end
+end
+
+class Array
+ # Returns a deep copy of array.
+ #
+ # array = [1, [2, 3]]
+ # dup = array.deep_dup
+ # dup[1][2] = 4
+ #
+ # array[1][2] #=> nil
+ # dup[1][2] #=> 4
+ def deep_dup
+ map { |it| it.deep_dup }
+ end
+end
+
+class Hash
+ # Returns a deep copy of hash.
+ #
+ # hash = { a: { b: 'b' } }
+ # dup = hash.deep_dup
+ # 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
+ end
+ end
+end
diff --git a/lib/active_support/core_ext/object/duplicable.rb b/lib/active_support/core_ext/object/duplicable.rb
index 9d1630b..9cd7485 100644
--- a/lib/active_support/core_ext/object/duplicable.rb
+++ b/lib/active_support/core_ext/object/duplicable.rb
@@ -19,7 +19,7 @@
class Object
# Can you safely dup this object?
#
- # False for +nil+, +false+, +true+, symbols, numbers, class and module objects;
+ # False for +nil+, +false+, +true+, symbol, and number objects;
# true otherwise.
def duplicable?
true
@@ -31,7 +31,6 @@ class NilClass
#
# nil.duplicable? # => false
# nil.dup # => TypeError: can't dup NilClass
- #
def duplicable?
false
end
@@ -42,7 +41,6 @@ class FalseClass
#
# false.duplicable? # => false
# false.dup # => TypeError: can't dup FalseClass
- #
def duplicable?
false
end
@@ -53,7 +51,6 @@ class TrueClass
#
# true.duplicable? # => false
# true.dup # => TypeError: can't dup TrueClass
- #
def duplicable?
false
end
@@ -64,7 +61,6 @@ class Symbol
#
# :my_symbol.duplicable? # => false
# :my_symbol.dup # => TypeError: can't dup Symbol
- #
def duplicable?
false
end
@@ -75,31 +71,6 @@ class Numeric
#
# 3.duplicable? # => false
# 3.dup # => TypeError: can't dup Fixnum
- #
- def duplicable?
- false
- end
-end
-
-class Class
- # Classes are not duplicable:
- #
- # c = Class.new # => #<Class:0x10328fd80>
- # c.dup # => #<Class:0x10328fd80>
- #
- # Note +dup+ returned the same class object.
- def duplicable?
- false
- end
-end
-
-class Module
- # Modules are not duplicable:
- #
- # m = Module.new # => #<Module:0x10328b6e0>
- # m.dup # => #<Module:0x10328b6e0>
- #
- # Note +dup+ returned the same module object.
def duplicable?
false
end
diff --git a/lib/active_support/core_ext/object/inclusion.rb b/lib/active_support/core_ext/object/inclusion.rb
index f611cdd..5ea5f84 100644
--- a/lib/active_support/core_ext/object/inclusion.rb
+++ b/lib/active_support/core_ext/object/inclusion.rb
@@ -1,24 +1,25 @@
+require 'active_support/deprecation'
+
class Object
- # Returns true if this object is included in the argument(s). Argument must be
- # any object which responds to +#include?+ or optionally, multiple arguments can be passed in. Usage:
+ # Returns true if this object is included in the argument. Argument must be
+ # any object which responds to +#include?+. Usage:
#
# characters = ["Konata", "Kagami", "Tsukasa"]
# "Konata".in?(characters) # => true
- #
- # character = "Konata"
- # character.in?("Konata", "Kagami", "Tsukasa") # => true
#
- # This will throw an ArgumentError if a single argument is passed in and it doesn't respond
+ # This will throw an ArgumentError if the argument doesn't respond
# to +#include?+.
def in?(*args)
if args.length > 1
+ ActiveSupport::Deprecation.warn "Calling #in? with multiple arguments is" \
+ " deprecated, please pass in an object that responds to #include? instead."
args.include? self
else
another_object = args.first
if another_object.respond_to? :include?
another_object.include? self
else
- raise ArgumentError.new("The single parameter passed to #in? must respond to #include?")
+ raise ArgumentError.new 'The single parameter passed to #in? must respond to #include?'
end
end
end
diff --git a/lib/active_support/core_ext/object/instance_variables.rb b/lib/active_support/core_ext/object/instance_variables.rb
index eda9694..755e1c6 100644
--- a/lib/active_support/core_ext/object/instance_variables.rb
+++ b/lib/active_support/core_ext/object/instance_variables.rb
@@ -1,6 +1,6 @@
class Object
- # Returns a hash that maps instance variable names without "@" to their
- # corresponding values. Keys are strings both in Ruby 1.8 and 1.9.
+ # Returns a hash with string keys that maps instance variable names without "@" to their
+ # corresponding values.
#
# class C
# def initialize(x, y)
@@ -9,12 +9,11 @@ class Object
# end
#
# C.new(0, 1).instance_values # => {"x" => 0, "y" => 1}
- def instance_values #:nodoc:
- Hash[instance_variables.map { |name| [name.to_s[1..-1], instance_variable_get(name)] }]
+ def instance_values
+ Hash[instance_variables.map { |name| [name[1..-1], instance_variable_get(name)] }]
end
- # Returns an array of instance variable names including "@". They are strings
- # both in Ruby 1.8 and 1.9.
+ # Returns an array of instance variable names as strings including "@".
#
# class C
# def initialize(x, y)
@@ -23,11 +22,7 @@ class Object
# end
#
# C.new(0, 1).instance_variable_names # => ["@y", "@x"]
- if RUBY_VERSION >= '1.9'
- def instance_variable_names
- instance_variables.map { |var| var.to_s }
- end
- else
- alias_method :instance_variable_names, :instance_variables
+ def instance_variable_names
+ instance_variables.map { |var| var.to_s }
end
end
diff --git a/lib/active_support/core_ext/object/to_json.rb b/lib/active_support/core_ext/object/to_json.rb
index e7dc60a..83cc806 100644
--- a/lib/active_support/core_ext/object/to_json.rb
+++ b/lib/active_support/core_ext/object/to_json.rb
@@ -17,3 +17,11 @@ end
end
end
end
+
+module Process
+ class Status
+ def as_json(options = nil)
+ { :exitstatus => exitstatus, :pid => pid }
+ end
+ end
+end
diff --git a/lib/active_support/core_ext/object/to_param.rb b/lib/active_support/core_ext/object/to_param.rb
index e5f8107..0d5f350 100644
--- a/lib/active_support/core_ext/object/to_param.rb
+++ b/lib/active_support/core_ext/object/to_param.rb
@@ -6,18 +6,21 @@ class Object
end
class NilClass
+ # Returns +self+.
def to_param
self
end
end
class TrueClass
+ # Returns +self+.
def to_param
self
end
end
class FalseClass
+ # Returns +self+.
def to_param
self
end
@@ -35,12 +38,12 @@ class Hash
# Returns a string representation of the receiver suitable for use as a URL
# query string:
#
- # {:name => 'David', :nationality => 'Danish'}.to_param
+ # {name: 'David', nationality: 'Danish'}.to_param
# # => "name=David&nationality=Danish"
#
# An optional namespace can be passed to enclose the param names:
#
- # {:name => 'David', :nationality => 'Danish'}.to_param('user')
+ # {name: 'David', nationality: 'Danish'}.to_param('user')
# # => "user[name]=David&user[nationality]=Danish"
#
# The string pairs "key=value" that conform the query string
diff --git a/lib/active_support/core_ext/object/try.rb b/lib/active_support/core_ext/object/try.rb
index 5fb2e31..534bbe3 100644
--- a/lib/active_support/core_ext/object/try.rb
+++ b/lib/active_support/core_ext/object/try.rb
@@ -1,39 +1,58 @@
class Object
- # Invokes the method identified by the symbol +method+, passing it any arguments
- # and/or the block specified, just like the regular Ruby <tt>Object#send</tt> does.
+ # Invokes the public method whose name goes as first argument just like
+ # +public_send+ does, except that if the receiver does not respond to it the
+ # call returns +nil+ rather than raising an exception.
#
- # *Unlike* that method however, a +NoMethodError+ exception will *not* be raised
- # and +nil+ will be returned instead, if the receiving object is a +nil+ object or NilClass.
+ # This method is defined to be able to write
#
- # If try is called without a method to call, it will yield any given block with the object.
- #
- # Please also note that +try+ is defined on +Object+, therefore it won't work with
- # subclasses of +BasicObject+. For example, using try with +SimpleDelegator+ will
- # delegate +try+ to target instead of calling it on delegator itself.
+ # @person.try(:name)
#
- # ==== Examples
+ # instead of
#
- # Without +try+
- # @person && @person.name
- # or
# @person ? @person.name : nil
#
- # With +try+
- # @person.try(:name)
+ # +try+ returns +nil+ when called on +nil+ regardless of whether it responds
+ # to the method:
+ #
+ # nil.try(:to_i) # => nil, rather than 0
+ #
+ # Arguments and blocks are forwarded to the method if invoked:
+ #
+ # @posts.try(:each_slice, 2) do |a, b|
+ # ...
+ # end
+ #
+ # The number of arguments in the signature must match. If the object responds
+ # to the method the call is attempted and +ArgumentError+ is still raised
+ # otherwise.
+ #
+ # If +try+ is called without arguments it yields the receiver to a given
+ # block unless it is +nil+:
#
- # +try+ also accepts arguments and/or a block, for the method it is trying
- # Person.try(:find, 1)
- # @people.try(:collect) {|p| p.name}
+ # @person.try do |p|
+ # ...
+ # end
#
- # Without a method argument try will yield to the block unless the receiver is nil.
- # @person.try { |p| "#{p.first_name} #{p.last_name}" }
- #--
- # +try+ behaves like +Object#send+, unless called on +NilClass+.
+ # Please also note that +try+ is defined on +Object+, therefore it won't work
+ # with instances of classes that do not have +Object+ among their ancestors,
+ # like direct subclasses of +BasicObject+. For example, using +try+ with
+ # +SimpleDelegator+ will delegate +try+ to the target instead of calling it on
+ # delegator itself.
def try(*a, &b)
if a.empty? && block_given?
yield self
else
- __send__(*a, &b)
+ public_send(*a, &b) if respond_to?(a.first)
+ end
+ end
+
+ # Same as #try, but will raise a NoMethodError exception if the receiving is not nil and
+ # does not implemented the tried method.
+ def try!(*a, &b)
+ if a.empty? && block_given?
+ yield self
+ else
+ public_send(*a, &b)
end
end
end
@@ -42,8 +61,6 @@ class NilClass
# Calling +try+ on +nil+ always returns +nil+.
# It becomes specially helpful when navigating through associations that may return +nil+.
#
- # === Examples
- #
# nil.try(:name) # => nil
#
# Without +try+
@@ -54,4 +71,8 @@ class NilClass
def try(*args)
nil
end
+
+ def try!(*args)
+ nil
+ end
end
diff --git a/lib/active_support/core_ext/object/with_options.rb b/lib/active_support/core_ext/object/with_options.rb
index 1397142..42e388b 100644
--- a/lib/active_support/core_ext/object/with_options.rb
+++ b/lib/active_support/core_ext/object/with_options.rb
@@ -10,16 +10,16 @@ class Object
# Without <tt>with_options></tt>, this code contains duplication:
#
# class Account < ActiveRecord::Base
- # has_many :customers, :dependent => :destroy
- # has_many :products, :dependent => :destroy
- # has_many :invoices, :dependent => :destroy
- # has_many :expenses, :dependent => :destroy
+ # has_many :customers, dependent: :destroy
+ # has_many :products, dependent: :destroy
+ # has_many :invoices, dependent: :destroy
+ # has_many :expenses, dependent: :destroy
# end
#
# Using <tt>with_options</tt>, we can remove the duplication:
#
# class Account < ActiveRecord::Base
- # with_options :dependent => :destroy do |assoc|
+ # with_options dependent: :destroy do |assoc|
# assoc.has_many :customers
# assoc.has_many :products
# assoc.has_many :invoices
@@ -29,14 +29,13 @@ class Object
#
# It can also be used with an explicit receiver:
#
- # I18n.with_options :locale => user.locale, :scope => "newsletter" do |i18n|
+ # I18n.with_options locale: user.locale, scope: 'newsletter' do |i18n|
# subject i18n.t :subject
- # body i18n.t :body, :user_name => user.name
+ # body i18n.t :body, user_name: user.name
# end
#
# <tt>with_options</tt> can also be nested since the call is forwarded to its receiver.
# Each nesting level will merge inherited defaults in addition to their own.
- #
def with_options(options)
yield ActiveSupport::OptionMerger.new(self, options)
end
diff --git a/lib/active_support/core_ext/proc.rb b/lib/active_support/core_ext/proc.rb
index 94bb5fb..166c385 100644
--- a/lib/active_support/core_ext/proc.rb
+++ b/lib/active_support/core_ext/proc.rb
@@ -1,7 +1,10 @@
require "active_support/core_ext/kernel/singleton_class"
+require "active_support/deprecation"
class Proc #:nodoc:
def bind(object)
+ ActiveSupport::Deprecation.warn 'Proc#bind is deprecated and will be removed in future versions'
+
block, time = self, Time.now
object.class_eval do
method_name = "__bind_#{time.to_i}_#{time.usec}"
diff --git a/lib/active_support/core_ext/process.rb b/lib/active_support/core_ext/process.rb
deleted file mode 100644
index 0b0bc6d..0000000
--- a/lib/active_support/core_ext/process.rb
+++ /dev/null
@@ -1 +0,0 @@
-require 'active_support/core_ext/process/daemon'
diff --git a/lib/active_support/core_ext/process/daemon.rb b/lib/active_support/core_ext/process/daemon.rb
deleted file mode 100644
index f5202dd..0000000
--- a/lib/active_support/core_ext/process/daemon.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-module Process
- def self.daemon(nochdir = nil, noclose = nil)
- exit if fork # Parent exits, child continues.
- Process.setsid # Become session leader.
- exit if fork # Zap session leader. See [1].
-
- unless nochdir
- Dir.chdir "/" # Release old working directory.
- end
-
- File.umask 0000 # Ensure sensible umask. Adjust as needed.
-
- unless noclose
- STDIN.reopen "/dev/null" # Free file descriptors and
- STDOUT.reopen "/dev/null", "a" # point them somewhere sensible.
- STDERR.reopen '/dev/null', 'a'
- end
-
- trap("TERM") { exit }
-
- return 0
- end unless respond_to?(:daemon)
-end
diff --git a/lib/active_support/core_ext/range.rb b/lib/active_support/core_ext/range.rb
index 2428a02..9368e81 100644
--- a/lib/active_support/core_ext/range.rb
+++ b/lib/active_support/core_ext/range.rb
@@ -1,5 +1,4 @@
-require 'active_support/core_ext/range/blockless_step'
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/cover'
+require 'active_support/core_ext/range/each'
diff --git a/lib/active_support/core_ext/range/blockless_step.rb b/lib/active_support/core_ext/range/blockless_step.rb
deleted file mode 100644
index db42ef5..0000000
--- a/lib/active_support/core_ext/range/blockless_step.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-require 'active_support/core_ext/module/aliasing'
-
-class Range
- begin
- (1..2).step
- # Range#step doesn't return an Enumerator
- rescue LocalJumpError
- # Return an array when step is called without a block.
- def step_with_blockless(*args, &block)
- if block_given?
- step_without_blockless(*args, &block)
- else
- array = []
- step_without_blockless(*args) { |step| array << step }
- array
- end
- end
- else
- def step_with_blockless(*args, &block) #:nodoc:
- if block_given?
- step_without_blockless(*args, &block)
- else
- step_without_blockless(*args).to_a
- end
- end
- end
-
- alias_method_chain :step, :blockless
-end
diff --git a/lib/active_support/core_ext/range/conversions.rb b/lib/active_support/core_ext/range/conversions.rb
index 43134b4..b1a1278 100644
--- a/lib/active_support/core_ext/range/conversions.rb
+++ b/lib/active_support/core_ext/range/conversions.rb
@@ -5,8 +5,6 @@ class Range
# Gives a human readable format of the range.
#
- # ==== Example
- #
# (1..100).to_formatted_s # => "1..100"
def to_formatted_s(format = :default)
if formatter = RANGE_FORMATS[format]
diff --git a/lib/active_support/core_ext/range/cover.rb b/lib/active_support/core_ext/range/cover.rb
deleted file mode 100644
index 3a182cd..0000000
--- a/lib/active_support/core_ext/range/cover.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-class Range
- alias_method(:cover?, :include?) unless instance_methods.include?(:cover?)
-end
diff --git a/lib/active_support/core_ext/range/each.rb b/lib/active_support/core_ext/range/each.rb
new file mode 100644
index 0000000..d51ea2e
--- /dev/null
+++ b/lib/active_support/core_ext/range/each.rb
@@ -0,0 +1,24 @@
+require 'active_support/core_ext/module/aliasing'
+require 'active_support/core_ext/object/acts_like'
+
+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.acts_like?(:time)
+ raise TypeError, "can't iterate from #{first.class}"
+ end
+ end
+end
diff --git a/lib/active_support/core_ext/range/include_range.rb b/lib/active_support/core_ext/range/include_range.rb
index 684b7cb..3a07401 100644
--- a/lib/active_support/core_ext/range/include_range.rb
+++ b/lib/active_support/core_ext/range/include_range.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/module/aliasing'
+
class Range
# Extends the default Range#include? to support range comparisons.
# (1..5).include?(1..5) # => true
@@ -5,7 +7,7 @@ class Range
# (1..5).include?(2..6) # => false
#
# The native Range#include? behavior is untouched.
- # ("a".."f").include?("c") # => true
+ # ('a'..'f').include?('c') # => true
# (5..9).include?(11) # => false
def include_with_range?(value)
if value.is_a?(::Range)
diff --git a/lib/active_support/core_ext/range/overlaps.rb b/lib/active_support/core_ext/range/overlaps.rb
index 7df653b..603657c 100644
--- a/lib/active_support/core_ext/range/overlaps.rb
+++ b/lib/active_support/core_ext/range/overlaps.rb
@@ -3,6 +3,6 @@ class Range
# (1..5).overlaps?(4..6) # => true
# (1..5).overlaps?(7..9) # => false
def overlaps?(other)
- include?(other.first) || other.include?(first)
+ cover?(other.first) || other.cover?(first)
end
end
diff --git a/lib/active_support/core_ext/rexml.rb b/lib/active_support/core_ext/rexml.rb
deleted file mode 100644
index 0419ebc..0000000
--- a/lib/active_support/core_ext/rexml.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-require 'active_support/core_ext/kernel/reporting'
-
-# Fixes the rexml vulnerability disclosed at:
-# http://www.ruby-lang.org/en/news/2008/08/23/dos-vulnerability-in-rexml/
-# This fix is identical to rexml-expansion-fix version 1.0.1.
-#
-# We still need to distribute this fix because albeit the REXML
-# in recent 1.8.7s is patched, it wasn't in early patchlevels.
-require 'rexml/rexml'
-
-# Earlier versions of rexml defined REXML::Version, newer ones REXML::VERSION
-unless (defined?(REXML::VERSION) ? REXML::VERSION : REXML::Version) > "3.1.7.2"
- silence_warnings { require 'rexml/document' }
-
- # REXML in 1.8.7 has the patch but early patchlevels didn't update Version from 3.1.7.2.
- unless REXML::Document.respond_to?(:entity_expansion_limit=)
- silence_warnings { require 'rexml/entity' }
-
- module REXML #:nodoc:
- class Entity < Child #:nodoc:
- undef_method :unnormalized
- def unnormalized
- document.record_entity_expansion! if document
- v = value()
- return nil if v.nil?
- @unnormalized = Text::unnormalize(v, parent)
- @unnormalized
- end
- end
- class Document < Element #:nodoc:
- @@entity_expansion_limit = 10_000
- def self.entity_expansion_limit= val
- @@entity_expansion_limit = val
- end
-
- def record_entity_expansion!
- @number_of_expansions ||= 0
- @number_of_expansions += 1
- if @number_of_expansions > @@entity_expansion_limit
- raise "Number of entity expansions exceeded, processing aborted."
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_support/core_ext/string.rb b/lib/active_support/core_ext/string.rb
index 72522d3..c656db2 100644
--- a/lib/active_support/core_ext/string.rb
+++ b/lib/active_support/core_ext/string.rb
@@ -4,11 +4,10 @@ require 'active_support/core_ext/string/multibyte'
require 'active_support/core_ext/string/starts_ends_with'
require 'active_support/core_ext/string/inflections'
require 'active_support/core_ext/string/access'
-require 'active_support/core_ext/string/xchar'
require 'active_support/core_ext/string/behavior'
-require 'active_support/core_ext/string/interpolation'
require 'active_support/core_ext/string/output_safety'
require 'active_support/core_ext/string/exclude'
-require 'active_support/core_ext/string/encoding'
require 'active_support/core_ext/string/strip'
require 'active_support/core_ext/string/inquiry'
+require 'active_support/core_ext/string/indent'
+require 'active_support/core_ext/string/zones'
diff --git a/lib/active_support/core_ext/string/access.rb b/lib/active_support/core_ext/string/access.rb
index c0d5cdf..8fa8157 100644
--- a/lib/active_support/core_ext/string/access.rb
+++ b/lib/active_support/core_ext/string/access.rb
@@ -1,99 +1,104 @@
-require "active_support/multibyte"
-
class String
- unless '1.9'.respond_to?(:force_encoding)
- # Returns the character at the +position+ treating the string as an array (where 0 is the first character).
- #
- # Examples:
- # "hello".at(0) # => "h"
- # "hello".at(4) # => "o"
- # "hello".at(10) # => ERROR if < 1.9, nil in 1.9
- def at(position)
- mb_chars[position, 1].to_s
- end
-
- # Returns the remaining of the string from the +position+ treating the string as an array (where 0 is the first character).
- #
- # Examples:
- # "hello".from(0) # => "hello"
- # "hello".from(2) # => "llo"
- # "hello".from(10) # => "" if < 1.9, nil in 1.9
- def from(position)
- mb_chars[position..-1].to_s
- end
-
- # Returns the beginning of the string up to the +position+ treating the string as an array (where 0 is the first character).
- #
- # Examples:
- # "hello".to(0) # => "h"
- # "hello".to(2) # => "hel"
- # "hello".to(10) # => "hello"
- def to(position)
- mb_chars[0..position].to_s
- end
-
- # Returns the first character of the string or the first +limit+ characters.
- #
- # Examples:
- # "hello".first # => "h"
- # "hello".first(2) # => "he"
- # "hello".first(10) # => "hello"
- def first(limit = 1)
- if limit == 0
- ''
- elsif limit >= size
- self
- else
- mb_chars[0...limit].to_s
- end
- end
-
- # Returns the last character of the string or the last +limit+ characters.
- #
- # Examples:
- # "hello".last # => "o"
- # "hello".last(2) # => "lo"
- # "hello".last(10) # => "hello"
- def last(limit = 1)
- if limit == 0
- ''
- elsif limit >= size
- self
- else
- mb_chars[(-limit)..-1].to_s
- end
- end
- else
- def at(position)
- self[position]
- end
+ # If you pass a single Fixnum, returns a substring of one character at that
+ # position. The first character of the string is at position 0, the next at
+ # position 1, and so on. If a range is supplied, a substring containing
+ # characters at offsets given by the range is returned. In both cases, if an
+ # offset is negative, it is counted from the end of the string. Returns nil
+ # if the initial offset falls outside the string. Returns an empty string if
+ # 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) #=> ""
+ #
+ # 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
+ def at(position)
+ self[position]
+ end
- def from(position)
- self[position..-1]
- end
+ # Returns a substring from the given position to the end of the 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"
+ #
+ # 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"
+ def from(position)
+ self[position..-1]
+ end
- def to(position)
- self[0..position]
- end
+ # Returns a substring from the beginning of the string to the given position.
+ # 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"
+ #
+ # 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"
+ def to(position)
+ self[0..position]
+ end
- def first(limit = 1)
- if limit == 0
- ''
- elsif limit >= size
- self
- else
- to(limit - 1)
- end
+ # Returns the first character. If a limit is supplied, returns a substring
+ # from the beginning of the string until it reaches the limit value. If the
+ # given limit is greater than or equal to the string length, returns self.
+ #
+ # str = "hello"
+ # str.first #=> "h"
+ # str.first(1) #=> "h"
+ # str.first(2) #=> "he"
+ # str.first(0) #=> ""
+ # str.first(6) #=> "hello"
+ def first(limit = 1)
+ if limit == 0
+ ''
+ elsif limit >= size
+ self
+ else
+ to(limit - 1)
end
+ end
- def last(limit = 1)
- if limit == 0
- ''
- elsif limit >= size
- self
- else
- from(-limit)
- end
+ # Returns the last character of the string. If a limit is supplied, returns a substring
+ # from the end of the string until it reaches the limit value (counting backwards). If
+ # the given limit is greater than or equal to the string length, returns self.
+ #
+ # str = "hello"
+ # str.last #=> "o"
+ # str.last(1) #=> "o"
+ # str.last(2) #=> "lo"
+ # str.last(0) #=> ""
+ # str.last(6) #=> "hello"
+ def last(limit = 1)
+ if limit == 0
+ ''
+ elsif limit >= size
+ self
+ else
+ from(-limit)
end
end
end
diff --git a/lib/active_support/core_ext/string/conversions.rb b/lib/active_support/core_ext/string/conversions.rb
index bce0b98..d2a2db3 100644
--- a/lib/active_support/core_ext/string/conversions.rb
+++ b/lib/active_support/core_ext/string/conversions.rb
@@ -1,43 +1,36 @@
-# encoding: utf-8
require 'date'
-require 'active_support/core_ext/time/publicize_conversion_methods'
require 'active_support/core_ext/time/calculations'
class String
- # Returns the codepoint of the first character of the string, assuming a
- # single-byte character encoding:
+ # Converts a string to a Time value.
+ # The +form+ can be either :utc or :local (default :local).
#
- # "a".ord # => 97
- # "à".ord # => 224, in ISO-8859-1
+ # The time is parsed using Time.parse method.
+ # If +form+ is :local, then the time is in the system timezone.
+ # If the date part is missing then the current date is used and if
+ # the time part is missing then it is assumed to be 00:00:00.
#
- # This method is defined in Ruby 1.8 for Ruby 1.9 forward compatibility on
- # these character encodings.
- #
- # <tt>ActiveSupport::Multibyte::Chars#ord</tt> is forward compatible with
- # Ruby 1.9 on UTF8 strings:
- #
- # "a".mb_chars.ord # => 97
- # "à".mb_chars.ord # => 224, in UTF8
- #
- # Note that the 224 is different in both examples. In ISO-8859-1 "à" is
- # represented as a single byte, 224. In UTF8 it is represented with two
- # bytes, namely 195 and 160, but its Unicode codepoint is 224. If we
- # call +ord+ on the UTF8 string "à" the return value will be 195. That is
- # not an error, because UTF8 is unsupported, the call itself would be
- # bogus.
- def ord
- self[0]
- end unless method_defined?(:ord)
+ # "13-12-2012".to_time # => 2012-12-13 00:00:00 +0100
+ # "06:12".to_time # => 2012-12-13 06:12:00 +0100
+ # "2012-12-13 06:12".to_time # => 2012-12-13 06:12:00 +0100
+ # "2012-12-13T06:12".to_time # => 2012-12-13 06:12:00 +0100
+ # "2012-12-13T06:12".to_time(:utc) # => 2012-12-13 05:12:00 UTC
+ def to_time(form = :local)
+ parts = Date._parse(self, false)
+ return if parts.empty?
- # +getbyte+ backport from Ruby 1.9
- alias_method :getbyte, :[] unless method_defined?(:getbyte)
+ now = Time.now
+ time = Time.new(
+ parts.fetch(:year, now.year),
+ parts.fetch(:mon, now.month),
+ parts.fetch(:mday, now.day),
+ parts.fetch(:hour, 0),
+ parts.fetch(:min, 0),
+ parts.fetch(:sec, 0) + parts.fetch(:sec_fraction, 0),
+ parts.fetch(:offset, form == :utc ? 0 : nil)
+ )
- # Form can be either :utc (default) or :local.
- def to_time(form = :utc)
- return nil if self.blank?
- d = ::Date._parse(self, false).values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset).map { |arg| arg || 0 }
- d[6] *= 1000000
- ::Time.send("#{form}_time", *d[0..6]) - d[7]
+ form == :utc ? time.utc : time.getlocal
end
# Converts a string to a Date value.
@@ -47,8 +40,7 @@ class String
# "2012-12-13".to_date #=> Thu, 13 Dec 2012
# "12/13/2012".to_date #=> ArgumentError: invalid date
def to_date
- return nil if self.blank?
- ::Date.new(*::Date._parse(self, false).values_at(:year, :mon, :mday))
+ ::Date.parse(self, false) unless blank?
end
# Converts a string to a DateTime value.
@@ -58,9 +50,6 @@ class String
# "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
- return nil if self.blank?
- d = ::Date._parse(self, false).values_at(:year, :mon, :mday, :hour, :min, :sec, :zone, :sec_fraction).map { |arg| arg || 0 }
- d[5] += d.pop
- ::DateTime.civil(*d)
+ ::DateTime.parse(self, false) unless blank?
end
end
diff --git a/lib/active_support/core_ext/string/encoding.rb b/lib/active_support/core_ext/string/encoding.rb
index d4781bf..a583b91 100644
--- a/lib/active_support/core_ext/string/encoding.rb
+++ b/lib/active_support/core_ext/string/encoding.rb
@@ -1,11 +1,8 @@
+require 'active_support/deprecation'
+
class String
- if defined?(Encoding) && "".respond_to?(:encode)
- def encoding_aware?
- true
- end
- else
- def encoding_aware?
- false
- end
+ def encoding_aware?
+ ActiveSupport::Deprecation.warn 'String#encoding_aware? is deprecated'
+ true
end
-end
\ No newline at end of file
+end
diff --git a/lib/active_support/core_ext/string/filters.rb b/lib/active_support/core_ext/string/filters.rb
index d478ee0..c62bb41 100644
--- a/lib/active_support/core_ext/string/filters.rb
+++ b/lib/active_support/core_ext/string/filters.rb
@@ -1,11 +1,10 @@
-require 'active_support/core_ext/string/multibyte'
-
class String
# Returns the string, first removing all whitespace on both ends of
# the string, and then changing remaining consecutive whitespace
# groups into one space each.
#
- # Examples:
+ # Note that it handles both ASCII and Unicode whitespace like mongolian vowel separator (U+180E).
+ #
# %{ Multi-line
# string }.squish # => "Multi-line string"
# " foo bar \n \t boo".squish # => "foo bar boo"
@@ -15,35 +14,42 @@ class String
# Performs a destructive squish. See String#squish.
def squish!
- strip!
- gsub!(/\s+/, ' ')
+ gsub!(/\A[[:space:]]+/, '')
+ gsub!(/[[:space:]]+\z/, '')
+ gsub!(/[[:space:]]+/, ' ')
self
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)
+ # 'Once upon a time in a world far far away'.truncate(27)
# # => "Once upon a time in a wo..."
#
- # Pass a <tt>:separator</tt> to truncate +text+ at a natural break:
+ # Pass a string or regexp <tt>:separator</tt> to truncate +text+ at a natural break:
+ #
+ # 'Once upon a time in a world far far away'.truncate(27, separator: ' ')
+ # # => "Once upon a time in a..."
#
- # "Once upon a time in a world far far away".truncate(27, :separator => ' ')
+ # 'Once upon a time in a world far far away'.truncate(27, separator: /\s/)
# # => "Once upon a time in a..."
#
# The last characters will be replaced with the <tt>:omission</tt> string (defaults to "...")
- # for a total length not exceeding <tt>:length</tt>:
+ # for a total length not exceeding <tt>length</tt>:
#
- # "And they found that many people were sleeping better.".truncate(25, :omission => "... (continued)")
+ # 'And they found that many people were sleeping better.'.truncate(25, omission: '... (continued)')
# # => "And they f... (continued)"
- def truncate(length, options = {})
- text = self.dup
- options[:omission] ||= "..."
+ def truncate(truncate_at, options = {})
+ return dup unless length > truncate_at
- length_with_room_for_omission = length - options[:omission].mb_chars.length
- chars = text.mb_chars
- stop = options[:separator] ?
- (chars.rindex(options[:separator].mb_chars, length_with_room_for_omission) || length_with_room_for_omission) : length_with_room_for_omission
+ options[:omission] ||= '...'
+ length_with_room_for_omission = truncate_at - options[:omission].length
+ stop = \
+ if options[:separator]
+ rindex(options[:separator], length_with_room_for_omission) || length_with_room_for_omission
+ else
+ length_with_room_for_omission
+ end
- (chars.length > length ? chars[0...stop] + options[:omission] : text).to_s
+ "#{self[0...stop]}#{options[:omission]}"
end
end
diff --git a/lib/active_support/core_ext/string/indent.rb b/lib/active_support/core_ext/string/indent.rb
new file mode 100644
index 0000000..ce3a69c
--- /dev/null
+++ b/lib/active_support/core_ext/string/indent.rb
@@ -0,0 +1,43 @@
+class String
+ # Same as +indent+, except it indents the receiver in-place.
+ #
+ # Returns the indented string, or +nil+ if there was nothing to indent.
+ def indent!(amount, indent_string=nil, indent_empty_lines=false)
+ indent_string = indent_string || self[/^[ \t]/] || ' '
+ re = indent_empty_lines ? /^/ : /^(?!$)/
+ gsub!(re, indent_string * amount)
+ end
+
+ # Indents the lines in the receiver:
+ #
+ # <<EOS.indent(2)
+ # def some_method
+ # some_code
+ # end
+ # EOS
+ # # =>
+ # def some_method
+ # some_code
+ # end
+ #
+ # The second argument, +indent_string+, specifies which indent string to
+ # use. The default is +nil+, which tells the method to make a guess by
+ # peeking at the first indented line, and fallback to a space if there is
+ # none.
+ #
+ # " foo".indent(2) # => " foo"
+ # "foo\n\t\tbar".indent(2) # => "\t\tfoo\n\t\t\t\tbar"
+ # "foo".indent(2, "\t") # => "\t\tfoo"
+ #
+ # While +indent_string+ is typically one space or tab, it may be any string.
+ #
+ # The third argument, +indent_empty_lines+, is a flag that says whether
+ # empty lines should be indented. Default is false.
+ #
+ # "foo\n\nbar".indent(2) # => " foo\n\n bar"
+ # "foo\n\nbar".indent(2, nil, true) # => " foo\n \n bar"
+ #
+ def indent(amount, indent_string=nil, indent_empty_lines=false)
+ dup.tap {|_| _.indent!(amount, indent_string, indent_empty_lines)}
+ end
+end
diff --git a/lib/active_support/core_ext/string/inflections.rb b/lib/active_support/core_ext/string/inflections.rb
index 48c1004..0b506a6 100644
--- a/lib/active_support/core_ext/string/inflections.rb
+++ b/lib/active_support/core_ext/string/inflections.rb
@@ -4,7 +4,7 @@ require 'active_support/inflector/transliterate'
# String inflections define new methods on the String class to transform names for different purposes.
# For instance, you can figure out the name of a table from the name of a class.
#
-# "ScaleScore".tableize # => "scale_scores"
+# 'ScaleScore'.tableize # => "scale_scores"
#
class String
# Returns the plural form of the word in the string.
@@ -13,43 +13,55 @@ class String
# the singular form will be returned if <tt>count == 1</tt>.
# For any other value of +count+ the plural will be returned.
#
- # ==== Examples
- # "post".pluralize # => "posts"
- # "octopus".pluralize # => "octopi"
- # "sheep".pluralize # => "sheep"
- # "words".pluralize # => "words"
- # "the blue mailman".pluralize # => "the blue mailmen"
- # "CamelOctopus".pluralize # => "CamelOctopi"
- # "apple".pluralize(1) # => "apple"
- # "apple".pluralize(2) # => "apples"
- def pluralize(count = nil)
+ # If the optional parameter +locale+ is specified,
+ # the word will be pluralized as a word of that language.
+ # By default, this parameter is set to <tt>:en</tt>.
+ # You must define your own inflection rules for languages other than English.
+ #
+ # 'post'.pluralize # => "posts"
+ # 'octopus'.pluralize # => "octopi"
+ # 'sheep'.pluralize # => "sheep"
+ # 'words'.pluralize # => "words"
+ # 'the blue mailman'.pluralize # => "the blue mailmen"
+ # 'CamelOctopus'.pluralize # => "CamelOctopi"
+ # 'apple'.pluralize(1) # => "apple"
+ # 'apple'.pluralize(2) # => "apples"
+ # 'ley'.pluralize(:es) # => "leyes"
+ # 'ley'.pluralize(1, :es) # => "ley"
+ def pluralize(count = nil, locale = :en)
+ locale = count if count.is_a?(Symbol)
if count == 1
self
else
- ActiveSupport::Inflector.pluralize(self)
+ ActiveSupport::Inflector.pluralize(self, locale)
end
end
# The reverse of +pluralize+, returns the singular form of a word in a string.
#
- # "posts".singularize # => "post"
- # "octopi".singularize # => "octopus"
- # "sheep".singularize # => "sheep"
- # "word".singularize # => "word"
- # "the blue mailmen".singularize # => "the blue mailman"
- # "CamelOctopi".singularize # => "CamelOctopus"
- def singularize
- ActiveSupport::Inflector.singularize(self)
+ # If the optional parameter +locale+ is specified,
+ # the word will be singularized as a word of that language.
+ # By default, this parameter is set to <tt>:en</tt>.
+ # You must define your own inflection rules for languages other than English.
+ #
+ # 'posts'.singularize # => "post"
+ # 'octopi'.singularize # => "octopus"
+ # 'sheep'.singularize # => "sheep"
+ # 'word'.singularize # => "word"
+ # 'the blue mailmen'.singularize # => "the blue mailman"
+ # 'CamelOctopi'.singularize # => "CamelOctopus"
+ # 'leyes'.singularize(:es) # => "ley"
+ def singularize(locale = :en)
+ ActiveSupport::Inflector.singularize(self, locale)
end
# +constantize+ tries to find a declared constant with the name specified
# in the string. It raises a NameError when the name is not in CamelCase
# or is not initialized. See ActiveSupport::Inflector.constantize
#
- # Examples
- # "Module".constantize # => Module
- # "Class".constantize # => Class
- # "blargle".constantize # => NameError: wrong constant name blargle
+ # 'Module'.constantize # => Module
+ # 'Class'.constantize # => Class
+ # 'blargle'.constantize # => NameError: wrong constant name blargle
def constantize
ActiveSupport::Inflector.constantize(self)
end
@@ -58,10 +70,9 @@ class String
# in the string. It returns nil when the name is not in CamelCase
# or is not initialized. See ActiveSupport::Inflector.safe_constantize
#
- # Examples
- # "Module".safe_constantize # => Module
- # "Class".safe_constantize # => Class
- # "blargle".safe_constantize # => nil
+ # 'Module'.safe_constantize # => Module
+ # 'Class'.safe_constantize # => Class
+ # 'blargle'.safe_constantize # => nil
def safe_constantize
ActiveSupport::Inflector.safe_constantize(self)
end
@@ -71,14 +82,16 @@ class String
#
# +camelize+ will also convert '/' to '::' which is useful for converting paths to namespaces.
#
- # "active_record".camelize # => "ActiveRecord"
- # "active_record".camelize(:lower) # => "activeRecord"
- # "active_record/errors".camelize # => "ActiveRecord::Errors"
- # "active_record/errors".camelize(:lower) # => "activeRecord::Errors"
+ # 'active_record'.camelize # => "ActiveRecord"
+ # 'active_record'.camelize(:lower) # => "activeRecord"
+ # 'active_record/errors'.camelize # => "ActiveRecord::Errors"
+ # 'active_record/errors'.camelize(:lower) # => "activeRecord::Errors"
def camelize(first_letter = :upper)
case first_letter
- when :upper then ActiveSupport::Inflector.camelize(self, true)
- when :lower then ActiveSupport::Inflector.camelize(self, false)
+ when :upper
+ ActiveSupport::Inflector.camelize(self, true)
+ when :lower
+ ActiveSupport::Inflector.camelize(self, false)
end
end
alias_method :camelcase, :camelize
@@ -89,8 +102,8 @@ class String
#
# +titleize+ is also aliased as +titlecase+.
#
- # "man from the boondocks".titleize # => "Man From The Boondocks"
- # "x-men: the last stand".titleize # => "X Men: The Last Stand"
+ # 'man from the boondocks'.titleize # => "Man From The Boondocks"
+ # 'x-men: the last stand'.titleize # => "X Men: The Last Stand"
def titleize
ActiveSupport::Inflector.titleize(self)
end
@@ -100,23 +113,23 @@ class String
#
# +underscore+ will also change '::' to '/' to convert namespaces to paths.
#
- # "ActiveModel".underscore # => "active_model"
- # "ActiveModel::Errors".underscore # => "active_model/errors"
+ # 'ActiveModel'.underscore # => "active_model"
+ # 'ActiveModel::Errors'.underscore # => "active_model/errors"
def underscore
ActiveSupport::Inflector.underscore(self)
end
# Replaces underscores with dashes in the string.
#
- # "puni_puni" # => "puni-puni"
+ # 'puni_puni'.dasherize # => "puni-puni"
def dasherize
ActiveSupport::Inflector.dasherize(self)
end
# Removes the module part from the constant expression in the string.
#
- # "ActiveRecord::CoreExtensions::String::Inflections".demodulize # => "Inflections"
- # "Inflections".demodulize # => "Inflections"
+ # 'ActiveRecord::CoreExtensions::String::Inflections'.demodulize # => "Inflections"
+ # 'Inflections'.demodulize # => "Inflections"
#
# See also +deconstantize+.
def demodulize
@@ -125,11 +138,11 @@ class String
# Removes the rightmost segment from the constant expression in the string.
#
- # "Net::HTTP".deconstantize # => "Net"
- # "::Net::HTTP".deconstantize # => "::Net"
- # "String".deconstantize # => ""
- # "::String".deconstantize # => ""
- # "".deconstantize # => ""
+ # 'Net::HTTP'.deconstantize # => "Net"
+ # '::Net::HTTP'.deconstantize # => "::Net"
+ # 'String'.deconstantize # => ""
+ # '::String'.deconstantize # => ""
+ # ''.deconstantize # => ""
#
# See also +demodulize+.
def deconstantize
@@ -138,8 +151,6 @@ class String
# Replaces special characters in a string so that it may be used as part of a 'pretty' URL.
#
- # ==== Examples
- #
# class Person
# def to_param
# "#{id}-#{name.parameterize}"
@@ -158,9 +169,9 @@ class String
# Creates the name of a table like Rails does for models to table names. This method
# uses the +pluralize+ method on the last word in the string.
#
- # "RawScaledScorer".tableize # => "raw_scaled_scorers"
- # "egg_and_ham".tableize # => "egg_and_hams"
- # "fancyCategory".tableize # => "fancy_categories"
+ # 'RawScaledScorer'.tableize # => "raw_scaled_scorers"
+ # 'egg_and_ham'.tableize # => "egg_and_hams"
+ # 'fancyCategory'.tableize # => "fancy_categories"
def tableize
ActiveSupport::Inflector.tableize(self)
end
@@ -169,12 +180,12 @@ class String
# Note that this returns a string and not a class. (To convert to an actual class
# follow +classify+ with +constantize+.)
#
- # "egg_and_hams".classify # => "EggAndHam"
- # "posts".classify # => "Post"
+ # 'egg_and_hams'.classify # => "EggAndHam"
+ # 'posts'.classify # => "Post"
#
# Singular names are not handled correctly.
#
- # "business".classify # => "Busines"
+ # 'business'.classify # => "Busines"
def classify
ActiveSupport::Inflector.classify(self)
end
@@ -182,8 +193,8 @@ class String
# Capitalizes the first word, turns underscores into spaces, and strips '_id'.
# Like +titleize+, this is meant for creating pretty output.
#
- # "employee_salary" # => "Employee salary"
- # "author_id" # => "Author"
+ # 'employee_salary'.humanize # => "Employee salary"
+ # 'author_id'.humanize # => "Author"
def humanize
ActiveSupport::Inflector.humanize(self)
end
@@ -192,10 +203,9 @@ class String
# +separate_class_name_and_id_with_underscore+ sets whether
# the method should put '_' between the name and 'id'.
#
- # Examples
- # "Message".foreign_key # => "message_id"
- # "Message".foreign_key(false) # => "messageid"
- # "Admin::Post".foreign_key # => "post_id"
+ # 'Message'.foreign_key # => "message_id"
+ # 'Message'.foreign_key(false) # => "messageid"
+ # 'Admin::Post'.foreign_key # => "post_id"
def foreign_key(separate_class_name_and_id_with_underscore = true)
ActiveSupport::Inflector.foreign_key(self, separate_class_name_and_id_with_underscore)
end
diff --git a/lib/active_support/core_ext/string/inquiry.rb b/lib/active_support/core_ext/string/inquiry.rb
index 5f0a017..1dcd949 100644
--- a/lib/active_support/core_ext/string/inquiry.rb
+++ b/lib/active_support/core_ext/string/inquiry.rb
@@ -2,9 +2,9 @@ require 'active_support/string_inquirer'
class String
# Wraps the current string in the <tt>ActiveSupport::StringInquirer</tt> class,
- # which gives you a prettier way to test for equality. Example:
+ # which gives you a prettier way to test for equality.
#
- # env = "production".inquiry
+ # env = 'production'.inquiry
# env.production? # => true
# env.development? # => false
def inquiry
diff --git a/lib/active_support/core_ext/string/interpolation.rb b/lib/active_support/core_ext/string/interpolation.rb
deleted file mode 100644
index 7f764e9..0000000
--- a/lib/active_support/core_ext/string/interpolation.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-require 'active_support/i18n'
-require 'i18n/core_ext/string/interpolate'
diff --git a/lib/active_support/core_ext/string/multibyte.rb b/lib/active_support/core_ext/string/multibyte.rb
index 400db2c..a124202 100644
--- a/lib/active_support/core_ext/string/multibyte.rb
+++ b/lib/active_support/core_ext/string/multibyte.rb
@@ -2,71 +2,48 @@
require 'active_support/multibyte'
class String
- if RUBY_VERSION >= "1.9"
- # == Multibyte proxy
- #
- # +mb_chars+ is a multibyte safe proxy for string methods.
- #
- # In Ruby 1.8 and older it creates and returns an instance of the ActiveSupport::Multibyte::Chars class which
- # encapsulates the original string. A Unicode safe version of all the String methods are defined on this proxy
- # class. If the proxy class doesn't respond to a certain method, it's forwarded to the encapsulated string.
- #
- # name = 'Claus Müller'
- # name.reverse # => "rell??M sualC"
- # name.length # => 13
- #
- # name.mb_chars.reverse.to_s # => "rellüM sualC"
- # name.mb_chars.length # => 12
- #
- # In Ruby 1.9 and newer +mb_chars+ returns +self+ because String is (mostly) encoding aware. This means that
- # it becomes easy to run one version of your code on multiple Ruby versions.
- #
- # == Method chaining
- #
- # All the methods on the Chars proxy which normally return a string will return a Chars object. This allows
- # method chaining on the result of any of these methods.
- #
- # name.mb_chars.reverse.length # => 12
- #
- # == Interoperability and configuration
- #
- # The Chars object tries to be as interchangeable with String objects as possible: sorting and comparing between
- # String and Char work like expected. The bang! methods change the internal string representation in the Chars
- # object. Interoperability problems can be resolved easily with a +to_s+ call.
- #
- # For more information about the methods defined on the Chars proxy see ActiveSupport::Multibyte::Chars. For
- # information about how to change the default Multibyte behavior see ActiveSupport::Multibyte.
- def mb_chars
- if ActiveSupport::Multibyte.proxy_class.consumes?(self)
- ActiveSupport::Multibyte.proxy_class.new(self)
- else
- self
- end
- end
-
- def is_utf8?
- case encoding
- when Encoding::UTF_8
- valid_encoding?
- when Encoding::ASCII_8BIT, Encoding::US_ASCII
- dup.force_encoding(Encoding::UTF_8).valid_encoding?
- else
- false
- end
- end
- else
- def mb_chars
- if ActiveSupport::Multibyte.proxy_class.wants?(self)
- ActiveSupport::Multibyte.proxy_class.new(self)
- else
- self
- end
- end
+ # == Multibyte proxy
+ #
+ # +mb_chars+ is a multibyte safe proxy for string methods.
+ #
+ # It creates and returns an instance of the ActiveSupport::Multibyte::Chars class which
+ # encapsulates the original string. A Unicode safe version of all the String methods are defined on this proxy
+ # class. If the proxy class doesn't respond to a certain method, it's forwarded to the encapsulated string.
+ #
+ # name = 'Claus Müller'
+ # name.reverse # => "rell??M sualC"
+ # name.length # => 13
+ #
+ # name.mb_chars.reverse.to_s # => "rellüM sualC"
+ # name.mb_chars.length # => 12
+ #
+ # == Method chaining
+ #
+ # All the methods on the Chars proxy which normally return a string will return a Chars object. This allows
+ # method chaining on the result of any of these methods.
+ #
+ # name.mb_chars.reverse.length # => 12
+ #
+ # == Interoperability and configuration
+ #
+ # The Chars object tries to be as interchangeable with String objects as possible: sorting and comparing between
+ # String and Char work like expected. The bang! methods change the internal string representation in the Chars
+ # object. Interoperability problems can be resolved easily with a +to_s+ call.
+ #
+ # For more information about the methods defined on the Chars proxy see ActiveSupport::Multibyte::Chars. For
+ # information about how to change the default Multibyte behavior see ActiveSupport::Multibyte.
+ def mb_chars
+ ActiveSupport::Multibyte.proxy_class.new(self)
+ end
- # Returns true if the string has UTF-8 semantics (a String used for purely byte resources is unlikely to have
- # them), returns false otherwise.
- def is_utf8?
- ActiveSupport::Multibyte::Chars.consumes?(self)
+ def is_utf8?
+ case encoding
+ when Encoding::UTF_8
+ valid_encoding?
+ when Encoding::ASCII_8BIT, Encoding::US_ASCII
+ dup.force_encoding(Encoding::UTF_8).valid_encoding?
+ else
+ false
end
end
end
diff --git a/lib/active_support/core_ext/string/output_safety.rb b/lib/active_support/core_ext/string/output_safety.rb
index 0984b25..dc033ed 100644
--- a/lib/active_support/core_ext/string/output_safety.rb
+++ b/lib/active_support/core_ext/string/output_safety.rb
@@ -3,35 +3,25 @@ require 'active_support/core_ext/kernel/singleton_class'
class ERB
module Util
- HTML_ESCAPE = { '&' => '&', '>' => '>', '<' => '<', '"' => '"', "'" => ''' }
+ HTML_ESCAPE = { '&' => '&', '>' => '>', '<' => '<', '"' => '"', "'" => ''' }
JSON_ESCAPE = { '&' => '\u0026', '>' => '\u003E', '<' => '\u003C' }
+ HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+));)/
+ JSON_ESCAPE_REGEXP = /[&"><]/
- if RUBY_VERSION >= '1.9'
- # A utility method for escaping HTML tag characters.
- # This method is also aliased as <tt>h</tt>.
- #
- # In your ERB templates, use this method to escape any unsafe content. For example:
- # <%=h @person.name %>
- #
- # ==== Example:
- # puts html_escape("is a > 0 & a < 10?")
- # # => is a > 0 & a < 10?
- def html_escape(s)
- s = s.to_s
- if s.html_safe?
- s
- else
- s.gsub(/[&"'><]/, HTML_ESCAPE).html_safe
- end
- end
- else
- def html_escape(s) #:nodoc:
- s = s.to_s
- if s.html_safe?
- s
- else
- s.gsub(/[&"'><]/n) { |special| HTML_ESCAPE[special] }.html_safe
- end
+ # A utility method for escaping HTML tag characters.
+ # This method is also aliased as <tt>h</tt>.
+ #
+ # In your ERB templates, use this method to escape any unsafe content. For example:
+ # <%=h @person.name %>
+ #
+ # puts html_escape('is a > 0 & a < 10?')
+ # # => is a > 0 & a < 10?
+ def html_escape(s)
+ s = s.to_s
+ if s.html_safe?
+ s
+ else
+ s.gsub(/[&"'><]/, HTML_ESCAPE).html_safe
end
end
@@ -44,10 +34,24 @@ class ERB
singleton_class.send(:remove_method, :html_escape)
module_function :html_escape
+ # A utility method for escaping HTML without affecting existing escaped entities.
+ #
+ # html_escape_once('1 < 2 & 3')
+ # # => "1 < 2 & 3"
+ #
+ # html_escape_once('<< Accept & Checkout')
+ # # => "<< Accept & Checkout"
+ def html_escape_once(s)
+ result = s.to_s.gsub(HTML_ESCAPE_ONCE_REGEXP, HTML_ESCAPE)
+ s.html_safe? ? result.html_safe : result
+ end
+
+ module_function :html_escape_once
+
# A utility method for escaping HTML entities in JSON strings
# using \uXXXX JavaScript escape sequences for string literals:
#
- # json_escape("is a > 0 & a < 10?")
+ # json_escape('is a > 0 & a < 10?')
# # => is a \u003E 0 \u0026 a \u003C 10?
#
# Note that after this operation is performed the output is not
@@ -55,19 +59,11 @@ class ERB
#
# json_escape('{"name":"john","created_at":"2010-04-28T01:39:31Z","id":1}')
# # => {name:john,created_at:2010-04-28T01:39:31Z,id:1}
- #
- # This method is also aliased as +j+, and available as a helper
- # in Rails templates:
- #
- # <%=j @person.to_json %>
- #
def json_escape(s)
- result = s.to_s.gsub(/[&"><]/) { |special| JSON_ESCAPE[special] }
+ result = s.to_s.gsub(JSON_ESCAPE_REGEXP, JSON_ESCAPE)
s.html_safe? ? result.html_safe : result
end
- alias j json_escape
- module_function :j
module_function :json_escape
end
end
@@ -86,26 +82,31 @@ end
module ActiveSupport #:nodoc:
class SafeBuffer < String
- UNSAFE_STRING_METHODS = ["capitalize", "chomp", "chop", "delete", "downcase", "gsub", "lstrip", "next", "reverse", "rstrip", "slice", "squeeze", "strip", "sub", "succ", "swapcase", "tr", "tr_s", "upcase", "prepend"].freeze
+ UNSAFE_STRING_METHODS = %w(
+ capitalize chomp chop delete downcase gsub lstrip next reverse rstrip
+ slice squeeze strip sub succ swapcase tr tr_s upcase prepend
+ )
alias_method :original_concat, :concat
private :original_concat
class SafeConcatError < StandardError
def initialize
- super "Could not concatenate to the buffer because it is not html safe."
+ super 'Could not concatenate to the buffer because it is not html safe.'
end
end
def [](*args)
- return super if args.size < 2
-
- if html_safe?
- new_safe_buffer = super
- new_safe_buffer.instance_eval { @html_safe = true }
- new_safe_buffer
+ if args.size < 2
+ super
else
- to_str[*args]
+ if html_safe?
+ new_safe_buffer = super
+ new_safe_buffer.instance_eval { @html_safe = true }
+ new_safe_buffer
+ else
+ to_str[*args]
+ end
end
end
@@ -141,6 +142,18 @@ module ActiveSupport #:nodoc:
dup.concat(other)
end
+ def %(args)
+ args = Array(args).map do |arg|
+ if !html_safe? || arg.html_safe?
+ arg
+ else
+ ERB::Util.h(arg)
+ end
+ end
+
+ self.class.new(super(args))
+ end
+
def html_safe?
defined?(@html_safe) && @html_safe
end
@@ -157,11 +170,6 @@ module ActiveSupport #:nodoc:
coder.represent_scalar nil, to_str
end
- def to_yaml(*args)
- return super() if defined?(YAML::ENGINE) && !YAML::ENGINE.syck?
- to_str.to_yaml(*args)
- end
-
UNSAFE_STRING_METHODS.each do |unsafe_method|
if 'String'.respond_to?(unsafe_method)
class_eval <<-EOT, __FILE__, __LINE__ + 1
diff --git a/lib/active_support/core_ext/string/xchar.rb b/lib/active_support/core_ext/string/xchar.rb
deleted file mode 100644
index f9a5b4f..0000000
--- a/lib/active_support/core_ext/string/xchar.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-begin
- # See http://fast-xs.rubyforge.org/ by Eric Wong.
- # Also included with hpricot.
- require 'fast_xs'
-rescue LoadError
- # fast_xs extension unavailable
-else
- begin
- require 'builder'
- rescue LoadError
- # builder demands the first shot at defining String#to_xs
- end
-
- class String
- alias_method :original_xs, :to_xs if method_defined?(:to_xs)
- alias_method :to_xs, :fast_xs
- end
-end
diff --git a/lib/active_support/core_ext/string/zones.rb b/lib/active_support/core_ext/string/zones.rb
new file mode 100644
index 0000000..e3f20ee
--- /dev/null
+++ b/lib/active_support/core_ext/string/zones.rb
@@ -0,0 +1,13 @@
+require 'active_support/core_ext/time/zones'
+
+class String
+ # Converts String to a TimeWithZone in the current zone if Time.zone or Time.zone_default
+ # is set, otherwise converts String to a Time via String#to_time
+ def in_time_zone(zone = ::Time.zone)
+ if zone
+ ::Time.find_zone!(zone).parse(self)
+ else
+ to_time
+ end
+ end
+end
diff --git a/lib/active_support/core_ext/struct.rb b/lib/active_support/core_ext/struct.rb
new file mode 100644
index 0000000..c2c3004
--- /dev/null
+++ b/lib/active_support/core_ext/struct.rb
@@ -0,0 +1,6 @@
+# Backport of Struct#to_h from Ruby 2.0
+class Struct # :nodoc:
+ def to_h
+ Hash[members.zip(values)]
+ end
+end unless Struct.instance_methods.include?(:to_h)
diff --git a/lib/active_support/core_ext/thread.rb b/lib/active_support/core_ext/thread.rb
new file mode 100644
index 0000000..e80f442
--- /dev/null
+++ b/lib/active_support/core_ext/thread.rb
@@ -0,0 +1,79 @@
+class Thread
+ LOCK = Mutex.new # :nodoc:
+
+ # Returns the value of a thread local variable that has been set. Note that
+ # these are different than fiber local values.
+ #
+ # Thread local values are carried along with threads, and do not respect
+ # fibers. For example:
+ #
+ # Thread.new {
+ # Thread.current.thread_variable_set("foo", "bar") # set a thread local
+ # Thread.current["foo"] = "bar" # set a fiber local
+ #
+ # Fiber.new {
+ # Fiber.yield [
+ # Thread.current.thread_variable_get("foo"), # get the thread local
+ # Thread.current["foo"], # get the fiber local
+ # ]
+ # }.resume
+ # }.join.value # => ['bar', nil]
+ #
+ # The value <tt>"bar"</tt> is returned for the thread local, where +nil+ is returned
+ # for the fiber local. The fiber is executed in the same thread, so the
+ # thread local values are available.
+ def thread_variable_get(key)
+ _locals[key.to_sym]
+ end
+
+ # Sets a thread local with +key+ to +value+. Note that these are local to
+ # threads, and not to fibers. Please see Thread#thread_variable_get for
+ # more information.
+ def thread_variable_set(key, value)
+ _locals[key.to_sym] = value
+ end
+
+ # Returns an an array of the names of the thread-local variables (as Symbols).
+ #
+ # thr = Thread.new do
+ # Thread.current.thread_variable_set(:cat, 'meow')
+ # Thread.current.thread_variable_set("dog", 'woof')
+ # end
+ # thr.join #=> #<Thread:0x401b3f10 dead>
+ # thr.thread_variables #=> [:dog, :cat]
+ #
+ # Note that these are not fiber local variables. Please see Thread#thread_variable_get
+ # for more details.
+ def thread_variables
+ _locals.keys
+ end
+
+ # Returns <tt>true</tt> if the given string (or symbol) exists as a
+ # thread-local variable.
+ #
+ # me = Thread.current
+ # me.thread_variable_set(:oliver, "a")
+ # me.thread_variable?(:oliver) #=> true
+ # me.thread_variable?(:stanley) #=> false
+ #
+ # Note that these are not fiber local variables. Please see Thread#thread_variable_get
+ # for more details.
+ def thread_variable?(key)
+ _locals.has_key?(key.to_sym)
+ end
+
+ def freeze
+ _locals.freeze
+ super
+ end
+
+ private
+
+ def _locals
+ if defined?(@_locals)
+ @_locals
+ else
+ LOCK.synchronize { @_locals ||= {} }
+ end
+ end
+end unless Thread.instance_methods.include?(:thread_variable_set)
diff --git a/lib/active_support/core_ext/time.rb b/lib/active_support/core_ext/time.rb
new file mode 100644
index 0000000..32cffe2
--- /dev/null
+++ b/lib/active_support/core_ext/time.rb
@@ -0,0 +1,5 @@
+require 'active_support/core_ext/time/acts_like'
+require 'active_support/core_ext/time/calculations'
+require 'active_support/core_ext/time/conversions'
+require 'active_support/core_ext/time/marshal'
+require 'active_support/core_ext/time/zones'
diff --git a/lib/active_support/core_ext/time/calculations.rb b/lib/active_support/core_ext/time/calculations.rb
index 9146d82..0e4867c 100644
--- a/lib/active_support/core_ext/time/calculations.rb
+++ b/lib/active_support/core_ext/time/calculations.rb
@@ -2,10 +2,13 @@ require 'active_support/duration'
require 'active_support/core_ext/time/conversions'
require 'active_support/time_with_zone'
require 'active_support/core_ext/time/zones'
+require 'active_support/core_ext/date_and_time/calculations'
+require 'active_support/deprecation'
class Time
+ include DateAndTime::Calculations
+
COMMON_YEAR_DAYS_IN_MONTH = [nil, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
- DAYS_INTO_WEEK = { :monday => 0, :tuesday => 1, :wednesday => 2, :thursday => 3, :friday => 4, :saturday => 5, :sunday => 6 }
class << self
# Overriding case equality method so that it returns true for ActiveSupport::TimeWithZone instances
@@ -16,28 +19,45 @@ class Time
# Return the number of days in the given month.
# If no year is specified, it will use the current year.
def days_in_month(month, year = now.year)
- return 29 if month == 2 && ::Date.gregorian_leap?(year)
- COMMON_YEAR_DAYS_IN_MONTH[month]
+ if month == 2 && ::Date.gregorian_leap?(year)
+ 29
+ else
+ COMMON_YEAR_DAYS_IN_MONTH[month]
+ end
end
+ # *DEPRECATED*: Use +Time#utc+ or +Time#local+ instead.
+ #
# Returns a new Time if requested year can be accommodated by Ruby's Time class
# (i.e., if year is within either 1970..2038 or 1902..2038, depending on system architecture);
# otherwise returns a DateTime.
def time_with_datetime_fallback(utc_or_local, year, month=1, day=1, hour=0, min=0, sec=0, usec=0)
+ ActiveSupport::Deprecation.warn 'time_with_datetime_fallback is deprecated. Use Time#utc or Time#local instead', caller
time = ::Time.send(utc_or_local, year, month, day, hour, min, sec, usec)
+
# This check is needed because Time.utc(y) returns a time object in the 2000s for 0 <= y <= 138.
- time.year == year ? time : ::DateTime.civil_from_format(utc_or_local, year, month, day, hour, min, sec)
+ if time.year == year
+ time
+ else
+ ::DateTime.civil_from_format(utc_or_local, year, month, day, hour, min, sec)
+ end
rescue
::DateTime.civil_from_format(utc_or_local, year, month, day, hour, min, sec)
end
+ # *DEPRECATED*: Use +Time#utc+ instead.
+ #
# Wraps class method +time_with_datetime_fallback+ with +utc_or_local+ set to <tt>:utc</tt>.
def utc_time(*args)
+ ActiveSupport::Deprecation.warn 'utc_time is deprecated. Use Time#utc instead', caller
time_with_datetime_fallback(:utc, *args)
end
+ # *DEPRECATED*: Use +Time#local+ instead.
+ #
# Wraps class method +time_with_datetime_fallback+ with +utc_or_local+ set to <tt>:local</tt>.
def local_time(*args)
+ ActiveSupport::Deprecation.warn 'local_time is deprecated. Use Time#local instead', caller
time_with_datetime_fallback(:local, *args)
end
@@ -45,21 +65,23 @@ class Time
def current
::Time.zone ? ::Time.zone.now : ::Time.now
end
- end
- # Tells whether the Time object's time lies in the past
- def past?
- self < ::Time.current
- end
+ # Layers additional behavior on Time.at so that ActiveSupport::TimeWithZone and DateTime
+ # instances can be used when called with a single argument
+ def at_with_coercion(*args)
+ return at_without_coercion(*args) if args.size != 1
- # Tells whether the Time object's time is today
- def today?
- to_date == ::Date.current
- end
+ # Time.at can be called with a time or numerical value
+ time_or_number = args.first
- # Tells whether the Time object's time lies in the future
- def future?
- self > ::Time.current
+ if time_or_number.is_a?(ActiveSupport::TimeWithZone) || time_or_number.is_a?(DateTime)
+ at_without_coercion(time_or_number.to_f).getlocal
+ else
+ at_without_coercion(time_or_number)
+ end
+ end
+ alias_method :at_without_coercion, :at
+ alias_method :at, :at_with_coercion
end
# Seconds since midnight: Time.now.seconds_since_midnight
@@ -67,20 +89,42 @@ class Time
to_i - change(:hour => 0).to_i + (usec / 1.0e+6)
end
- # Returns a new Time where one or more of the elements have been changed according to the +options+ parameter. The time options
- # (hour, min, sec, usec) reset cascadingly, so if only the hour is passed, then minute, sec, and usec is set to 0. If the hour and
- # minute is passed, then sec and usec is set to 0.
+ # Returns the number of seconds until 23:59:59.
+ #
+ # Time.new(2012, 8, 29, 0, 0, 0).seconds_until_end_of_day # => 86399
+ # Time.new(2012, 8, 29, 12, 34, 56).seconds_until_end_of_day # => 41103
+ # Time.new(2012, 8, 29, 23, 59, 59).seconds_until_end_of_day # => 0
+ def seconds_until_end_of_day
+ end_of_day.to_i - to_i
+ end
+
+ # Returns a new Time where one or more of the elements have been changed according
+ # to the +options+ parameter. The time options (<tt>:hour</tt>, <tt>:min</tt>,
+ # <tt>:sec</tt>, <tt>:usec</tt>) reset cascadingly, so if only the hour is passed,
+ # then minute, sec, and usec is set to 0. If the hour and minute is passed, then
+ # sec and usec is set to 0. The +options+ parameter takes a hash with any of these
+ # keys: <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>, <tt>:hour</tt>, <tt>:min</tt>,
+ # <tt>:sec</tt>, <tt>:usec</tt>.
+ #
+ # Time.new(2012, 8, 29, 22, 35, 0).change(day: 1) # => Time.new(2012, 8, 1, 22, 35, 0)
+ # Time.new(2012, 8, 29, 22, 35, 0).change(year: 1981, day: 1) # => Time.new(1981, 8, 1, 22, 35, 0)
+ # Time.new(2012, 8, 29, 22, 35, 0).change(year: 1981, hour: 0) # => Time.new(1981, 8, 29, 0, 0, 0)
def change(options)
- ::Time.send(
- utc? ? :utc_time : :local_time,
- options[:year] || year,
- options[:month] || month,
- options[:day] || day,
- options[:hour] || hour,
- options[:min] || (options[:hour] ? 0 : min),
- options[:sec] || ((options[:hour] || options[:min]) ? 0 : sec),
- options[:usec] || ((options[:hour] || options[:min] || options[:sec]) ? 0 : usec)
- )
+ new_year = options.fetch(:year, year)
+ new_month = options.fetch(:month, month)
+ new_day = options.fetch(:day, day)
+ new_hour = options.fetch(:hour, hour)
+ new_min = options.fetch(:min, options[:hour] ? 0 : min)
+ new_sec = options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec)
+ new_usec = options.fetch(:usec, (options[:hour] || options[:min] || options[:sec]) ? 0 : Rational(nsec, 1000))
+
+ if utc?
+ ::Time.utc(new_year, new_month, new_day, new_hour, new_min, new_sec, new_usec)
+ elsif zone
+ ::Time.local(new_year, new_month, new_day, new_hour, new_min, new_sec, new_usec)
+ else
+ ::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec + (new_usec.to_r / 1000000), utc_offset)
+ end
end
# Uses Date to provide precise Time calculations for years, months, and days.
@@ -90,18 +134,26 @@ class Time
def advance(options)
unless options[:weeks].nil?
options[:weeks], partial_weeks = options[:weeks].divmod(1)
- options[:days] = (options[:days] || 0) + 7 * partial_weeks
+ options[:days] = options.fetch(:days, 0) + 7 * partial_weeks
end
unless options[:days].nil?
options[:days], partial_days = options[:days].divmod(1)
- options[:hours] = (options[:hours] || 0) + 24 * partial_days
+ options[:hours] = options.fetch(:hours, 0) + 24 * partial_days
end
d = to_date.advance(options)
time_advanced_by_date = change(:year => d.year, :month => d.month, :day => d.day)
- seconds_to_advance = (options[:seconds] || 0) + (options[:minutes] || 0) * 60 + (options[:hours] || 0) * 3600
- seconds_to_advance == 0 ? time_advanced_by_date : time_advanced_by_date.since(seconds_to_advance)
+ seconds_to_advance = \
+ options.fetch(:seconds, 0) +
+ options.fetch(:minutes, 0) * 60 +
+ options.fetch(:hours, 0) * 3600
+
+ if seconds_to_advance.zero?
+ time_advanced_by_date
+ else
+ time_advanced_by_date.since(seconds_to_advance)
+ end
end
# Returns a new Time representing the time a number of seconds ago, this is basically a wrapper around the Numeric extension
@@ -117,98 +169,9 @@ class Time
end
alias :in :since
- # Returns a new Time representing the time a number of specified weeks ago.
- def weeks_ago(weeks)
- advance(:weeks => -weeks)
- end
-
- # Returns a new Time representing the time a number of specified months ago
- def months_ago(months)
- advance(:months => -months)
- end
-
- # Returns a new Time representing the time a number of specified months in the future
- def months_since(months)
- advance(:months => months)
- end
-
- # Returns a new Time representing the time a number of specified years ago
- def years_ago(years)
- advance(:years => -years)
- end
-
- # Returns a new Time representing the time a number of specified years in the future
- def years_since(years)
- advance(:years => years)
- end
-
- # Short-hand for years_ago(1)
- def prev_year
- years_ago(1)
- end
-
- # Short-hand for years_since(1)
- def next_year
- years_since(1)
- end
-
- # Short-hand for months_ago(1)
- def prev_month
- months_ago(1)
- end
-
- # Short-hand for months_since(1)
- def next_month
- months_since(1)
- end
-
- # Returns number of days to start of this week, week starts on start_day (default is :monday).
- def days_to_week_start(start_day = :monday)
- start_day_number = DAYS_INTO_WEEK[start_day]
- current_day_number = wday != 0 ? wday - 1 : 6
- days_span = current_day_number - start_day_number
- days_span >= 0 ? days_span : 7 + days_span
- end
-
- # Returns a new Time representing the "start" of this week, week starts on start_day (default is :monday, i.e. Monday, 0:00).
- def beginning_of_week(start_day = :monday)
- days_to_start = days_to_week_start(start_day)
- (self - days_to_start.days).midnight
- end
- alias :at_beginning_of_week :beginning_of_week
-
- # Returns a new +Date+/+DateTime+ representing the start of this week. Week is
- # assumed to start on a Monday. +DateTime+ objects have their time set to 0:00.
- def monday
- beginning_of_week
- end
-
- # Returns a new Time representing the end of this week, week starts on start_day (default is :monday, i.e. end of Sunday).
- def end_of_week(start_day = :monday)
- days_to_end = 6 - days_to_week_start(start_day)
- (self + days_to_end.days).end_of_day
- end
- alias :at_end_of_week :end_of_week
-
- # Returns a new +Date+/+DateTime+ representing the end of this week. Week is
- # assumed to start on a Monday. +DateTime+ objects have their time set to 23:59:59.
- def sunday
- end_of_week
- end
-
- # Returns a new Time representing the start of the given day in the previous week (default is :monday).
- def prev_week(day = :monday)
- ago(1.week).beginning_of_week.since(DAYS_INTO_WEEK[day].day).change(:hour => 0)
- end
-
- # Returns a new Time representing the start of the given day in next week (default is :monday).
- def next_week(day = :monday)
- since(1.week).beginning_of_week.since(DAYS_INTO_WEEK[day].day).change(:hour => 0)
- end
-
# Returns a new Time representing the start of the day (0:00)
def beginning_of_day
- #(self - seconds_since_midnight).change(:usec => 0)
+ #(self - seconds_since_midnight).change(usec: 0)
change(:hour => 0)
end
alias :midnight :beginning_of_day
@@ -217,8 +180,14 @@ class Time
# Returns a new Time representing the end of the day, 23:59:59.999999 (.999999999 in ruby1.9)
def end_of_day
- change(:hour => 23, :min => 59, :sec => 59, :usec => Rational(999999999, 1000))
+ change(
+ :hour => 23,
+ :min => 59,
+ :sec => 59,
+ :usec => Rational(999999999, 1000)
+ )
end
+ alias :at_end_of_day :end_of_day
# Returns a new Time representing the start of the hour (x:00)
def beginning_of_hour
@@ -228,65 +197,37 @@ class Time
# Returns a new Time representing the end of the hour, x:59:59.999999 (.999999999 in ruby1.9)
def end_of_hour
- change(:min => 59, :sec => 59, :usec => Rational(999999999, 1000))
- end
-
- # Returns a new Time representing the start of the month (1st of the month, 0:00)
- def beginning_of_month
- #self - ((self.mday-1).days + self.seconds_since_midnight)
- change(:day => 1, :hour => 0)
- end
- alias :at_beginning_of_month :beginning_of_month
-
- # Returns a new Time representing the end of the month (end of the last day of the month)
- def end_of_month
- #self - ((self.mday-1).days + self.seconds_since_midnight)
- last_day = ::Time.days_in_month(month, year)
- change(:day => last_day, :hour => 23, :min => 59, :sec => 59, :usec => Rational(999999999, 1000))
- end
- alias :at_end_of_month :end_of_month
-
- # Returns a new Time representing the start of the quarter (1st of january, april, july, october, 0:00)
- def beginning_of_quarter
- beginning_of_month.change(:month => [10, 7, 4, 1].detect { |m| m <= month })
- end
- alias :at_beginning_of_quarter :beginning_of_quarter
-
- # Returns a new Time representing the end of the quarter (end of the last day of march, june, september, december)
- def end_of_quarter
- beginning_of_month.change(:month => [3, 6, 9, 12].detect { |m| m >= month }).end_of_month
- end
- alias :at_end_of_quarter :end_of_quarter
-
- # Returns a new Time representing the start of the year (1st of january, 0:00)
- def beginning_of_year
- change(:month => 1, :day => 1, :hour => 0)
- end
- alias :at_beginning_of_year :beginning_of_year
-
- # Returns a new Time representing the end of the year (end of the 31st of december)
- def end_of_year
- change(:month => 12, :day => 31, :hour => 23, :min => 59, :sec => 59, :usec => Rational(999999999, 1000))
+ change(
+ :min => 59,
+ :sec => 59,
+ :usec => Rational(999999999, 1000)
+ )
end
- alias :at_end_of_year :end_of_year
+ alias :at_end_of_hour :end_of_hour
- # Convenience method which returns a new Time representing the time 1 day ago
- def yesterday
- advance(:days => -1)
+ # Returns a new Time representing the start of the minute (x:xx:00)
+ def beginning_of_minute
+ change(:sec => 0)
end
+ alias :at_beginning_of_minute :beginning_of_minute
- # Convenience method which returns a new Time representing the time 1 day since the instance time
- def tomorrow
- advance(:days => 1)
+ # Returns a new Time representing the end of the minute, x:xx:59.999999 (.999999999 in ruby1.9)
+ def end_of_minute
+ change(
+ :sec => 59,
+ :usec => Rational(999999999, 1000)
+ )
end
+ alias :at_end_of_minute :end_of_minute
# Returns a Range representing the whole day of the current time.
def all_day
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 :monday, i.e. end of Sunday).
- def all_week(start_day = :monday)
+ # 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
@@ -339,7 +280,11 @@ class Time
# can be chronologically compared with a Time
def compare_with_coercion(other)
# we're avoiding Time#to_datetime cause it's expensive
- other.is_a?(Time) ? compare_without_coercion(other.to_time) : to_datetime <=> other
+ if other.is_a?(Time)
+ compare_without_coercion(other.to_time)
+ else
+ to_datetime <=> other
+ end
end
alias_method :compare_without_coercion, :<=>
alias_method :<=>, :compare_with_coercion
@@ -353,4 +298,5 @@ class Time
end
alias_method :eql_without_coercion, :eql?
alias_method :eql?, :eql_with_coercion
+
end
diff --git a/lib/active_support/core_ext/time/conversions.rb b/lib/active_support/core_ext/time/conversions.rb
index d180e1e..48654eb 100644
--- a/lib/active_support/core_ext/time/conversions.rb
+++ b/lib/active_support/core_ext/time/conversions.rb
@@ -1,35 +1,39 @@
require 'active_support/inflector/methods'
-require 'active_support/core_ext/time/publicize_conversion_methods'
require 'active_support/values/time_zone'
class Time
DATE_FORMATS = {
- :db => "%Y-%m-%d %H:%M:%S",
- :number => "%Y%m%d%H%M%S",
- :time => "%H:%M",
- :short => "%d %b %H:%M",
- :long => "%B %d, %Y %H:%M",
- :long_ordinal => lambda { |time| time.strftime("%B #{ActiveSupport::Inflector.ordinalize(time.day)}, %Y %H:%M") },
- :rfc822 => lambda { |time| time.strftime("%a, %d %b %Y %H:%M:%S #{time.formatted_offset(false)}") }
+ :db => '%Y-%m-%d %H:%M:%S',
+ :number => '%Y%m%d%H%M%S',
+ :nsec => '%Y%m%d%H%M%S%9N',
+ :time => '%H:%M',
+ :short => '%d %b %H:%M',
+ :long => '%B %d, %Y %H:%M',
+ :long_ordinal => lambda { |time|
+ day_format = ActiveSupport::Inflector.ordinalize(time.day)
+ time.strftime("%B #{day_format}, %Y %H:%M")
+ },
+ :rfc822 => lambda { |time|
+ offset_format = time.formatted_offset(false)
+ time.strftime("%a, %d %b %Y %H:%M:%S #{offset_format}")
+ }
}
- DATE_FORMATS[:nsec] = '%Y%m%d%H%M%S%9N' if RUBY_VERSION >= '1.9'
-
# Converts to a formatted string. See DATE_FORMATS for builtin formats.
#
# This method is aliased to <tt>to_s</tt>.
#
- # time = Time.now # => Thu Jan 18 06:10:17 CST 2007
+ # time = Time.now # => Thu Jan 18 06:10:17 CST 2007
#
- # time.to_formatted_s(:time) # => "06:10"
- # time.to_s(:time) # => "06:10"
+ # time.to_formatted_s(:time) # => "06:10"
+ # time.to_s(:time) # => "06:10"
#
- # time.to_formatted_s(:db) # => "2007-01-18 06:10:17"
- # time.to_formatted_s(:number) # => "20070118061017"
- # time.to_formatted_s(:short) # => "18 Jan 06:10"
- # time.to_formatted_s(:long) # => "January 18, 2007 06:10"
- # time.to_formatted_s(:long_ordinal) # => "January 18th, 2007 06:10"
- # time.to_formatted_s(:rfc822) # => "Thu, 18 Jan 2007 06:10:17 -0600"
+ # time.to_formatted_s(:db) # => "2007-01-18 06:10:17"
+ # time.to_formatted_s(:number) # => "20070118061017"
+ # time.to_formatted_s(:short) # => "18 Jan 06:10"
+ # time.to_formatted_s(:long) # => "January 18, 2007 06:10"
+ # time.to_formatted_s(:long_ordinal) # => "January 18th, 2007 06:10"
+ # time.to_formatted_s(:rfc822) # => "Thu, 18 Jan 2007 06:10:17 -0600"
#
# == Adding your own time formats to +to_formatted_s+
# You can add your own formats to the Time::DATE_FORMATS hash.
@@ -37,8 +41,8 @@ class Time
# or Proc instance that takes a time argument as the value.
#
# # config/initializers/time_formats.rb
- # Time::DATE_FORMATS[:month_and_year] = "%B %Y"
- # Time::DATE_FORMATS[:short_ordinal] = lambda { |time| time.strftime("%B #{time.day.ordinalize}") }
+ # Time::DATE_FORMATS[:month_and_year] = '%B %Y'
+ # Time::DATE_FORMATS[:short_ordinal] = ->(time) { time.strftime("%B #{time.day.ordinalize}") }
def to_formatted_s(format = :default)
if formatter = DATE_FORMATS[format]
formatter.respond_to?(:call) ? formatter.call(self).to_s : strftime(formatter)
@@ -51,37 +55,9 @@ class Time
# Returns the UTC offset as an +HH:MM formatted string.
#
- # Time.local(2000).formatted_offset # => "-06:00"
- # Time.local(2000).formatted_offset(false) # => "-0600"
+ # Time.local(2000).formatted_offset # => "-06:00"
+ # Time.local(2000).formatted_offset(false) # => "-0600"
def formatted_offset(colon = true, alternate_utc_string = nil)
utc? && alternate_utc_string || ActiveSupport::TimeZone.seconds_to_utc_offset(utc_offset, colon)
end
-
- # Converts a Time object to a Date, dropping hour, minute, and second precision.
- #
- # my_time = Time.now # => Mon Nov 12 22:59:51 -0500 2007
- # my_time.to_date # => Mon, 12 Nov 2007
- #
- # your_time = Time.parse("1/13/2009 1:13:03 P.M.") # => Tue Jan 13 13:13:03 -0500 2009
- # your_time.to_date # => Tue, 13 Jan 2009
- def to_date
- ::Date.new(year, month, day)
- end unless method_defined?(:to_date)
-
- # A method to keep Time, Date and DateTime instances interchangeable on conversions.
- # In this case, it simply returns +self+.
- def to_time
- self
- end unless method_defined?(:to_time)
-
- # Converts a Time instance to a Ruby DateTime instance, preserving UTC offset.
- #
- # my_time = Time.now # => Mon Nov 12 23:04:21 -0500 2007
- # my_time.to_datetime # => Mon, 12 Nov 2007 23:04:21 -0500
- #
- # your_time = Time.parse("1/13/2009 1:13:03 P.M.") # => Tue Jan 13 13:13:03 -0500 2009
- # your_time.to_datetime # => Tue, 13 Jan 2009 13:13:03 -0500
- def to_datetime
- ::DateTime.civil(year, month, day, hour, min, sec, Rational(utc_offset, 86400))
- end unless method_defined?(:to_datetime)
end
diff --git a/lib/active_support/core_ext/time/marshal.rb b/lib/active_support/core_ext/time/marshal.rb
index ce5948d..497c4c3 100644
--- a/lib/active_support/core_ext/time/marshal.rb
+++ b/lib/active_support/core_ext/time/marshal.rb
@@ -1,30 +1,3 @@
-# Pre-1.9 versions of Ruby have a bug with marshaling Time instances, where utc instances are
-# unmarshalled in the local zone, instead of utc. We're layering behavior on the _dump and _load
-# methods so that utc instances can be flagged on dump, and coerced back to utc on load.
-if !Marshal.load(Marshal.dump(Time.now.utc)).utc?
- class Time
- class << self
- alias_method :_load_without_utc_flag, :_load
- def _load(marshaled_time)
- time = _load_without_utc_flag(marshaled_time)
- time.instance_eval do
- if defined?(@marshal_with_utc_coercion)
- val = remove_instance_variable("@marshal_with_utc_coercion")
- end
- val ? utc : self
- end
- end
- end
-
- alias_method :_dump_without_utc_flag, :_dump
- def _dump(*args)
- obj = dup
- obj.instance_variable_set('@marshal_with_utc_coercion', utc?)
- obj.send :_dump_without_utc_flag, *args
- end
- end
-end
-
# Ruby 1.9.2 adds utc_offset and zone to Time, but marshaling only
# preserves utc_offset. Preserve zone also, even though it may not
# work in some edge cases.
diff --git a/lib/active_support/core_ext/time/publicize_conversion_methods.rb b/lib/active_support/core_ext/time/publicize_conversion_methods.rb
deleted file mode 100644
index e1878d3..0000000
--- a/lib/active_support/core_ext/time/publicize_conversion_methods.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-require 'date'
-
-class Time
- # Ruby 1.8-cvs and early 1.9 series define private Time#to_date
- %w(to_date to_datetime).each do |method|
- if private_instance_methods.include?(method) || private_instance_methods.include?(method.to_sym)
- public method
- end
- end
-end
diff --git a/lib/active_support/core_ext/time/zones.rb b/lib/active_support/core_ext/time/zones.rb
index 0c59628..139d48f 100644
--- a/lib/active_support/core_ext/time/zones.rb
+++ b/lib/active_support/core_ext/time/zones.rb
@@ -26,11 +26,11 @@ class Time
# around_filter :set_time_zone
#
# def set_time_zone
- # old_time_zone = Time.zone
- # Time.zone = current_user.time_zone if logged_in?
- # yield
- # ensure
- # Time.zone = old_time_zone
+ # if logged_in?
+ # Time.use_zone(current_user.time_zone) { yield }
+ # else
+ # yield
+ # end
# end
# end
def zone=(time_zone)
@@ -50,13 +50,21 @@ class Time
# Returns a TimeZone instance or nil, or raises an ArgumentError for invalid timezones.
def find_zone!(time_zone)
- return time_zone if time_zone.nil? || time_zone.is_a?(ActiveSupport::TimeZone)
- # lookup timezone based on identifier (unless we've been passed a TZInfo::Timezone)
- unless time_zone.respond_to?(:period_for_local)
- time_zone = ActiveSupport::TimeZone[time_zone] || TZInfo::Timezone.get(time_zone)
+ if !time_zone || time_zone.is_a?(ActiveSupport::TimeZone)
+ time_zone
+ else
+ # lookup timezone based on identifier (unless we've been passed a TZInfo::Timezone)
+ unless time_zone.respond_to?(:period_for_local)
+ time_zone = ActiveSupport::TimeZone[time_zone] || TZInfo::Timezone.get(time_zone)
+ end
+
+ # Return if a TimeZone instance, or wrap in a TimeZone instance if a TZInfo::Timezone
+ if time_zone.is_a?(ActiveSupport::TimeZone)
+ time_zone
+ else
+ ActiveSupport::TimeZone.create(time_zone.name, nil, time_zone)
+ end
end
- # Return if a TimeZone instance, or wrap in a TimeZone instance if a TZInfo::Timezone
- time_zone.is_a?(ActiveSupport::TimeZone) ? time_zone : ActiveSupport::TimeZone.create(time_zone.name, nil, time_zone)
rescue TZInfo::InvalidTimezoneIdentifier
raise ArgumentError, "Invalid Timezone: #{time_zone}"
end
@@ -68,8 +76,8 @@ class Time
# Returns the simultaneous time in <tt>Time.zone</tt>.
#
- # Time.zone = 'Hawaii' # => 'Hawaii'
- # Time.utc(2000).in_time_zone # => Fri, 31 Dec 1999 14:00:00 HST -10:00
+ # Time.zone = 'Hawaii' # => 'Hawaii'
+ # Time.utc(2000).in_time_zone # => Fri, 31 Dec 1999 14:00:00 HST -10:00
#
# This method is similar to Time#localtime, except that it uses <tt>Time.zone</tt> as the local zone
# instead of the operating system's time zone.
@@ -77,10 +85,12 @@ class Time
# You can also pass in a TimeZone instance or string that identifies a TimeZone as an argument,
# and the conversion will be based on that zone instead of <tt>Time.zone</tt>.
#
- # Time.utc(2000).in_time_zone('Alaska') # => Fri, 31 Dec 1999 15:00:00 AKST -09:00
+ # Time.utc(2000).in_time_zone('Alaska') # => Fri, 31 Dec 1999 15:00:00 AKST -09:00
def in_time_zone(zone = ::Time.zone)
- return self unless zone
-
- ActiveSupport::TimeWithZone.new(utc? ? self : getutc, ::Time.find_zone!(zone))
+ if zone
+ ActiveSupport::TimeWithZone.new(utc? ? self : getutc, ::Time.find_zone!(zone))
+ else
+ self
+ end
end
end
diff --git a/lib/active_support/core_ext/uri.rb b/lib/active_support/core_ext/uri.rb
index ee991e3..bfe0832 100644
--- a/lib/active_support/core_ext/uri.rb
+++ b/lib/active_support/core_ext/uri.rb
@@ -1,22 +1,18 @@
# encoding: utf-8
-if RUBY_VERSION >= '1.9'
- require 'uri'
+require 'uri'
+str = "\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E" # Ni-ho-nn-go in UTF-8, means Japanese.
+parser = URI::Parser.new
- str = "\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E" # Ni-ho-nn-go in UTF-8, means Japanese.
-
- parser = URI::Parser.new
-
- unless str == parser.unescape(parser.escape(str))
- URI::Parser.class_eval do
- remove_method :unescape
- def unescape(str, escaped = /%[a-fA-F\d]{2}/)
- # TODO: Are we actually sure that ASCII == UTF-8?
- # YK: My initial experiments say yes, but let's be sure please
- enc = str.encoding
- enc = Encoding::UTF_8 if enc == Encoding::US_ASCII
- str.gsub(escaped) { [$&[1, 2].hex].pack('C') }.force_encoding(enc)
- end
+unless str == parser.unescape(parser.escape(str))
+ URI::Parser.class_eval do
+ remove_method :unescape
+ def unescape(str, escaped = /%[a-fA-F\d]{2}/)
+ # TODO: Are we actually sure that ASCII == UTF-8?
+ # YK: My initial experiments say yes, but let's be sure please
+ enc = str.encoding
+ enc = Encoding::UTF_8 if enc == Encoding::US_ASCII
+ str.gsub(escaped) { [$&[1, 2].hex].pack('C') }.force_encoding(enc)
end
end
end
@@ -24,7 +20,7 @@ end
module URI
class << self
def parser
- @parser ||= URI.const_defined?(:Parser) ? URI::Parser.new : URI
+ @parser ||= URI::Parser.new
end
end
end
diff --git a/lib/active_support/dependencies.rb b/lib/active_support/dependencies.rb
index 25ca5a9..7f2f4f5 100644
--- a/lib/active_support/dependencies.rb
+++ b/lib/active_support/dependencies.rb
@@ -1,5 +1,6 @@
require 'set'
require 'thread'
+require 'thread_safe'
require 'pathname'
require 'active_support/core_ext/module/aliasing'
require 'active_support/core_ext/module/attribute_accessors'
@@ -7,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'
@@ -43,8 +45,9 @@ module ActiveSupport #:nodoc:
mattr_accessor :autoload_once_paths
self.autoload_once_paths = []
- # An array of qualified constant names that have been loaded. Adding a name to
- # this array will cause it to be unloaded the next time Dependencies are cleared.
+ # An array of qualified constant names that have been loaded. Adding a name
+ # to this array will cause it to be unloaded the next time Dependencies are
+ # cleared.
mattr_accessor :autoloaded_constants
self.autoloaded_constants = []
@@ -53,30 +56,32 @@ module ActiveSupport #:nodoc:
mattr_accessor :explicitly_unloadable_constants
self.explicitly_unloadable_constants = []
- # The logger is used for generating information on the action run-time (including benchmarking) if available.
- # Can be set to nil for no logging. Compatible with both Ruby's own Logger and Log4r loggers.
+ # The logger is used for generating information on the action run-time
+ # (including benchmarking) if available. Can be set to nil for no logging.
+ # Compatible with both Ruby's own Logger and Log4r loggers.
mattr_accessor :logger
- # Set to true to enable logging of const_missing and file loads
+ # Set to +true+ to enable logging of const_missing and file loads.
mattr_accessor :log_activity
self.log_activity = false
- # The WatchStack keeps a stack of the modules being watched as files are loaded.
- # If a file in the process of being loaded (parent.rb) triggers the load of
- # another file (child.rb) the stack will ensure that child.rb handles the new
- # constants.
+ # The WatchStack keeps a stack of the modules being watched as files are
+ # loaded. If a file in the process of being loaded (parent.rb) triggers the
+ # load of another file (child.rb) the stack will ensure that child.rb
+ # handles the new constants.
#
# If child.rb is being autoloaded, its constants will be added to
# autoloaded_constants. If it was being `require`d, they will be discarded.
#
# This is handled by walking back up the watch stack and adding the constants
- # found by child.rb to the list of original constants in parent.rb
+ # found by child.rb to the list of original constants in parent.rb.
class WatchStack
include Enumerable
# @watching is a stack of lists of constants being watched. For instance,
- # if parent.rb is autoloaded, the stack will look like [[Object]]. If parent.rb
- # then requires namespace/child.rb, the stack will look like [[Object], [Namespace]].
+ # if parent.rb is autoloaded, the stack will look like [[Object]]. If
+ # parent.rb then requires namespace/child.rb, the stack will look like
+ # [[Object], [Namespace]].
def initialize
@watching = []
@@ -91,7 +96,8 @@ module ActiveSupport #:nodoc:
!@watching.empty?
end
- # return a list of new constants found since the last call to watch_namespaces
+ # Returns a list of new constants found since the last call to
+ # <tt>watch_namespaces</tt>.
def new_constants
constants = []
@@ -105,7 +111,7 @@ module ActiveSupport #:nodoc:
next unless mod.is_a?(Module)
# Get a list of the constants that were added
- new_constants = mod.local_constant_names - original_constants
+ new_constants = mod.local_constants - original_constants
# self[namespace] returns an Array of the constants that are being evaluated
# for that namespace. For instance, if parent.rb requires child.rb, the first
@@ -127,18 +133,17 @@ module ActiveSupport #:nodoc:
pop_modules(@watching.pop)
end
- # Add a set of modules to the watch stack, remembering the initial constants
+ # Add a set of modules to the watch stack, remembering the initial
+ # constants.
def watch_namespaces(namespaces)
- watching = []
- namespaces.map do |namespace|
+ @watching << namespaces.map do |namespace|
module_name = Dependencies.to_constant_name(namespace)
original_constants = Dependencies.qualified_const_defined?(module_name) ?
- Inflector.constantize(module_name).local_constant_names : []
+ Inflector.constantize(module_name).local_constants : []
- watching << module_name
@stack[module_name] << original_constants
+ module_name
end
- @watching << watching
end
private
@@ -151,7 +156,7 @@ module ActiveSupport #:nodoc:
mattr_accessor :constant_watch_stack
self.constant_watch_stack = WatchStack.new
- # Module includes this module
+ # Module includes this module.
module ModuleConstMissing #:nodoc:
def self.append_features(base)
base.class_eval do
@@ -170,36 +175,13 @@ module ActiveSupport #:nodoc:
end
end
- # Use const_missing to autoload associations so we don't have to
- # require_association when using single-table inheritance.
- def const_missing(const_name, nesting = nil)
- klass_name = name.presence || "Object"
-
- unless nesting
- # We'll assume that the nesting of Foo::Bar is ["Foo::Bar", "Foo"]
- # even though it might not be, such as in the case of
- # class Foo::Bar; Baz; end
- nesting = []
- klass_name.to_s.scan(/::|$/) { nesting.unshift $` }
- end
-
- # If there are multiple levels of nesting to search under, the top
- # level is the one we want to report as the lookup fail.
- error = nil
-
- nesting.each do |namespace|
- begin
- return Dependencies.load_missing_constant Inflector.constantize(namespace), const_name
- rescue NoMethodError then raise
- rescue NameError => e
- error ||= e
- end
- end
-
- # Raise the first error for this set. If this const_missing came from an
- # earlier const_missing, this will result in the real error bubbling
- # all the way up
- raise error
+ 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
+ Dependencies.load_missing_constant(from_mod, const_name)
end
def unloadable(const_desc = self)
@@ -207,7 +189,7 @@ module ActiveSupport #:nodoc:
end
end
- # Object includes this module
+ # Object includes this module.
module Loadable #:nodoc:
def self.exclude_from(base)
base.class_eval { define_method(:load, Kernel.instance_method(:load)) }
@@ -222,11 +204,7 @@ module ActiveSupport #:nodoc:
raise ArgumentError, "the file name must be a String -- you passed #{file_name.inspect}"
end
- Dependencies.depend_on(file_name, false, message)
- end
-
- def require_association(file_name)
- Dependencies.associate_with(file_name)
+ Dependencies.depend_on(file_name, message)
end
def load_dependency(file)
@@ -236,7 +214,7 @@ module ActiveSupport #:nodoc:
yield
end
rescue Exception => exception # errors from loading file
- exception.blame_file! file
+ exception.blame_file! file if exception.respond_to? :blame_file!
raise
end
@@ -252,25 +230,25 @@ module ActiveSupport #:nodoc:
result
end
- # Mark the given constant as unloadable. Unloadable constants are removed each
- # time dependencies are cleared.
+ # Mark the given constant as unloadable. Unloadable constants are removed
+ # each time dependencies are cleared.
#
# Note that marking a constant for unloading need only be done once. Setup
# or init scripts may list each unloadable constant that may need unloading;
- # each constant will be removed for every subsequent clear, as opposed to for
- # the first clear.
+ # each constant will be removed for every subsequent clear, as opposed to
+ # for the first clear.
#
# The provided constant descriptor may be a (non-anonymous) module or class,
# or a qualified constant name as a string or symbol.
#
- # Returns true if the constant was not previously marked for unloading, false
- # otherwise.
+ # Returns +true+ if the constant was not previously marked for unloading,
+ # +false+ otherwise.
def unloadable(const_desc)
Dependencies.mark_for_unload const_desc
end
end
- # Exception file-blaming
+ # Exception file-blaming.
module Blamable #:nodoc:
def blame_file!(file)
(@blamed_files ||= []).unshift file
@@ -295,33 +273,26 @@ module ActiveSupport #:nodoc:
Object.class_eval { include Loadable }
Module.class_eval { include ModuleConstMissing }
Exception.class_eval { include Blamable }
- true
end
def unhook!
ModuleConstMissing.exclude_from(Module)
Loadable.exclude_from(Object)
- true
end
def load?
mechanism == :load
end
- def depend_on(file_name, swallow_load_errors = false, message = "No such file to load -- %s.rb")
+ def depend_on(file_name, message = "No such file to load -- %s.rb")
path = search_for_file(file_name)
require_or_load(path || file_name)
rescue LoadError => load_error
- unless swallow_load_errors
- if file_name = load_error.message[/ -- (.*?)(\.rb)?$/, 1]
- raise LoadError.new(message % file_name).copy_blame!(load_error)
- end
- raise
+ if file_name = load_error.message[/ -- (.*?)(\.rb)?$/, 1]
+ load_error.message.replace(message % file_name)
+ load_error.copy_blame!(load_error)
end
- end
-
- def associate_with(file_name)
- depend_on(file_name, true)
+ raise
end
def clear
@@ -332,7 +303,7 @@ module ActiveSupport #:nodoc:
def require_or_load(file_name, const_path = nil)
log_call file_name, const_path
- file_name = $1 if file_name =~ /^(.*)\.rb$/
+ file_name = $` if file_name =~ /\.rb\z/
expanded = File.expand_path(file_name)
return if loaded.include?(expanded)
@@ -365,36 +336,19 @@ module ActiveSupport #:nodoc:
# Record history *after* loading so first load gets warnings.
history << expanded
- return result
+ result
end
# Is the provided constant path defined?
- if Module.method(:const_defined?).arity == 1
- def qualified_const_defined?(path)
- Object.qualified_const_defined?(path.sub(/^::/, ''))
- end
- else
- def qualified_const_defined?(path)
- Object.qualified_const_defined?(path.sub(/^::/, ''), false)
- end
- end
-
- if Module.method(:const_defined?).arity == 1
- # Does this module define this constant?
- # Wrapper to accommodate changing Module#const_defined? in Ruby 1.9
- def local_const_defined?(mod, const)
- mod.const_defined?(const)
- end
- else
- def local_const_defined?(mod, const) #:nodoc:
- mod.const_defined?(const, false)
- end
+ def qualified_const_defined?(path)
+ Object.qualified_const_defined?(path.sub(/^::/, ''), false)
end
- # Given +path+, a filesystem path to a ruby file, return an array of constant
- # paths which would cause Dependencies to attempt to load this file.
+ # Given +path+, a filesystem path to a ruby file, return an array of
+ # constant paths which would cause Dependencies to attempt to load this
+ # file.
def loadable_constants_for_path(path, bases = autoload_paths)
- path = $1 if path =~ /\A(.*)\.rb\Z/
+ path = $` if path =~ /\.rb\z/
expanded_path = File.expand_path(path)
paths = []
@@ -425,7 +379,8 @@ module ActiveSupport #:nodoc:
end
# Does the provided path_suffix correspond to an autoloadable module?
- # Instead of returning a boolean, the autoload base for this module is returned.
+ # Instead of returning a boolean, the autoload base for this module is
+ # returned.
def autoloadable_module?(path_suffix)
autoload_paths.each do |load_path|
return load_path if File.directory? File.join(load_path, path_suffix)
@@ -439,16 +394,16 @@ module ActiveSupport #:nodoc:
end
# Attempt to autoload the provided module name by searching for a directory
- # matching the expected path suffix. If found, the module is created and assigned
- # to +into+'s constants with the name +const_name+. Provided that the directory
- # was loaded from a reloadable base path, it is added to the set of constants
- # that are to be unloaded.
+ # matching the expected path suffix. If found, the module is created and
+ # assigned to +into+'s constants with the name +const_name+. Provided that
+ # the directory was loaded from a reloadable base path, it is added to the
+ # set of constants that are to be unloaded.
def autoload_module!(into, const_name, qualified_name, path_suffix)
return nil unless base_path = autoloadable_module?(path_suffix)
mod = Module.new
into.const_set const_name, mod
autoloaded_constants << qualified_name unless autoload_once_paths.include?(base_path)
- return mod
+ mod
end
# Load the file at the provided path. +const_paths+ is a set of qualified
@@ -456,13 +411,13 @@ module ActiveSupport #:nodoc:
# addition of these constants. Each that is defined will be marked as
# autoloaded, and will be removed when Dependencies.clear is next called.
#
- # If the second parameter is left off, then Dependencies will construct a set
- # of names that the file at +path+ may define. See
+ # If the second parameter is left off, then Dependencies will construct a
+ # set of names that the file at +path+ may define. See
# +loadable_constants_for_path+ for more details.
def load_file(path, const_paths = loadable_constants_for_path(path))
log_call path, const_paths
const_paths = [const_paths].compact unless const_paths.is_a? Array
- parent_paths = const_paths.collect { |const_path| /(.*)::[^:]+\Z/ =~ const_path ? $1 : :Object }
+ parent_paths = const_paths.collect { |const_path| const_path[/.*(?=::)/] || :Object }
result = nil
newly_defined_paths = new_constants_in(*parent_paths) do
@@ -472,18 +427,18 @@ module ActiveSupport #:nodoc:
autoloaded_constants.concat newly_defined_paths unless load_once_path?(path)
autoloaded_constants.uniq!
log "loading #{path} defined #{newly_defined_paths * ', '}" unless newly_defined_paths.empty?
- return result
+ result
end
- # Return the constant path for the provided parent and constant name.
+ # Returns the constant path for the provided parent and constant name.
def qualified_name_for(mod, name)
mod_name = to_constant_name mod
mod_name == "Object" ? name.to_s : "#{mod_name}::#{name}"
end
# Load the constant named +const_name+ which is missing from +from_mod+. If
- # it is not possible to load the constant into from_mod, try its parent module
- # using const_missing.
+ # it is not possible to load the constant into from_mod, try its parent
+ # module using +const_missing+.
def load_missing_constant(from_mod, const_name)
log_call from_mod, const_name
@@ -491,26 +446,52 @@ 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 local_const_defined?(from_mod, const_name)
+ 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
file_path = search_for_file(path_suffix)
- if file_path && ! loaded.include?(File.expand_path(file_path).sub(/\.rb\z/, '')) # We found a matching file to load
- require_or_load file_path
- raise LoadError, "Expected #{file_path} to define #{qualified_name}" unless local_const_defined?(from_mod, const_name)
- return from_mod.const_get(const_name)
+ if file_path
+ expanded = File.expand_path(file_path)
+ expanded.sub!(/\.rb\z/, '')
+
+ if loaded.include?(expanded)
+ raise "Circular dependency detected while autoloading constant #{qualified_name}"
+ else
+ 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
elsif mod = autoload_module!(from_mod, const_name, qualified_name, path_suffix)
return mod
elsif (parent = from_mod.parent) && parent != from_mod &&
- ! from_mod.parents.any? { |p| local_const_defined?(p, const_name) }
+ ! from_mod.parents.any? { |p| p.const_defined?(const_name, false) }
# If our parents do not have a constant named +const_name+ then we are free
# to attempt to load upwards. If they do have such a constant, then this
# const_missing must be due to from_mod::const_name, which should not
# return constants from from_mod's parents.
begin
+ # Since Ruby does not pass the nesting at the point the unknown
+ # constant triggered the callback we cannot fully emulate constant
+ # name lookup and need to make a trade-off: we are going to assume
+ # that the nesting in the body of Foo::Bar is [Foo::Bar, Foo] even
+ # though it might not be. Counterexamples are
+ #
+ # class Foo::Bar
+ # Module.nesting # => [Foo::Bar]
+ # end
+ #
+ # or
+ #
+ # module M::N
+ # module S::T
+ # Module.nesting # => [S::T, M::N]
+ # end
+ # end
+ #
+ # for example.
return parent.const_missing(const_name)
rescue NameError => e
raise unless e.missing_name? qualified_name_for(parent, const_name)
@@ -519,7 +500,7 @@ module ActiveSupport #:nodoc:
raise NameError,
"uninitialized constant #{qualified_name}",
- caller.reject {|l| l.starts_with? __FILE__ }
+ caller.reject { |l| l.starts_with? __FILE__ }
end
# Remove the constants that have been autoloaded, and those that have been
@@ -538,7 +519,7 @@ module ActiveSupport #:nodoc:
class ClassCache
def initialize
- @store = Hash.new
+ @store = ThreadSafe::Cache.new
end
def empty?
@@ -557,10 +538,7 @@ module ActiveSupport #:nodoc:
def safe_get(key)
key = key.name if key.respond_to?(:name)
- @store[key] || begin
- klass = Inflector.safe_constantize(key)
- @store[key] = klass
- end
+ @store[key] ||= Inflector.safe_constantize(key)
end
def store(klass)
@@ -589,14 +567,13 @@ module ActiveSupport #:nodoc:
end
# Get the reference for class named +name+ if one exists.
- # Otherwise returns nil.
+ # Otherwise returns +nil+.
def safe_constantize(name)
Reference.safe_get(name)
end
# Determine if the given constant has been automatically loaded.
def autoloaded?(desc)
- # No name => anonymous module.
return false if desc.is_a?(Module) && desc.anonymous?
name = to_constant_name desc
return false unless qualified_const_defined? name
@@ -614,10 +591,10 @@ module ActiveSupport #:nodoc:
def mark_for_unload(const_desc)
name = to_constant_name const_desc
if explicitly_unloadable_constants.include? name
- return false
+ false
else
explicitly_unloadable_constants << name
- return true
+ true
end
end
@@ -645,10 +622,10 @@ module ActiveSupport #:nodoc:
return new_constants unless aborting
log "Error during loading, removing partially loaded constants "
- new_constants.each {|c| remove_constant(c) }.clear
+ new_constants.each { |c| remove_constant(c) }.clear
end
- return []
+ []
end
# Convert the provided const desc to a qualified constant name (as a string).
@@ -665,19 +642,62 @@ module ActiveSupport #:nodoc:
end
def remove_constant(const) #:nodoc:
- return false unless qualified_const_defined? const
+ # Normalize ::Foo, ::Object::Foo, Object::Foo, Object::Object::Foo, etc. as Foo.
+ normalized = const.to_s.sub(/\A::/, '')
+ normalized.sub!(/\A(Object::)+/, '')
- # Normalize ::Foo, Foo, Object::Foo, and ::Object::Foo to Object::Foo
- names = const.to_s.sub(/^::(Object)?/, 'Object::').split("::")
- to_remove = names.pop
- parent = Inflector.constantize(names * '::')
+ constants = normalized.split('::')
+ to_remove = constants.pop
+
+ if constants.empty?
+ parent = Object
+ else
+ # This method is robust to non-reachable constants.
+ #
+ # Non-reachable constants may be passed if some of the parents were
+ # autoloaded and already removed. It is easier to do a sanity check
+ # here than require the caller to be clever. We check the parent
+ # rather than the very const argument because we do not want to
+ # trigger Kernel#autoloads, see the comment below.
+ parent_name = constants.join('::')
+ return unless qualified_const_defined?(parent_name)
+ parent = constantize(parent_name)
+ end
log "removing constant #{const}"
- constantized = constantize(const)
- constantized.before_remove_const if constantized.respond_to?(:before_remove_const)
- parent.instance_eval { remove_const to_remove }
- return true
+ # In an autoloaded user.rb like this
+ #
+ # autoload :Foo, 'foo'
+ #
+ # class User < ActiveRecord::Base
+ # end
+ #
+ # we correctly register "Foo" as being autoloaded. But if the app does
+ # not use the "Foo" constant we need to be careful not to trigger
+ # loading "foo.rb" ourselves. While #const_defined? and #const_get? do
+ # require the file, #autoload? and #remove_const don't.
+ #
+ # We are going to remove the constant nonetheless ---which exists as
+ # far as Ruby is concerned--- because if the user removes the macro
+ # call from a class or module that were not autoloaded, as in the
+ # example above with Object, accessing to that constant must err.
+ unless parent.autoload?(to_remove)
+ begin
+ constantized = parent.const_get(to_remove, false)
+ rescue NameError
+ log "the constant #{const} is not reachable anymore, skipping"
+ return
+ else
+ constantized.before_remove_const if constantized.respond_to?(:before_remove_const)
+ end
+ end
+
+ begin
+ parent.instance_eval { remove_const to_remove }
+ rescue NameError
+ log "the constant #{const} is not reachable anymore, skipping"
+ end
end
protected
diff --git a/lib/active_support/dependencies/autoload.rb b/lib/active_support/dependencies/autoload.rb
index 4c771da..c0dba5f 100644
--- a/lib/active_support/dependencies/autoload.rb
+++ b/lib/active_support/dependencies/autoload.rb
@@ -1,50 +1,77 @@
require "active_support/inflector/methods"
-require "active_support/lazy_load_hooks"
module ActiveSupport
+ # Autoload and eager load conveniences for your library.
+ #
+ # This module allows you to define autoloads based on
+ # Rails conventions (i.e. no need to define the path
+ # it is automatically guessed based on the filename)
+ # and also define a set of constants that needs to be
+ # eager loaded:
+ #
+ # module MyLib
+ # extend ActiveSupport::Autoload
+ #
+ # autoload :Model
+ #
+ # eager_autoload do
+ # autoload :Cache
+ # end
+ # end
+ #
+ # Then your library can be eager loaded by simply calling:
+ #
+ # MyLib.eager_load!
module Autoload
- @@autoloads = {}
- @@under_path = nil
- @@at_path = nil
- @@eager_autoload = false
+ def self.extended(base) # :nodoc:
+ base.class_eval do
+ @_autoloads = {}
+ @_under_path = nil
+ @_at_path = nil
+ @_eager_autoload = false
+ end
+ end
- def autoload(const_name, path = @@at_path)
- full = [self.name, @@under_path, const_name.to_s, path].compact.join("::")
- location = path || Inflector.underscore(full)
+ def autoload(const_name, path = @_at_path)
+ unless path
+ full = [name, @_under_path, const_name.to_s].compact.join("::")
+ path = Inflector.underscore(full)
+ end
- if @@eager_autoload
- @@autoloads[const_name] = location
+ if @_eager_autoload
+ @_autoloads[const_name] = path
end
- super const_name, location
+
+ super const_name, path
end
def autoload_under(path)
- @@under_path, old_path = path, @@under_path
+ @_under_path, old_path = path, @_under_path
yield
ensure
- @@under_path = old_path
+ @_under_path = old_path
end
def autoload_at(path)
- @@at_path, old_path = path, @@at_path
+ @_at_path, old_path = path, @_at_path
yield
ensure
- @@at_path = old_path
+ @_at_path = old_path
end
def eager_autoload
- old_eager, @@eager_autoload = @@eager_autoload, true
+ old_eager, @_eager_autoload = @_eager_autoload, true
yield
ensure
- @@eager_autoload = old_eager
+ @_eager_autoload = old_eager
end
- def self.eager_autoload!
- @@autoloads.values.each { |file| require file }
+ def eager_load!
+ @_autoloads.values.each { |file| require file }
end
def autoloads
- @@autoloads
+ @_autoloads
end
end
end
diff --git a/lib/active_support/deprecation.rb b/lib/active_support/deprecation.rb
index 9a4b249..6c15fff 100644
--- a/lib/active_support/deprecation.rb
+++ b/lib/active_support/deprecation.rb
@@ -1,18 +1,43 @@
-require 'active_support/deprecation/behaviors'
-require 'active_support/deprecation/reporting'
-require 'active_support/deprecation/method_wrappers'
-require 'active_support/deprecation/proxy_wrappers'
+require 'singleton'
module ActiveSupport
- module Deprecation
- class << self
- # The version the deprecated behavior will be removed, by default.
- attr_accessor :deprecation_horizon
- end
- self.deprecation_horizon = '4.0'
+ # \Deprecation specifies the API used by Rails to deprecate methods, instance
+ # variables, objects and constants.
+ class Deprecation
+ # active_support.rb sets an autoload for ActiveSupport::Deprecation.
+ #
+ # If these requires were at the top of the file the constant would not be
+ # defined by the time their files were loaded. Since some of them reopen
+ # ActiveSupport::Deprecation its autoload would be triggered, resulting in
+ # a circular require warning for active_support/deprecation.rb.
+ #
+ # So, we define the constant first, and load dependencies later.
+ require 'active_support/deprecation/instance_delegator'
+ require 'active_support/deprecation/behaviors'
+ require 'active_support/deprecation/reporting'
+ require 'active_support/deprecation/method_wrappers'
+ require 'active_support/deprecation/proxy_wrappers'
+ require 'active_support/core_ext/module/deprecation'
+
+ include Singleton
+ include InstanceDelegator
+ include Behavior
+ include Reporting
+ include MethodWrapper
- # By default, warnings are not silenced and debugging is off.
- self.silenced = false
- self.debug = false
+ # The version the deprecated behavior will be removed, by default.
+ attr_accessor :deprecation_horizon
+
+ # It accepts two parameters on initialization. The first is an version of library
+ # and the second is an library name
+ #
+ # ActiveSupport::Deprecation.new('2.0', 'MyLibrary')
+ def initialize(deprecation_horizon = '4.1', gem_name = 'Rails')
+ self.gem_name = gem_name
+ self.deprecation_horizon = deprecation_horizon
+ # By default, warnings are not silenced and debugging is off.
+ self.silenced = false
+ self.debug = false
+ end
end
-end
\ No newline at end of file
+end
diff --git a/lib/active_support/deprecation/behaviors.rb b/lib/active_support/deprecation/behaviors.rb
index f9505a2..45df3ac 100644
--- a/lib/active_support/deprecation/behaviors.rb
+++ b/lib/active_support/deprecation/behaviors.rb
@@ -1,49 +1,76 @@
require "active_support/notifications"
-require "active_support/core_ext/array/wrap"
module ActiveSupport
- module Deprecation
- class << self
+ class DeprecationException < StandardError
+ end
+
+ class Deprecation
+ # Default warning behaviors per Rails.env.
+ DEFAULT_BEHAVIORS = {
+ 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: ->(message, callstack) {
+ logger =
+ if defined?(Rails) && Rails.logger
+ Rails.logger
+ else
+ require 'active_support/logger'
+ ActiveSupport::Logger.new($stderr)
+ end
+ logger.warn message
+ logger.debug callstack.join("\n ") if debug
+ },
+
+ notify: ->(message, callstack) {
+ ActiveSupport::Notifications.instrument("deprecation.rails",
+ :message => message, :callstack => callstack)
+ },
+
+ silence: ->(message, callstack) {},
+ }
+
+ module Behavior
# Whether to print a backtrace along with the warning.
attr_accessor :debug
- # Returns the set behavior or if one isn't set, defaults to +:stderr+
+ # Returns the current behavior or if one isn't set, defaults to +:stderr+.
def behavior
@behavior ||= [DEFAULT_BEHAVIORS[:stderr]]
end
- # Sets the behavior to the specified value. Can be a single value or an array.
+ # Sets the behavior to the specified value. Can be a single value, array,
+ # or an object that responds to +call+.
#
- # Examples
+ # 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+.
+ # [+silence+] Do nothing.
+ #
+ # Setting behaviors only affects deprecations that happen after boot time.
+ # Deprecation warnings raised by gems are not affected by this setting
+ # because they happen before Rails boots up.
#
# ActiveSupport::Deprecation.behavior = :stderr
# ActiveSupport::Deprecation.behavior = [:stderr, :log]
+ # ActiveSupport::Deprecation.behavior = MyCustomHandler
+ # ActiveSupport::Deprecation.behavior = ->(message, callstack) {
+ # # custom stuff
+ # }
def behavior=(behavior)
- @behavior = Array.wrap(behavior).map { |b| DEFAULT_BEHAVIORS[b] || b }
+ @behavior = Array(behavior).map { |b| DEFAULT_BEHAVIORS[b] || b }
end
end
-
- # Default warning behaviors per Rails.env.
- DEFAULT_BEHAVIORS = {
- :stderr => Proc.new { |message, callstack|
- $stderr.puts(message)
- $stderr.puts callstack.join("\n ") if debug
- },
- :log => Proc.new { |message, callstack|
- logger =
- if defined?(Rails) && Rails.logger
- Rails.logger
- else
- require 'logger'
- Logger.new($stderr)
- end
- logger.warn message
- logger.debug callstack.join("\n ") if debug
- },
- :notify => Proc.new { |message, callstack|
- ActiveSupport::Notifications.instrument("deprecation.rails",
- :message => message, :callstack => callstack)
- }
- }
end
end
diff --git a/lib/active_support/deprecation/instance_delegator.rb b/lib/active_support/deprecation/instance_delegator.rb
new file mode 100644
index 0000000..8472a58
--- /dev/null
+++ b/lib/active_support/deprecation/instance_delegator.rb
@@ -0,0 +1,24 @@
+require 'active_support/core_ext/kernel/singleton_class'
+require 'active_support/core_ext/module/delegation'
+
+module ActiveSupport
+ class Deprecation
+ module InstanceDelegator # :nodoc:
+ def self.included(base)
+ base.extend(ClassMethods)
+ base.public_class_method :new
+ end
+
+ module ClassMethods # :nodoc:
+ def include(included_module)
+ included_module.instance_methods.each { |m| method_added(m) }
+ super
+ end
+
+ def method_added(method_name)
+ singleton_class.delegate(method_name, to: :instance)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/active_support/deprecation/method_wrappers.rb b/lib/active_support/deprecation/method_wrappers.rb
index d0d8b57..cab8a1b 100644
--- a/lib/active_support/deprecation/method_wrappers.rb
+++ b/lib/active_support/deprecation/method_wrappers.rb
@@ -1,27 +1,42 @@
-require 'active_support/core_ext/module/deprecation'
require 'active_support/core_ext/module/aliasing'
require 'active_support/core_ext/array/extract_options'
module ActiveSupport
- class << Deprecation
- # Declare that a method has been deprecated.
- def deprecate_methods(target_module, *method_names)
- options = method_names.extract_options!
- method_names += options.keys
+ class Deprecation
+ module MethodWrapper
+ # Declare that a method has been deprecated.
+ #
+ # module Fred
+ # extend self
+ #
+ # def foo; end
+ # def bar; end
+ # def baz; end
+ # end
+ #
+ # ActiveSupport::Deprecation.deprecate_methods(Fred, :foo, bar: :qux, baz: 'use Bar#baz instead')
+ # # => [:foo, :bar, :baz]
+ #
+ # Fred.foo
+ # # => "DEPRECATION WARNING: foo is deprecated and will be removed from Rails 4.1."
+ #
+ # Fred.bar
+ # # => "DEPRECATION WARNING: bar is deprecated and will be removed from Rails 4.1 (use qux instead)."
+ #
+ # Fred.baz
+ # # => "DEPRECATION WARNING: baz is deprecated and will be removed from Rails 4.1 (use Bar#baz instead)."
+ def deprecate_methods(target_module, *method_names)
+ options = method_names.extract_options!
+ deprecator = options.delete(:deprecator) || ActiveSupport::Deprecation.instance
+ method_names += options.keys
- method_names.each do |method_name|
- target_module.alias_method_chain(method_name, :deprecation) do |target, punctuation|
- target_module.module_eval(<<-end_eval, __FILE__, __LINE__ + 1)
- def #{target}_with_deprecation#{punctuation}(*args, &block)
- ::ActiveSupport::Deprecation.warn(
- ::ActiveSupport::Deprecation.deprecated_method_warning(
- :#{method_name},
- #{options[method_name].inspect}),
- caller
- )
- send(:#{target}_without_deprecation#{punctuation}, *args, &block)
+ method_names.each do |method_name|
+ target_module.alias_method_chain(method_name, :deprecation) do |target, punctuation|
+ target_module.send(:define_method, "#{target}_with_deprecation#{punctuation}") do |*args, &block|
+ deprecator.deprecation_warning(method_name, options[method_name])
+ send(:"#{target}_without_deprecation#{punctuation}", *args, &block)
end
- end_eval
+ end
end
end
end
diff --git a/lib/active_support/deprecation/proxy_wrappers.rb b/lib/active_support/deprecation/proxy_wrappers.rb
index a65fcaf..a03a66b 100644
--- a/lib/active_support/deprecation/proxy_wrappers.rb
+++ b/lib/active_support/deprecation/proxy_wrappers.rb
@@ -1,7 +1,7 @@
require 'active_support/inflector/methods'
module ActiveSupport
- module Deprecation
+ class Deprecation
class DeprecationProxy #:nodoc:
def self.new(*args, &block)
object = args.first
@@ -25,10 +25,20 @@ module ActiveSupport
end
end
- class DeprecatedObjectProxy < DeprecationProxy #:nodoc:
- def initialize(object, message)
+ # This DeprecatedObjectProxy transforms object to deprecated object.
+ #
+ # @old_object = DeprecatedObjectProxy.new(Object.new, "Don't use this object anymore!")
+ # @old_object = DeprecatedObjectProxy.new(Object.new, "Don't use this object anymore!", deprecator_instance)
+ #
+ # When someone executes any method except +inspect+ on proxy object this will
+ # trigger +warn+ method on +deprecator_instance+.
+ #
+ # Default deprecator is <tt>ActiveSupport::Deprecation</tt>
+ class DeprecatedObjectProxy < DeprecationProxy
+ def initialize(object, message, deprecator = ActiveSupport::Deprecation.instance)
@object = object
@message = message
+ @deprecator = deprecator
end
private
@@ -37,15 +47,40 @@ module ActiveSupport
end
def warn(callstack, called, args)
- ActiveSupport::Deprecation.warn(@message, callstack)
+ @deprecator.warn(@message, callstack)
end
end
- # Stand-in for <tt>@request</tt>, <tt>@attributes</tt>, <tt>@params</tt>, etc.
- # which emits deprecation warnings on any method call (except +inspect+).
- class DeprecatedInstanceVariableProxy < DeprecationProxy #:nodoc:
- def initialize(instance, method, var = "@#{method}")
- @instance, @method, @var = instance, method, var
+ # This DeprecatedInstanceVariableProxy transforms instance variable to
+ # deprecated instance variable.
+ #
+ # class Example
+ # def initialize(deprecator)
+ # @request = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new(self, :request, :@request, deprecator)
+ # @_request = :a_request
+ # end
+ #
+ # def request
+ # @_request
+ # end
+ #
+ # def old_request
+ # @request
+ # end
+ # end
+ #
+ # When someone execute any method on @request variable this will trigger
+ # +warn+ method on +deprecator_instance+ and will fetch <tt>@_request</tt>
+ # variable via +request+ method and execute the same method on non-proxy
+ # instance variable.
+ #
+ # Default deprecator is <tt>ActiveSupport::Deprecation</tt>.
+ class DeprecatedInstanceVariableProxy < DeprecationProxy
+ def initialize(instance, method, var = "@#{method}", deprecator = ActiveSupport::Deprecation.instance)
+ @instance = instance
+ @method = method
+ @var = var
+ @deprecator = deprecator
end
private
@@ -54,14 +89,24 @@ module ActiveSupport
end
def warn(callstack, called, args)
- ActiveSupport::Deprecation.warn("#{@var} is deprecated! Call #{@method}.#{called} instead of #{@var}.#{called}. Args: #{args.inspect}", callstack)
+ @deprecator.warn("#{@var} is deprecated! Call #{@method}.#{called} instead of #{@var}.#{called}. Args: #{args.inspect}", callstack)
end
end
- class DeprecatedConstantProxy < DeprecationProxy #:nodoc:all
- def initialize(old_const, new_const)
+ # This DeprecatedConstantProxy transforms constant to deprecated constant.
+ #
+ # OLD_CONST = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('OLD_CONST', 'NEW_CONST')
+ # OLD_CONST = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('OLD_CONST', 'NEW_CONST', deprecator_instance)
+ #
+ # When someone use old constant this will trigger +warn+ method on
+ # +deprecator_instance+.
+ #
+ # Default deprecator is <tt>ActiveSupport::Deprecation</tt>.
+ class DeprecatedConstantProxy < DeprecationProxy
+ def initialize(old_const, new_const, deprecator = ActiveSupport::Deprecation.instance)
@old_const = old_const
@new_const = new_const
+ @deprecator = deprecator
end
def class
@@ -74,7 +119,7 @@ module ActiveSupport
end
def warn(callstack, called, args)
- ActiveSupport::Deprecation.warn("#{@old_const} is deprecated! Use #{@new_const} instead.", callstack)
+ @deprecator.warn("#{@old_const} is deprecated! Use #{@new_const} instead.", callstack)
end
end
end
diff --git a/lib/active_support/deprecation/reporting.rb b/lib/active_support/deprecation/reporting.rb
index 5d7e241..a7d265d 100644
--- a/lib/active_support/deprecation/reporting.rb
+++ b/lib/active_support/deprecation/reporting.rb
@@ -1,20 +1,34 @@
module ActiveSupport
- module Deprecation
- class << self
+ class Deprecation
+ module Reporting
+ # Whether to print a message (silent mode)
attr_accessor :silenced
+ # Name of gem where method is deprecated
+ attr_accessor :gem_name
- # Outputs a deprecation warning to the output configured by <tt>ActiveSupport::Deprecation.behavior</tt>
+ # Outputs a deprecation warning to the output configured by
+ # <tt>ActiveSupport::Deprecation.behavior</tt>.
#
- # ActiveSupport::Deprecation.warn("something broke!")
+ # ActiveSupport::Deprecation.warn('something broke!')
# # => "DEPRECATION WARNING: something broke! (called from your_code.rb:1)"
- def warn(message = nil, callstack = caller)
+ def warn(message = nil, callstack = nil)
return if silenced
+
+ callstack ||= caller(2)
deprecation_message(callstack, message).tap do |m|
behavior.each { |b| b.call(m, callstack) }
end
end
# Silence deprecation warnings within the block.
+ #
+ # ActiveSupport::Deprecation.warn('something broke!')
+ # # => "DEPRECATION WARNING: something broke! (called from your_code.rb:1)"
+ #
+ # ActiveSupport::Deprecation.silence do
+ # ActiveSupport::Deprecation.warn('something broke!')
+ # end
+ # # => nil
def silence
old_silenced, @silenced = @silenced, true
yield
@@ -22,16 +36,31 @@ module ActiveSupport
@silenced = old_silenced
end
- def deprecated_method_warning(method_name, message = nil)
- warning = "#{method_name} is deprecated and will be removed from Rails #{deprecation_horizon}"
- case message
- when Symbol then "#{warning} (use #{message} instead)"
- when String then "#{warning} (#{message})"
- else warning
+ def deprecation_warning(deprecated_method_name, message = nil, caller_backtrace = nil)
+ caller_backtrace ||= caller(2)
+ deprecated_method_warning(deprecated_method_name, message).tap do |msg|
+ warn(msg, caller_backtrace)
end
end
private
+ # Outputs a deprecation warning message
+ #
+ # ActiveSupport::Deprecation.deprecated_method_warning(:method_name)
+ # # => "method_name is deprecated and will be removed from Rails #{deprecation_horizon}"
+ # ActiveSupport::Deprecation.deprecated_method_warning(:method_name, :another_method)
+ # # => "method_name is deprecated and will be removed from Rails #{deprecation_horizon} (use another_method instead)"
+ # ActiveSupport::Deprecation.deprecated_method_warning(:method_name, "Optional message")
+ # # => "method_name is deprecated and will be removed from Rails #{deprecation_horizon} (Optional message)"
+ def deprecated_method_warning(method_name, message = nil)
+ warning = "#{method_name} is deprecated and will be removed from #{gem_name} #{deprecation_horizon}"
+ case message
+ when Symbol then "#{warning} (use #{message} instead)"
+ when String then "#{warning} (#{message})"
+ else warning
+ end
+ end
+
def deprecation_message(callstack, message = nil)
message ||= "You are using deprecated behavior which will be removed from the next major or minor release."
message += '.' unless message =~ /\.$/
diff --git a/lib/active_support/descendants_tracker.rb b/lib/active_support/descendants_tracker.rb
index e2a8b4d..27861e0 100644
--- a/lib/active_support/descendants_tracker.rb
+++ b/lib/active_support/descendants_tracker.rb
@@ -2,35 +2,50 @@ module ActiveSupport
# This module provides an internal implementation to track descendants
# which is faster than iterating through ObjectSpace.
module DescendantsTracker
- @@direct_descendants = Hash.new { |h, k| h[k] = [] }
+ @@direct_descendants = {}
- def self.direct_descendants(klass)
- @@direct_descendants[klass]
- end
+ class << self
+ def direct_descendants(klass)
+ @@direct_descendants[klass] || []
+ end
- def self.descendants(klass)
- @@direct_descendants[klass].inject([]) do |descendants, _klass|
- descendants << _klass
- descendants.concat _klass.descendants
+ def descendants(klass)
+ arr = []
+ accumulate_descendants(klass, arr)
+ arr
end
- end
- def self.clear
- if defined? ActiveSupport::Dependencies
- @@direct_descendants.each do |klass, descendants|
- if ActiveSupport::Dependencies.autoloaded?(klass)
- @@direct_descendants.delete(klass)
- else
- descendants.reject! { |v| ActiveSupport::Dependencies.autoloaded?(v) }
+ def clear
+ if defined? ActiveSupport::Dependencies
+ @@direct_descendants.each do |klass, descendants|
+ if ActiveSupport::Dependencies.autoloaded?(klass)
+ @@direct_descendants.delete(klass)
+ else
+ descendants.reject! { |v| ActiveSupport::Dependencies.autoloaded?(v) }
+ end
end
+ else
+ @@direct_descendants.clear
+ end
+ end
+
+ # This is the only method that is not thread safe, but is only ever called
+ # during the eager loading phase.
+ def store_inherited(klass, descendant)
+ (@@direct_descendants[klass] ||= []) << descendant
+ end
+
+ private
+ def accumulate_descendants(klass, acc)
+ if direct_descendants = @@direct_descendants[klass]
+ acc.concat(direct_descendants)
+ direct_descendants.each { |direct_descendant| accumulate_descendants(direct_descendant, acc) }
end
- else
- @@direct_descendants.clear
end
end
def inherited(base)
- self.direct_descendants << base
+ DescendantsTracker.store_inherited(self, base)
super
end
diff --git a/lib/active_support/duration.rb b/lib/active_support/duration.rb
index 89b0923..2cb1f40 100644
--- a/lib/active_support/duration.rb
+++ b/lib/active_support/duration.rb
@@ -1,16 +1,14 @@
-require 'active_support/basic_object'
+require 'active_support/proxy_object'
require 'active_support/core_ext/array/conversions'
require 'active_support/core_ext/object/acts_like'
module ActiveSupport
# Provides accurate date and time measurements using Date#advance and
# Time#advance, respectively. It mainly supports the methods on Numeric.
- # Example:
#
- # 1.month.ago # equivalent to Time.now.advance(:months => -1)
- class Duration < BasicObject
+ # 1.month.ago # equivalent to Time.now.advance(months: -1)
+ class Duration < ProxyObject
attr_accessor :value, :parts
- delegate :duplicable?, :to => :value # required when using ActiveSupport's BasicObject on 1.8
def initialize(value, parts) #:nodoc:
@value, @parts = value, parts
@@ -41,8 +39,8 @@ module ActiveSupport
end
alias :kind_of? :is_a?
- # Returns true if <tt>other</tt> is also a Duration instance with the
- # same <tt>value</tt>, or if <tt>other == value</tt>.
+ # Returns +true+ if +other+ is also a Duration instance with the
+ # same +value+, or if <tt>other == value</tt>.
def ==(other)
if Duration === other
other.value == value
@@ -72,7 +70,7 @@ module ActiveSupport
alias :until :ago
def inspect #:nodoc:
- consolidated = parts.inject(::Hash.new(0)) { |h,part| h[part.first] += part.last; h }
+ 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?
diff --git a/lib/active_support/file_update_checker.rb b/lib/active_support/file_update_checker.rb
index a4ad2da..20136dd 100644
--- a/lib/active_support/file_update_checker.rb
+++ b/lib/active_support/file_update_checker.rb
@@ -1,26 +1,21 @@
-require "active_support/core_ext/array/wrap"
-require "active_support/core_ext/array/extract_options"
-
module ActiveSupport
- # \FileUpdateChecker specifies the API used by Rails to watch files
+ # FileUpdateChecker specifies the API used by Rails to watch files
# and control reloading. The API depends on four methods:
#
# * +initialize+ which expects two parameters and one block as
- # described below;
+ # described below.
#
# * +updated?+ which returns a boolean if there were updates in
- # the filesystem or not;
+ # the filesystem or not.
#
# * +execute+ which executes the given block on initialization
- # and updates the counter to the latest timestamp;
+ # and updates the latest watched files and timestamp.
#
- # * +execute_if_updated+ which just executes the block if it was updated;
+ # * +execute_if_updated+ which just executes the block if it was updated.
#
# After initialization, a call to +execute_if_updated+ must execute
# the block only if there was really a change in the filesystem.
#
- # == Examples
- #
# This class is used by Rails to reload the I18n framework whenever
# they are changed upon a new request.
#
@@ -31,52 +26,55 @@ module ActiveSupport
# ActionDispatch::Reloader.to_prepare do
# i18n_reloader.execute_if_updated
# end
- #
class FileUpdateChecker
# It accepts two parameters on initialization. The first is an array
# of files and the second is an optional hash of directories. The hash must
# have directories as keys and the value is an array of extensions to be
# watched under that directory.
#
- # This method must also receive a block that will be called once a path changes.
- #
- # == Implementation details
- #
- # This particular implementation checks for added and updated files,
- # but not removed files. Directories lookup are compiled to a glob for
- # performance. Therefore, while someone can add new files to the +files+
- # array after initialization (and parts of Rails do depend on this feature),
- # adding new directories after initialization is not allowed.
- #
- # Notice that other objects that implements FileUpdateChecker API may
- # not even allow new files to be added after initialization. If this
- # is the case, we recommend freezing the +files+ after initialization to
- # avoid changes that won't make effect.
+ # This method must also receive a block that will be called once a path
+ # changes. The array of files and list of directories cannot be changed
+ # after FileUpdateChecker has been initialized.
def initialize(files, dirs={}, &block)
- @files = files
+ @files = files.freeze
@glob = compile_glob(dirs)
@block = block
+
+ @watched = nil
@updated_at = nil
- @last_update_at = updated_at
+
+ @last_watched = watched
+ @last_update_at = updated_at(@last_watched)
end
- # Check if any of the entries were updated. If so, the updated_at
- # value is cached until the block is executed via +execute+ or +execute_if_updated+
+ # Check if any of the entries were updated. If so, the watched and/or
+ # updated_at values are cached until the block is executed via +execute+
+ # or +execute_if_updated+.
def updated?
- current_updated_at = updated_at
- if @last_update_at < current_updated_at
- @updated_at = updated_at
+ current_watched = watched
+ if @last_watched.size != current_watched.size
+ @watched = current_watched
true
else
- false
+ current_updated_at = updated_at(current_watched)
+ if @last_update_at < current_updated_at
+ @watched = current_watched
+ @updated_at = current_updated_at
+ true
+ else
+ false
+ end
end
end
- # Executes the given block and updates the counter to latest timestamp.
+ # Executes the given block and updates the latest watched files and
+ # timestamp.
def execute
- @last_update_at = updated_at
+ @last_watched = watched
+ @last_update_at = updated_at(@last_watched)
@block.call
ensure
+ @watched = nil
@updated_at = nil
end
@@ -92,28 +90,46 @@ module ActiveSupport
private
- def updated_at #:nodoc:
- @updated_at || begin
- all = []
- all.concat @files.select { |f| File.exists?(f) }
- all.concat Dir[@glob] if @glob
- all.map { |path| File.mtime(path) }.max || Time.at(0)
+ def watched
+ @watched || begin
+ all = @files.select { |f| File.exists?(f) }
+ all.concat(Dir[@glob]) if @glob
+ all
end
end
- def compile_glob(hash) #:nodoc:
+ def updated_at(paths)
+ @updated_at || max_mtime(paths) || Time.at(0)
+ end
+
+ # This method returns the maximum mtime of the files in +paths+, or +nil+
+ # if the array is empty.
+ #
+ # Files with a mtime in the future are ignored. Such abnormal situation
+ # can happen for example if the user changes the clock by hand. It is
+ # healthy to consider this edge case because with mtimes in the future
+ # reloading is not triggered.
+ def max_mtime(paths)
+ time_now = Time.now
+ paths.map {|path| File.mtime(path)}.reject {|mtime| time_now < mtime}.max
+ end
+
+ def compile_glob(hash)
hash.freeze # Freeze so changes aren't accidently pushed
return if hash.empty?
- globs = []
- hash.each do |key, value|
- globs << "#{key}/**/*#{compile_ext(value)}"
+ globs = hash.map do |key, value|
+ "#{escape(key)}/**/*#{compile_ext(value)}"
end
"{#{globs.join(",")}}"
end
- def compile_ext(array) #:nodoc:
- array = Array.wrap(array)
+ def escape(key)
+ key.gsub(',','\,')
+ end
+
+ def compile_ext(array)
+ array = Array(array)
return if array.empty?
".{#{array.join(",")}}"
end
diff --git a/lib/active_support/gzip.rb b/lib/active_support/gzip.rb
index 9651f02..b837c87 100644
--- a/lib/active_support/gzip.rb
+++ b/lib/active_support/gzip.rb
@@ -1,14 +1,20 @@
require 'zlib'
require 'stringio'
-require 'active_support/core_ext/string/encoding'
module ActiveSupport
- # A convenient wrapper for the zlib standard library that allows compression/decompression of strings with gzip.
+ # A convenient wrapper for the zlib standard library that allows
+ # compression/decompression of strings with gzip.
+ #
+ # gzip = ActiveSupport::Gzip.compress('compress me!')
+ # # => "\x1F\x8B\b\x00o\x8D\xCDO\x00\x03K\xCE\xCF-(J-.V\xC8MU\x04\x00R>n\x83\f\x00\x00\x00"
+ #
+ # ActiveSupport::Gzip.decompress(gzip)
+ # # => "compress me!"
module Gzip
class Stream < StringIO
def initialize(*)
super
- set_encoding "BINARY" if "".encoding_aware?
+ set_encoding "BINARY"
end
def close; rewind; end
end
@@ -19,9 +25,9 @@ module ActiveSupport
end
# Compresses a string using gzip.
- def self.compress(source)
+ def self.compress(source, level=Zlib::DEFAULT_COMPRESSION, strategy=Zlib::DEFAULT_STRATEGY)
output = Stream.new
- gz = Zlib::GzipWriter.new(output)
+ gz = Zlib::GzipWriter.new(output, level, strategy)
gz.write(source)
gz.close
output.string
diff --git a/lib/active_support/hash_with_indifferent_access.rb b/lib/active_support/hash_with_indifferent_access.rb
index 9dc93de..0a81a83 100644
--- a/lib/active_support/hash_with_indifferent_access.rb
+++ b/lib/active_support/hash_with_indifferent_access.rb
@@ -1,13 +1,47 @@
require 'active_support/core_ext/hash/keys'
-# This class has dubious semantics and we only have it so that
-# people can write <tt>params[:key]</tt> instead of <tt>params['key']</tt>
-# and they get the same value for both keys.
-
module ActiveSupport
+ # Implements a hash where keys <tt>:foo</tt> and <tt>"foo"</tt> are considered
+ # to be the same.
+ #
+ # rgb = ActiveSupport::HashWithIndifferentAccess.new
+ #
+ # rgb[:black] = '#000000'
+ # rgb[:black] # => '#000000'
+ # rgb['black'] # => '#000000'
+ #
+ # rgb['white'] = '#FFFFFF'
+ # rgb[:white] # => '#FFFFFF'
+ # rgb['white'] # => '#FFFFFF'
+ #
+ # Internally symbols are mapped to strings when used as keys in the entire
+ # writing interface (calling <tt>[]=</tt>, <tt>merge</tt>, etc). This
+ # mapping belongs to the public interface. For example, given:
+ #
+ # hash = ActiveSupport::HashWithIndifferentAccess.new(a: 1)
+ #
+ # You are guaranteed that the key is returned as a string:
+ #
+ # hash.keys # => ["a"]
+ #
+ # Technically other types of keys are accepted:
+ #
+ # hash = ActiveSupport::HashWithIndifferentAccess.new(a: 1)
+ # hash[0] = 0
+ # hash # => {"a"=>1, 0=>0}
+ #
+ # but this class is intended for use cases where strings or symbols are the
+ # expected keys and it is convenient to understand both as the same. For
+ # example the +params+ hash in Ruby on Rails.
+ #
+ # Note that core extensions define <tt>Hash#with_indifferent_access</tt>:
+ #
+ # rgb = { black: '#000000', white: '#FFFFFF' }.with_indifferent_access
+ #
+ # which may be handy.
class HashWithIndifferentAccess < Hash
-
- # Always returns true, so that <tt>Array#extract_options!</tt> finds members of this class.
+ # Returns +true+ so that <tt>Array#extract_options!</tt> finds members of
+ # this class.
def extractable_options?
true
end
@@ -43,35 +77,60 @@ module ActiveSupport
end
end
+ def self.[](*args)
+ new.merge!(Hash[*args])
+ end
+
alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
alias_method :regular_update, :update unless method_defined?(:regular_update)
# Assigns a new value to the hash:
#
- # hash = HashWithIndifferentAccess.new
- # hash[:key] = "value"
+ # hash = ActiveSupport::HashWithIndifferentAccess.new
+ # hash[:key] = 'value'
#
+ # This value can be later fetched using either +:key+ or +'key'+.
def []=(key, value)
- regular_writer(convert_key(key), convert_value(value))
+ regular_writer(convert_key(key), convert_value(value, for: :assignment))
end
alias_method :store, :[]=
- # Updates the instantized hash with values from the second:
+ # Updates the receiver in-place, merging in the hash passed as argument:
#
- # hash_1 = HashWithIndifferentAccess.new
- # hash_1[:key] = "value"
+ # hash_1 = ActiveSupport::HashWithIndifferentAccess.new
+ # hash_1[:key] = 'value'
#
- # hash_2 = HashWithIndifferentAccess.new
- # hash_2[:key] = "New Value!"
+ # hash_2 = ActiveSupport::HashWithIndifferentAccess.new
+ # hash_2[:key] = 'New Value!'
#
# hash_1.update(hash_2) # => {"key"=>"New Value!"}
#
+ # The argument can be either an
+ # <tt>ActiveSupport::HashWithIndifferentAccess</tt> or a regular +Hash+.
+ # In either case the merge respects the semantics of indifferent access.
+ #
+ # If the argument is a regular hash with keys +:key+ and +"key"+ only one
+ # of the values end up in the receiver, but which one is unspecified.
+ #
+ # When given a block, the value for duplicated keys will be determined
+ # by the result of invoking the block with the duplicated key, the value
+ # in the receiver, and the value in +other_hash+. The rules for duplicated
+ # keys follow the semantics of indifferent access:
+ #
+ # hash_1[:key] = 10
+ # hash_2['key'] = 12
+ # hash_1.update(hash_2) { |key, old, new| old + new } # => {"key"=>22}
def update(other_hash)
if other_hash.is_a? HashWithIndifferentAccess
super(other_hash)
else
- other_hash.each_pair { |key, value| regular_writer(convert_key(key), convert_value(value)) }
+ other_hash.each_pair do |key, value|
+ if block_given? && key?(key)
+ value = yield(convert_key(key), self[key], value)
+ end
+ regular_writer(convert_key(key), convert_value(value))
+ end
self
end
end
@@ -80,11 +139,10 @@ module ActiveSupport
# Checks the hash for a key matching the argument passed in:
#
- # hash = HashWithIndifferentAccess.new
- # hash["key"] = "value"
- # hash.key? :key # => true
- # hash.key? "key" # => true
- #
+ # hash = ActiveSupport::HashWithIndifferentAccess.new
+ # hash['key'] = 'value'
+ # hash.key?(:key) # => true
+ # hash.key?('key') # => true
def key?(key)
super(convert_key(key))
end
@@ -96,25 +154,23 @@ module ActiveSupport
# Same as <tt>Hash#fetch</tt> where the key passed as argument can be
# either a string or a symbol:
#
- # counters = HashWithIndifferentAccess.new
+ # counters = ActiveSupport::HashWithIndifferentAccess.new
# counters[:foo] = 1
#
- # counters.fetch("foo") # => 1
+ # counters.fetch('foo') # => 1
# counters.fetch(:bar, 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)
end
# Returns an array of the values at the specified indices:
#
- # hash = HashWithIndifferentAccess.new
- # hash[:a] = "x"
- # hash[:b] = "y"
- # hash.values_at("a", "b") # => ["x", "y"]
- #
+ # hash = ActiveSupport::HashWithIndifferentAccess.new
+ # hash[:a] = 'x'
+ # hash[:b] = 'y'
+ # hash.values_at('a', 'b') # => ["x", "y"]
def values_at(*indices)
indices.collect {|key| self[convert_key(key)]}
end
@@ -126,36 +182,58 @@ module ActiveSupport
end
end
- # Merges the instantized and the specified hashes together, giving precedence to the values from the second hash.
- # Does not overwrite the existing hash.
- def merge(hash)
- self.dup.update(hash)
+ # This method has the same semantics of +update+, except it does not
+ # modify the receiver but rather returns a new hash with indifferent
+ # access with the result of the merge.
+ def merge(hash, &block)
+ self.dup.update(hash, &block)
end
- # Performs the opposite of merge, with the keys and values from the first hash taking precedence over the second.
- # This overloaded definition prevents returning a regular hash, if reverse_merge is called on a <tt>HashWithDifferentAccess</tt>.
+ # Like +merge+ but the other way around: Merges the receiver into the
+ # argument and returns a new hash with indifferent access as result:
+ #
+ # hash = ActiveSupport::HashWithIndifferentAccess.new
+ # hash['a'] = nil
+ # hash.reverse_merge(a: 0, b: 1) # => {"a"=>nil, "b"=>1}
def reverse_merge(other_hash)
- super self.class.new_from_hash_copying_default(other_hash)
+ super(self.class.new_from_hash_copying_default(other_hash))
end
+ # Same semantics as +reverse_merge+ but modifies the receiver in-place.
def reverse_merge!(other_hash)
replace(reverse_merge( other_hash ))
end
- # Removes a specified key from the hash.
+ # Replaces the contents of this hash with other_hash.
+ #
+ # h = { "a" => 100, "b" => 200 }
+ # 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
+
+ # Removes the specified key from the hash.
def delete(key)
super(convert_key(key))
end
def stringify_keys!; self end
+ def deep_stringify_keys!; self end
def stringify_keys; dup end
+ def deep_stringify_keys; dup end
undef :symbolize_keys!
- def symbolize_keys; to_hash.symbolize_keys end
+ undef :deep_symbolize_keys!
+ def symbolize_keys; to_hash.symbolize_keys! end
+ def deep_symbolize_keys; to_hash.deep_symbolize_keys end
def to_options!; self end
- # Convert to a Hash with String keys.
+ # Convert to a regular hash with string keys.
def to_hash
- Hash.new(default).merge!(self)
+ _new_hash= {}
+ each do |key, value|
+ _new_hash[convert_key(key)] = convert_value(value, for: :to_hash)
+ end
+ Hash.new(default).merge!(_new_hash)
end
protected
@@ -163,11 +241,18 @@ module ActiveSupport
key.kind_of?(Symbol) ? key.to_s : key
end
- def convert_value(value)
+ def convert_value(value, options = {})
if value.is_a? Hash
- value.nested_under_indifferent_access
+ if options[:for] == :to_hash
+ value.to_hash
+ else
+ value.nested_under_indifferent_access
+ end
elsif value.is_a?(Array)
- value.dup.replace(value.map { |e| convert_value(e) })
+ unless options[:for] == :assignment
+ value = value.dup
+ end
+ value.map! { |e| convert_value(e, options) }
else
value
end
diff --git a/lib/active_support/i18n.rb b/lib/active_support/i18n.rb
index f9c5e5e..22521a8 100644
--- a/lib/active_support/i18n.rb
+++ b/lib/active_support/i18n.rb
@@ -1,4 +1,7 @@
begin
+ require 'active_support/core_ext/hash/deep_merge'
+ require 'active_support/core_ext/hash/except'
+ require 'active_support/core_ext/hash/slice'
require 'i18n'
require 'active_support/lazy_load_hooks'
rescue LoadError => e
@@ -6,4 +9,5 @@ rescue LoadError => e
raise e
end
+ActiveSupport.run_load_hooks(:i18n)
I18n.load_path << "#{File.dirname(__FILE__)}/locale/en.yml"
diff --git a/lib/active_support/i18n_railtie.rb b/lib/active_support/i18n_railtie.rb
index bbeb8d8..890dd93 100644
--- a/lib/active_support/i18n_railtie.rb
+++ b/lib/active_support/i18n_railtie.rb
@@ -9,25 +9,6 @@ module I18n
config.i18n.load_path = []
config.i18n.fallbacks = ActiveSupport::OrderedOptions.new
- def self.reloader
- @reloader ||= ActiveSupport::FileUpdateChecker.new(reloader_paths){ I18n.reload! }
- end
-
- def self.reloader_paths
- @reloader_paths ||= []
- end
-
- # Add <tt>I18n::Railtie.reloader</tt> to ActionDispatch callbacks. Since, at this
- # point, no path was added to the reloader, I18n.reload! is not triggered
- # on to_prepare callbacks. This will only happen on the config.after_initialize
- # callback below.
- initializer "i18n.callbacks" do |app|
- app.reloaders << I18n::Railtie.reloader
- ActionDispatch::Reloader.to_prepare do
- I18n::Railtie.reloader.execute_if_updated
- end
- end
-
# Set the i18n configuration after initialization since a lot of
# configuration is still usually done in application initializers.
config.after_initialize do |app|
@@ -35,7 +16,7 @@ module I18n
end
# Trigger i18n config before any eager loading has happened
- # so it's ready if any classes require it when eager loaded
+ # so it's ready if any classes require it when eager loaded.
config.before_eager_load do |app|
I18n::Railtie.initialize_i18n(app)
end
@@ -44,7 +25,7 @@ module I18n
@i18n_inited = false
- # Setup i18n configuration
+ # Setup i18n configuration.
def self.initialize_i18n(app)
return if @i18n_inited
@@ -63,7 +44,9 @@ module I18n
init_fallbacks(fallbacks) if fallbacks && validate_fallbacks(fallbacks)
- reloader_paths.concat I18n.load_path
+ reloader = ActiveSupport::FileUpdateChecker.new(I18n.load_path.dup){ I18n.reload! }
+ app.reloaders << reloader
+ ActionDispatch::Reloader.to_prepare { reloader.execute_if_updated }
reloader.execute
@i18n_inited = true
diff --git a/lib/active_support/inflections.rb b/lib/active_support/inflections.rb
index 23a8e68..ef882eb 100644
--- a/lib/active_support/inflections.rb
+++ b/lib/active_support/inflections.rb
@@ -1,10 +1,10 @@
require 'active_support/inflector/inflections'
module ActiveSupport
- Inflector.inflections do |inflect|
+ Inflector.inflections(:en) do |inflect|
inflect.plural(/$/, 's')
inflect.plural(/s$/i, 's')
- inflect.plural(/(ax|test)is$/i, '\1es')
+ inflect.plural(/^(ax|test)is$/i, '\1es')
inflect.plural(/(octop|vir)us$/i, '\1i')
inflect.plural(/(octop|vir)i$/i, '\1i')
inflect.plural(/(alias|status)$/i, '\1es')
@@ -18,17 +18,18 @@ module ActiveSupport
inflect.plural(/([^aeiouy]|qu)y$/i, '\1ies')
inflect.plural(/(x|ch|ss|sh)$/i, '\1es')
inflect.plural(/(matr|vert|ind)(?:ix|ex)$/i, '\1ices')
- inflect.plural(/(m|l)ouse$/i, '\1ice')
- inflect.plural(/(m|l)ice$/i, '\1ice')
+ inflect.plural(/^(m|l)ouse$/i, '\1ice')
+ inflect.plural(/^(m|l)ice$/i, '\1ice')
inflect.plural(/^(ox)$/i, '\1en')
inflect.plural(/^(oxen)$/i, '\1')
inflect.plural(/(quiz)$/i, '\1zes')
inflect.singular(/s$/i, '')
+ inflect.singular(/(ss)$/i, '\1')
inflect.singular(/(n)ews$/i, '\1ews')
inflect.singular(/([ti])a$/i, '\1um')
- inflect.singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i, '\1\2sis')
- inflect.singular(/(^analy)ses$/i, '\1sis')
+ inflect.singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)$/i, '\1sis')
+ inflect.singular(/(^analy)(sis|ses)$/i, '\1sis')
inflect.singular(/([^f])ves$/i, '\1fe')
inflect.singular(/(hive)s$/i, '\1')
inflect.singular(/(tive)s$/i, '\1')
@@ -37,13 +38,14 @@ module ActiveSupport
inflect.singular(/(s)eries$/i, '\1eries')
inflect.singular(/(m)ovies$/i, '\1ovie')
inflect.singular(/(x|ch|ss|sh)es$/i, '\1')
- inflect.singular(/(m|l)ice$/i, '\1ouse')
- inflect.singular(/(bus)es$/i, '\1')
+ inflect.singular(/^(m|l)ice$/i, '\1ouse')
+ inflect.singular(/(bus)(es)?$/i, '\1')
inflect.singular(/(o)es$/i, '\1')
inflect.singular(/(shoe)s$/i, '\1')
- inflect.singular(/(cris|ax|test)es$/i, '\1is')
- inflect.singular(/(octop|vir)i$/i, '\1us')
- inflect.singular(/(alias|status)es$/i, '\1')
+ inflect.singular(/(cris|test)(is|es)$/i, '\1is')
+ inflect.singular(/^(a)x[ie]s$/i, '\1xis')
+ inflect.singular(/(octop|vir)(us|i)$/i, '\1us')
+ inflect.singular(/(alias|status)(es)?$/i, '\1')
inflect.singular(/^(ox)en/i, '\1')
inflect.singular(/(vert|ind)ices$/i, '\1ex')
inflect.singular(/(matr)ices$/i, '\1ix')
@@ -58,6 +60,6 @@ module ActiveSupport
inflect.irregular('cow', 'kine')
inflect.irregular('zombie', 'zombies')
- inflect.uncountable(%w(equipment information rice money species series fish sheep jeans))
+ inflect.uncountable(%w(equipment information rice money species series fish sheep jeans police))
end
end
diff --git a/lib/active_support/inflector/inflections.rb b/lib/active_support/inflector/inflections.rb
index 1e27eac..c96debb 100644
--- a/lib/active_support/inflector/inflections.rb
+++ b/lib/active_support/inflector/inflections.rb
@@ -1,25 +1,34 @@
+require 'thread_safe'
+require 'active_support/core_ext/array/prepend_and_append'
+require 'active_support/i18n'
+
module ActiveSupport
module Inflector
extend self
- # A singleton instance of this class is yielded by Inflector.inflections, which can then be used to specify additional
- # inflection rules. Examples:
+ # A singleton instance of this class is yielded by Inflector.inflections,
+ # which can then be used to specify additional inflection rules. If passed
+ # an optional locale, rules for other languages can be specified. The
+ # default locale is <tt>:en</tt>. Only rules for English are provided.
#
- # ActiveSupport::Inflector.inflections do |inflect|
+ # ActiveSupport::Inflector.inflections(:en) do |inflect|
# inflect.plural /^(ox)$/i, '\1\2en'
# inflect.singular /^(ox)en/i, '\1'
#
# inflect.irregular 'octopus', 'octopi'
#
- # inflect.uncountable "equipment"
+ # inflect.uncountable 'equipment'
# end
#
- # New rules are added at the top. So in the example above, the irregular rule for octopus will now be the first of the
- # pluralization and singularization rules that is runs. This guarantees that your rules run before any of the rules that may
- # already have been loaded.
+ # New rules are added at the top. So in the example above, the irregular
+ # rule for octopus will now be the first of the pluralization and
+ # singularization rules that is runs. This guarantees that your rules run
+ # before any of the rules that may already have been loaded.
class Inflections
- def self.instance
- @__instance__ ||= new
+ @__instance__ = ThreadSafe::Cache.new
+
+ def self.instance(locale = :en)
+ @__instance__[locale] ||= new
end
attr_reader :plurals, :singulars, :uncountables, :humans, :acronyms, :acronym_regex
@@ -28,31 +37,41 @@ module ActiveSupport
@plurals, @singulars, @uncountables, @humans, @acronyms, @acronym_regex = [], [], [], [], {}, /(?=a)b/
end
- # Specifies a new acronym. An acronym must be specified as it will appear in a camelized string. An underscore
- # string that contains the acronym will retain the acronym when passed to `camelize`, `humanize`, or `titleize`.
- # A camelized string that contains the acronym will maintain the acronym when titleized or humanized, and will
- # convert the acronym into a non-delimited single lowercase word when passed to +underscore+.
+ # Private, for the test suite.
+ def initialize_dup(orig) # :nodoc:
+ %w(plurals singulars uncountables humans acronyms acronym_regex).each do |scope|
+ instance_variable_set("@#{scope}", orig.send(scope).dup)
+ end
+ end
+
+ # Specifies a new acronym. An acronym must be specified as it will appear
+ # in a camelized string. An underscore string that contains the acronym
+ # will retain the acronym when passed to +camelize+, +humanize+, or
+ # +titleize+. A camelized string that contains the acronym will maintain
+ # the acronym when titleized or humanized, and will convert the acronym
+ # into a non-delimited single lowercase word when passed to +underscore+.
#
- # Examples:
# acronym 'HTML'
- # titleize 'html' #=> 'HTML'
- # camelize 'html' #=> '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:
+ # 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 'https' #=> 'Https', not 'HTTPs'
+ # underscore 'HTTPS' #=> 'http_s', not 'https'
#
# acronym 'HTTPS'
- # camelize '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 pluralized result. To work around this, you must specify the pluralized form as an
- # acronym as well:
+ # Note: Acronyms that are passed to +pluralize+ will no longer be
+ # recognized, since the acronym will not occur as a delimited unit in the
+ # pluralized result. To work around this, you must specify the pluralized
+ # form as an acronym as well:
#
# acronym 'API'
# camelize(pluralize('api')) #=> 'Apis'
@@ -60,90 +79,107 @@ module ActiveSupport
# acronym '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+ 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.
#
- # Examples:
# acronym 'RESTful'
- # underscore 'RESTful' #=> 'restful'
+ # underscore 'RESTful' #=> 'restful'
# underscore 'RESTfulController' #=> 'restful_controller'
- # titleize 'RESTfulController' #=> 'RESTful Controller'
- # camelize 'restful' #=> 'RESTful'
- # camelize 'restful_controller' #=> 'RESTfulController'
+ # titleize 'RESTfulController' #=> 'RESTful Controller'
+ # camelize 'restful' #=> 'RESTful'
+ # camelize 'restful_controller' #=> 'RESTfulController'
#
# acronym 'McDonald'
# underscore 'McDonald' #=> 'mcdonald'
- # camelize 'mcdonald' #=> 'McDonald'
+ # camelize 'mcdonald' #=> 'McDonald'
def acronym(word)
@acronyms[word.downcase] = word
@acronym_regex = /#{@acronyms.values.join("|")}/
end
- # Specifies a new pluralization rule and its replacement. The rule can either be a string or a regular expression.
- # The replacement should always be a string that may include references to the matched data from the rule.
+ # Specifies a new pluralization rule and its replacement. The rule can
+ # either be a string or a regular expression. The replacement should
+ # always be a string that may include references to the matched data from
+ # the rule.
def plural(rule, replacement)
@uncountables.delete(rule) if rule.is_a?(String)
@uncountables.delete(replacement)
- @plurals.insert(0, [rule, replacement])
+ @plurals.prepend([rule, replacement])
end
- # Specifies a new singularization rule and its replacement. The rule can either be a string or a regular expression.
- # The replacement should always be a string that may include references to the matched data from the rule.
+ # Specifies a new singularization rule and its replacement. The rule can
+ # either be a string or a regular expression. The replacement should
+ # always be a string that may include references to the matched data from
+ # the rule.
def singular(rule, replacement)
@uncountables.delete(rule) if rule.is_a?(String)
@uncountables.delete(replacement)
- @singulars.insert(0, [rule, replacement])
+ @singulars.prepend([rule, replacement])
end
- # Specifies a new irregular that applies to both pluralization and singularization at the same time. This can only be used
- # for strings, not regular expressions. You simply pass the irregular in singular and plural form.
+ # Specifies a new irregular that applies to both pluralization and
+ # singularization at the same time. This can only be used for strings, not
+ # regular expressions. You simply pass the irregular in singular and
+ # plural form.
#
- # Examples:
# irregular 'octopus', 'octopi'
# irregular 'person', 'people'
def irregular(singular, plural)
@uncountables.delete(singular)
@uncountables.delete(plural)
- if singular[0,1].upcase == plural[0,1].upcase
- plural(Regexp.new("(#{singular[0,1]})#{singular[1..-1]}$", "i"), '\1' + plural[1..-1])
- plural(Regexp.new("(#{plural[0,1]})#{plural[1..-1]}$", "i"), '\1' + plural[1..-1])
- singular(Regexp.new("(#{plural[0,1]})#{plural[1..-1]}$", "i"), '\1' + singular[1..-1])
+
+ s0 = singular[0]
+ srest = singular[1..-1]
+
+ p0 = plural[0]
+ prest = plural[1..-1]
+
+ if s0.upcase == p0.upcase
+ plural(/(#{s0})#{srest}$/i, '\1' + prest)
+ plural(/(#{p0})#{prest}$/i, '\1' + prest)
+
+ singular(/(#{s0})#{srest}$/i, '\1' + srest)
+ singular(/(#{p0})#{prest}$/i, '\1' + srest)
else
- plural(Regexp.new("#{singular[0,1].upcase}(?i)#{singular[1..-1]}$"), plural[0,1].upcase + plural[1..-1])
- plural(Regexp.new("#{singular[0,1].downcase}(?i)#{singular[1..-1]}$"), plural[0,1].downcase + plural[1..-1])
- plural(Regexp.new("#{plural[0,1].upcase}(?i)#{plural[1..-1]}$"), plural[0,1].upcase + plural[1..-1])
- plural(Regexp.new("#{plural[0,1].downcase}(?i)#{plural[1..-1]}$"), plural[0,1].downcase + plural[1..-1])
- singular(Regexp.new("#{plural[0,1].upcase}(?i)#{plural[1..-1]}$"), singular[0,1].upcase + singular[1..-1])
- singular(Regexp.new("#{plural[0,1].downcase}(?i)#{plural[1..-1]}$"), singular[0,1].downcase + singular[1..-1])
+ plural(/#{s0.upcase}(?i)#{srest}$/, p0.upcase + prest)
+ plural(/#{s0.downcase}(?i)#{srest}$/, p0.downcase + prest)
+ plural(/#{p0.upcase}(?i)#{prest}$/, p0.upcase + prest)
+ plural(/#{p0.downcase}(?i)#{prest}$/, p0.downcase + prest)
+
+ singular(/#{s0.upcase}(?i)#{srest}$/, s0.upcase + srest)
+ singular(/#{s0.downcase}(?i)#{srest}$/, s0.downcase + srest)
+ singular(/#{p0.upcase}(?i)#{prest}$/, s0.upcase + srest)
+ singular(/#{p0.downcase}(?i)#{prest}$/, s0.downcase + srest)
end
end
# Add uncountable words that shouldn't be attempted inflected.
#
- # Examples:
- # uncountable "money"
- # uncountable "money", "information"
+ # uncountable 'money'
+ # uncountable 'money', 'information'
# uncountable %w( money information rice )
def uncountable(*words)
(@uncountables << words).flatten!
end
- # Specifies a humanized form of a string by a regular expression rule or by a string mapping.
- # When using a regular expression based replacement, the normal humanize formatting is called after the replacement.
- # When a string is used, the human form should be specified as desired (example: 'The name', not 'the_name')
+ # Specifies a humanized form of a string by a regular expression rule or
+ # by a string mapping. When using a regular expression based replacement,
+ # the normal humanize formatting is called after the replacement. When a
+ # string is used, the human form should be specified as desired (example:
+ # 'The name', not 'the_name').
#
- # Examples:
# human /_cnt$/i, '\1_count'
- # human "legacy_col_person_name", "Name"
+ # human 'legacy_col_person_name', 'Name'
def human(rule, replacement)
- @humans.insert(0, [rule, replacement])
+ @humans.prepend([rule, replacement])
end
- # Clears the loaded inflections within a given scope (default is <tt>:all</tt>).
- # Give the scope as a symbol of the inflection type, the options are: <tt>:plurals</tt>,
- # <tt>:singulars</tt>, <tt>:uncountables</tt>, <tt>:humans</tt>.
+ # Clears the loaded inflections within a given scope (default is
+ # <tt>:all</tt>). Give the scope as a symbol of the inflection type, the
+ # options are: <tt>:plurals</tt>, <tt>:singulars</tt>, <tt>:uncountables</tt>,
+ # <tt>:humans</tt>.
#
- # Examples:
# clear :all
# clear :plurals
def clear(scope = :all)
@@ -156,18 +192,19 @@ module ActiveSupport
end
end
- # Yields a singleton instance of Inflector::Inflections so you can specify additional
- # inflector rules.
+ # Yields a singleton instance of Inflector::Inflections so you can specify
+ # additional inflector rules. If passed an optional locale, rules for other
+ # languages can be specified. If not specified, defaults to <tt>:en</tt>.
+ # Only rules for English are provided.
#
- # Example:
- # ActiveSupport::Inflector.inflections do |inflect|
- # inflect.uncountable "rails"
+ # ActiveSupport::Inflector.inflections(:en) do |inflect|
+ # inflect.uncountable 'rails'
# end
- def inflections
+ def inflections(locale = :en)
if block_given?
- yield Inflections.instance
+ yield Inflections.instance(locale)
else
- Inflections.instance
+ Inflections.instance(locale)
end
end
end
diff --git a/lib/active_support/inflector/methods.rb b/lib/active_support/inflector/methods.rb
index 5a7c5e1..3964872 100644
--- a/lib/active_support/inflector/methods.rb
+++ b/lib/active_support/inflector/methods.rb
@@ -1,57 +1,71 @@
+# encoding: utf-8
+
require 'active_support/inflector/inflections'
require 'active_support/inflections'
module ActiveSupport
- # The Inflector transforms words from singular to plural, class names to table names, modularized class names to ones without,
- # and class names to foreign keys. The default inflections for pluralization, singularization, and uncountable words are kept
- # in inflections.rb.
+ # The Inflector transforms words from singular to plural, class names to table
+ # names, modularized class names to ones without, and class names to foreign
+ # keys. The default inflections for pluralization, singularization, and
+ # uncountable words are kept in inflections.rb.
#
- # The Rails core team has stated patches for the inflections library will not be accepted
- # in order to avoid breaking legacy applications which may be relying on errant inflections.
- # If you discover an incorrect inflection and require it for your application, you'll need
- # to correct it yourself (explained below).
+ # The Rails core team has stated patches for the inflections library will not
+ # be accepted in order to avoid breaking legacy applications which may be
+ # relying on errant inflections. If you discover an incorrect inflection and
+ # require it for your application or wish to define rules for languages other
+ # than English, please correct or add them yourself (explained below).
module Inflector
extend self
# Returns the plural form of the word in the string.
#
- # Examples:
- # "post".pluralize # => "posts"
- # "octopus".pluralize # => "octopi"
- # "sheep".pluralize # => "sheep"
- # "words".pluralize # => "words"
- # "CamelOctopus".pluralize # => "CamelOctopi"
- def pluralize(word)
- apply_inflections(word, inflections.plurals)
+ # If passed an optional +locale+ parameter, the word will be
+ # pluralized using rules defined for that language. By default,
+ # this parameter is set to <tt>:en</tt>.
+ #
+ # 'post'.pluralize # => "posts"
+ # 'octopus'.pluralize # => "octopi"
+ # 'sheep'.pluralize # => "sheep"
+ # 'words'.pluralize # => "words"
+ # 'CamelOctopus'.pluralize # => "CamelOctopi"
+ # 'ley'.pluralize(:es) # => "leyes"
+ def pluralize(word, locale = :en)
+ apply_inflections(word, inflections(locale).plurals)
end
- # The reverse of +pluralize+, returns the singular form of a word in a string.
- #
- # Examples:
- # "posts".singularize # => "post"
- # "octopi".singularize # => "octopus"
- # "sheep".singularize # => "sheep"
- # "word".singularize # => "word"
- # "CamelOctopi".singularize # => "CamelOctopus"
- def singularize(word)
- apply_inflections(word, inflections.singulars)
+ # The reverse of +pluralize+, returns the singular form of a word in a
+ # string.
+ #
+ # If passed an optional +locale+ parameter, the word will be
+ # pluralized using rules defined for that language. By default,
+ # this parameter is set to <tt>:en</tt>.
+ #
+ # 'posts'.singularize # => "post"
+ # 'octopi'.singularize # => "octopus"
+ # 'sheep'.singularize # => "sheep"
+ # 'word'.singularize # => "word"
+ # 'CamelOctopi'.singularize # => "CamelOctopus"
+ # 'leyes'.singularize(:es) # => "ley"
+ def singularize(word, locale = :en)
+ apply_inflections(word, inflections(locale).singulars)
end
- # By default, +camelize+ converts strings to UpperCamelCase. If the argument to +camelize+
- # is set to <tt>:lower</tt> then +camelize+ produces lowerCamelCase.
+ # By default, +camelize+ converts strings to UpperCamelCase. If the argument
+ # to +camelize+ is set to <tt>:lower</tt> then +camelize+ produces
+ # lowerCamelCase.
#
- # +camelize+ will also convert '/' to '::' which is useful for converting paths to namespaces.
+ # +camelize+ will also convert '/' to '::' which is useful for converting
+ # paths to namespaces.
#
- # Examples:
- # "active_model".camelize # => "ActiveModel"
- # "active_model".camelize(:lower) # => "activeModel"
- # "active_model/errors".camelize # => "ActiveModel::Errors"
- # "active_model/errors".camelize(:lower) # => "activeModel::Errors"
+ # 'active_model'.camelize # => "ActiveModel"
+ # 'active_model'.camelize(:lower) # => "activeModel"
+ # 'active_model/errors'.camelize # => "ActiveModel::Errors"
+ # 'active_model/errors'.camelize(:lower) # => "activeModel::Errors"
#
- # As a rule of thumb you can think of +camelize+ as the inverse of +underscore+,
- # though there are cases where that does not hold:
+ # As a rule of thumb you can think of +camelize+ as the inverse of
+ # +underscore+, though there are cases where that does not hold:
#
- # "SSLError".underscore.camelize # => "SslError"
+ # 'SSLError'.underscore.camelize # => "SslError"
def camelize(term, uppercase_first_letter = true)
string = term.to_s
if uppercase_first_letter
@@ -66,17 +80,16 @@ module ActiveSupport
#
# Changes '::' to '/' to convert namespaces to paths.
#
- # Examples:
- # "ActiveModel".underscore # => "active_model"
- # "ActiveModel::Errors".underscore # => "active_model/errors"
+ # 'ActiveModel'.underscore # => "active_model"
+ # 'ActiveModel::Errors'.underscore # => "active_model/errors"
#
- # As a rule of thumb you can think of +underscore+ as the inverse of +camelize+,
- # though there are cases where that does not hold:
+ # As a rule of thumb you can think of +underscore+ as the inverse of
+ # +camelize+, though there are cases where that does not hold:
#
- # "SSLError".underscore.camelize # => "SslError"
+ # 'SSLError'.underscore.camelize # => "SslError"
def underscore(camel_cased_word)
word = camel_cased_word.to_s.dup
- word.gsub!(/::/, '/')
+ word.gsub!('::', '/')
word.gsub!(/(?:([A-Za-z\d])|^)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1}#{$1 && '_'}#{$2.downcase}" }
word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
@@ -86,57 +99,55 @@ module ActiveSupport
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.
+ # trailing "_id", if any. Like +titleize+, this is meant for creating pretty
+ # output.
#
- # Examples:
- # "employee_salary" # => "Employee salary"
- # "author_id" # => "Author"
+ # 'employee_salary'.humanize # => "Employee salary"
+ # 'author_id'.humanize # => "Author"
def humanize(lower_case_and_underscored_word)
result = lower_case_and_underscored_word.to_s.dup
- inflections.humans.each { |(rule, replacement)| break if result.gsub!(rule, replacement) }
+ inflections.humans.each { |(rule, replacement)| break if result.sub!(rule, replacement) }
result.gsub!(/_id$/, "")
- result.gsub!(/_/, ' ')
+ result.tr!('_', ' ')
result.gsub(/([a-z\d]*)/i) { |match|
"#{inflections.acronyms[match] || match.downcase}"
}.gsub(/^\w/) { $&.upcase }
end
- # Capitalizes all the words and replaces some characters in the string to create
- # a nicer looking title. +titleize+ is meant for creating pretty output. It is not
- # used in the Rails internals.
+ # Capitalizes all the words and replaces some characters in the string to
+ # create a nicer looking title. +titleize+ is meant for creating pretty
+ # output. It is not used in the Rails internals.
#
- # +titleize+ is also aliased as as +titlecase+.
+ # +titleize+ is also aliased as +titlecase+.
#
- # Examples:
- # "man from the boondocks".titleize # => "Man From The Boondocks"
- # "x-men: the last stand".titleize # => "X Men: The Last Stand"
- # "TheManWithoutAPast".titleize # => "The Man Without A Past"
- # "raiders_of_the_lost_ark".titleize # => "Raiders Of The Lost Ark"
+ # 'man from the boondocks'.titleize # => "Man From The Boondocks"
+ # 'x-men: the last stand'.titleize # => "X Men: The Last Stand"
+ # 'TheManWithoutAPast'.titleize # => "The Man Without A Past"
+ # 'raiders_of_the_lost_ark'.titleize # => "Raiders Of The Lost Ark"
def titleize(word)
- humanize(underscore(word)).gsub(/\b('?[a-z])/) { $1.capitalize }
+ humanize(underscore(word)).gsub(/\b(?<!['’`])[a-z]/) { $&.capitalize }
end
- # Create the name of a table like Rails does for models to table names. This method
- # uses the +pluralize+ method on the last word in the string.
+ # Create the name of a table like Rails does for models to table names. This
+ # method uses the +pluralize+ method on the last word in the string.
#
- # Examples
- # "RawScaledScorer".tableize # => "raw_scaled_scorers"
- # "egg_and_ham".tableize # => "egg_and_hams"
- # "fancyCategory".tableize # => "fancy_categories"
+ # 'RawScaledScorer'.tableize # => "raw_scaled_scorers"
+ # 'egg_and_ham'.tableize # => "egg_and_hams"
+ # 'fancyCategory'.tableize # => "fancy_categories"
def tableize(class_name)
pluralize(underscore(class_name))
end
- # Create a class name from a plural table name like Rails does for table names to models.
- # Note that this returns a string and not a Class. (To convert to an actual class
- # follow +classify+ with +constantize+.)
+ # Create a class name from a plural table name like Rails does for table
+ # names to models. Note that this returns a string and not a Class (To
+ # convert to an actual class follow +classify+ with +constantize+).
#
- # Examples:
- # "egg_and_hams".classify # => "EggAndHam"
- # "posts".classify # => "Post"
+ # 'egg_and_hams'.classify # => "EggAndHam"
+ # 'posts'.classify # => "Post"
#
# Singular names are not handled correctly:
- # "business".classify # => "Busines"
+ #
+ # 'business'.classify # => "Busines"
def classify(table_name)
# strip out any leading schema name
camelize(singularize(table_name.to_s.sub(/.*\./, '')))
@@ -144,16 +155,15 @@ module ActiveSupport
# Replaces underscores with dashes in the string.
#
- # Example:
- # "puni_puni" # => "puni-puni"
+ # 'puni_puni'.dasherize # => "puni-puni"
def dasherize(underscored_word)
- underscored_word.gsub(/_/, '-')
+ underscored_word.tr('_', '-')
end
- # Removes the module part from the expression in the string:
+ # Removes the module part from the expression in the string.
#
- # "ActiveRecord::CoreExtensions::String::Inflections".demodulize # => "Inflections"
- # "Inflections".demodulize # => "Inflections"
+ # 'ActiveRecord::CoreExtensions::String::Inflections'.demodulize # => "Inflections"
+ # 'Inflections'.demodulize # => "Inflections"
#
# See also +deconstantize+.
def demodulize(path)
@@ -165,13 +175,13 @@ module ActiveSupport
end
end
- # Removes the rightmost segment from the constant expression in the string:
+ # Removes the rightmost segment from the constant expression in the string.
#
- # "Net::HTTP".deconstantize # => "Net"
- # "::Net::HTTP".deconstantize # => "::Net"
- # "String".deconstantize # => ""
- # "::String".deconstantize # => ""
- # "".deconstantize # => ""
+ # 'Net::HTTP'.deconstantize # => "Net"
+ # '::Net::HTTP'.deconstantize # => "::Net"
+ # 'String'.deconstantize # => ""
+ # '::String'.deconstantize # => ""
+ # ''.deconstantize # => ""
#
# See also +demodulize+.
def deconstantize(path)
@@ -182,94 +192,115 @@ module ActiveSupport
# +separate_class_name_and_id_with_underscore+ sets whether
# the method should put '_' between the name and 'id'.
#
- # Examples:
- # "Message".foreign_key # => "message_id"
- # "Message".foreign_key(false) # => "messageid"
- # "Admin::Post".foreign_key # => "post_id"
+ # 'Message'.foreign_key # => "message_id"
+ # 'Message'.foreign_key(false) # => "messageid"
+ # 'Admin::Post'.foreign_key # => "post_id"
def foreign_key(class_name, separate_class_name_and_id_with_underscore = true)
underscore(demodulize(class_name)) + (separate_class_name_and_id_with_underscore ? "_id" : "id")
end
- # Ruby 1.9 introduces an inherit argument for Module#const_get and
- # #const_defined? and changes their default behavior.
- if Module.method(:const_get).arity == 1
- # Tries to find a constant with the name specified in the argument string:
- #
- # "Module".constantize # => Module
- # "Test::Unit".constantize # => Test::Unit
- #
- # The name is assumed to be the one of a top-level constant, no matter whether
- # it starts with "::" or not. No lexical context is taken into account:
- #
- # C = 'outside'
- # module M
- # C = 'inside'
- # C # => 'inside'
- # "C".constantize # => 'outside', same as ::C
- # end
- #
- # NameError is raised when the name is not in CamelCase or the constant is
- # unknown.
- def constantize(camel_cased_word)
- names = camel_cased_word.split('::')
- names.shift if names.empty? || names.first.empty?
+ # Tries to find a constant with the name specified in the argument string.
+ #
+ # 'Module'.constantize # => Module
+ # 'Test::Unit'.constantize # => Test::Unit
+ #
+ # The name is assumed to be the one of a top-level constant, no matter
+ # whether it starts with "::" or not. No lexical context is taken into
+ # account:
+ #
+ # C = 'outside'
+ # module M
+ # C = 'inside'
+ # C # => 'inside'
+ # 'C'.constantize # => 'outside', same as ::C
+ # end
+ #
+ # NameError is raised when the name is not in CamelCase or the constant is
+ # unknown.
+ def constantize(camel_cased_word)
+ names = camel_cased_word.split('::')
+ names.shift if names.empty? || names.first.empty?
+
+ names.inject(Object) do |constant, name|
+ if constant == Object
+ constant.const_get(name)
+ else
+ candidate = constant.const_get(name)
+ next candidate if constant.const_defined?(name, false)
+ next candidate unless Object.const_defined?(name)
- constant = Object
- names.each do |name|
- constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
- end
- constant
- end
- else
- def constantize(camel_cased_word) #:nodoc:
- names = camel_cased_word.split('::')
- names.shift if names.empty? || names.first.empty?
+ # Go down the ancestors to check it it's owned
+ # directly before we reach Object or the end of ancestors.
+ constant = constant.ancestors.inject do |const, ancestor|
+ break const if ancestor == Object
+ break ancestor if ancestor.const_defined?(name, false)
+ const
+ end
- constant = Object
- names.each do |name|
- constant = constant.const_defined?(name, false) ? constant.const_get(name) : constant.const_missing(name)
+ # owner is in Object, so raise
+ constant.const_get(name, false)
end
- constant
end
end
- # Tries to find a constant with the name specified in the argument string:
+ # Tries to find a constant with the name specified in the argument string.
#
- # "Module".safe_constantize # => Module
- # "Test::Unit".safe_constantize # => Test::Unit
+ # 'Module'.safe_constantize # => Module
+ # 'Test::Unit'.safe_constantize # => Test::Unit
#
- # The name is assumed to be the one of a top-level constant, no matter whether
- # it starts with "::" or not. No lexical context is taken into account:
+ # The name is assumed to be the one of a top-level constant, no matter
+ # whether it starts with "::" or not. No lexical context is taken into
+ # account:
#
# C = 'outside'
# module M
# C = 'inside'
# C # => 'inside'
- # "C".safe_constantize # => 'outside', same as ::C
+ # 'C'.safe_constantize # => 'outside', same as ::C
# end
#
- # nil is returned when the name is not in CamelCase or the constant (or part of it) is
- # unknown.
- #
- # "blargle".safe_constantize # => nil
- # "UnknownModule".safe_constantize # => nil
- # "UnknownModule::Foo::Bar".safe_constantize # => nil
+ # +nil+ is returned when the name is not in CamelCase or the constant (or
+ # part of it) is unknown.
#
+ # 'blargle'.safe_constantize # => nil
+ # 'UnknownModule'.safe_constantize # => nil
+ # 'UnknownModule::Foo::Bar'.safe_constantize # => nil
def safe_constantize(camel_cased_word)
- begin
- constantize(camel_cased_word)
- rescue NameError => e
- raise unless e.message =~ /(uninitialized constant|wrong constant name) #{const_regexp(camel_cased_word)}$/ ||
- e.name.to_s == camel_cased_word.to_s
- rescue ArgumentError => e
- raise unless e.message =~ /not missing constant #{const_regexp(camel_cased_word)}\!$/
+ constantize(camel_cased_word)
+ rescue NameError => e
+ raise unless e.message =~ /(uninitialized constant|wrong constant name) #{const_regexp(camel_cased_word)}$/ ||
+ e.name.to_s == camel_cased_word.to_s
+ rescue ArgumentError => e
+ raise unless e.message =~ /not missing constant #{const_regexp(camel_cased_word)}\!$/
+ end
+
+ # Returns the suffix that should be added to a number to denote the position
+ # in an ordered sequence such as 1st, 2nd, 3rd, 4th.
+ #
+ # ordinal(1) # => "st"
+ # ordinal(2) # => "nd"
+ # ordinal(1002) # => "nd"
+ # ordinal(1003) # => "rd"
+ # ordinal(-11) # => "th"
+ # ordinal(-1021) # => "st"
+ def ordinal(number)
+ abs_number = number.to_i.abs
+
+ if (11..13).include?(abs_number % 100)
+ "th"
+ else
+ case abs_number % 10
+ when 1; "st"
+ when 2; "nd"
+ when 3; "rd"
+ else "th"
+ end
end
end
# Turns a number into an ordinal string used to denote the position in an
# ordered sequence such as 1st, 2nd, 3rd, 4th.
#
- # Examples:
# ordinalize(1) # => "1st"
# ordinalize(2) # => "2nd"
# ordinalize(1002) # => "1002nd"
@@ -277,16 +308,7 @@ module ActiveSupport
# ordinalize(-11) # => "-11th"
# ordinalize(-1021) # => "-1021st"
def ordinalize(number)
- if (11..13).include?(number.to_i.abs % 100)
- "#{number}th"
- else
- case number.to_i.abs % 10
- when 1; "#{number}st"
- when 2; "#{number}nd"
- when 3; "#{number}rd"
- else "#{number}th"
- end
- end
+ "#{number}#{ordinal(number)}"
end
private
@@ -304,16 +326,15 @@ module ActiveSupport
# Applies inflection rules for +singularize+ and +pluralize+.
#
- # Examples:
- # apply_inflections("post", inflections.plurals) # => "posts"
- # apply_inflections("posts", inflections.singulars) # => "post"
+ # apply_inflections('post', inflections.plurals) # => "posts"
+ # apply_inflections('posts', inflections.singulars) # => "post"
def apply_inflections(word, rules)
result = word.to_s.dup
- if word.empty? || inflections.uncountables.any? { |inflection| result =~ /\b#{inflection}\Z/i }
+ if word.empty? || inflections.uncountables.include?(result.downcase[/\b\w+\Z/])
result
else
- rules.each { |(rule, replacement)| break if result.gsub!(rule, replacement) }
+ rules.each { |(rule, replacement)| break if result.sub!(rule, replacement) }
result
end
end
diff --git a/lib/active_support/inflector/transliterate.rb b/lib/active_support/inflector/transliterate.rb
index 40e7a0e..1cde417 100644
--- a/lib/active_support/inflector/transliterate.rb
+++ b/lib/active_support/inflector/transliterate.rb
@@ -8,7 +8,7 @@ module ActiveSupport
# Replaces non-ASCII characters with an ASCII approximation, or if none
# exists, a replacement character which defaults to "?".
#
- # transliterate("Ærøskøbing")
+ # transliterate('Ærøskøbing')
# # => "AEroskobing"
#
# Default approximations are provided for Western/Latin characters,
@@ -30,33 +30,33 @@ module ActiveSupport
# ö: "oe"
#
# # Or set them using Ruby
- # I18n.backend.store_translations(:de, :i18n => {
- # :transliterate => {
- # :rule => {
- # "ü" => "ue",
- # "ö" => "oe"
+ # I18n.backend.store_translations(:de, i18n: {
+ # transliterate: {
+ # rule: {
+ # 'ü' => 'ue',
+ # 'ö' => 'oe'
# }
# }
# })
#
- # The value for <tt>i18n.transliterate.rule</tt> can be a simple Hash that maps
- # characters to ASCII approximations as shown above, or, for more complex
- # requirements, a Proc:
+ # The value for <tt>i18n.transliterate.rule</tt> can be a simple Hash that
+ # maps characters to ASCII approximations as shown above, or, for more
+ # complex requirements, a Proc:
#
- # I18n.backend.store_translations(:de, :i18n => {
- # :transliterate => {
- # :rule => lambda {|string| MyTransliterator.transliterate(string)}
+ # I18n.backend.store_translations(:de, i18n: {
+ # transliterate: {
+ # rule: ->(string) { MyTransliterator.transliterate(string) }
# }
# })
#
# Now you can have different transliterations for each locale:
#
# I18n.locale = :en
- # transliterate("Jürgen")
+ # transliterate('Jürgen')
# # => "Jurgen"
#
# I18n.locale = :de
- # transliterate("Jürgen")
+ # transliterate('Jürgen')
# # => "Juergen"
def transliterate(string, replacement = "?")
I18n.transliterate(ActiveSupport::Multibyte::Unicode.normalize(
@@ -64,9 +64,8 @@ module ActiveSupport
:replacement => replacement)
end
- # Replaces special characters in a string so that it may be used as part of a 'pretty' URL.
- #
- # ==== Examples
+ # Replaces special characters in a string so that it may be used as part of
+ # a 'pretty' URL.
#
# class Person
# def to_param
diff --git a/lib/active_support/json/decoding.rb b/lib/active_support/json/decoding.rb
index f95eb7a..a4a32b2 100644
--- a/lib/active_support/json/decoding.rb
+++ b/lib/active_support/json/decoding.rb
@@ -8,14 +8,13 @@ module ActiveSupport
module JSON
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, options ={})
- # Can't reliably detect whether MultiJson responds to load, since it's
- # a reserved word. Use adapter as a proxy for new features.
- data = if MultiJson.respond_to?(:adapter)
- MultiJson.load(json, options)
- else
- MultiJson.decode(json, options)
- end
+ data = MultiJson.load(json, options)
if ActiveSupport.parse_json_times
convert_dates_from(data)
else
@@ -24,20 +23,12 @@ module ActiveSupport
end
def engine
- if MultiJson.respond_to?(:adapter)
- MultiJson.adapter
- else
- MultiJson.engine
- end
+ MultiJson.adapter
end
alias :backend :engine
def engine=(name)
- if MultiJson.respond_to?(:use)
- MultiJson.use name
- else
- MultiJson.engine = name
- end
+ MultiJson.use(name)
end
alias :backend= :engine=
@@ -48,6 +39,16 @@ module ActiveSupport
self.backend = old_backend
end
+ # Returns the class of the error that will be raised when there is an
+ # error in decoding JSON. Using this method means you won't directly
+ # depend on the ActiveSupport's JSON implementation, in case it changes
+ # in the future.
+ #
+ # begin
+ # obj = ActiveSupport::JSON.decode(some_string)
+ # rescue ActiveSupport::JSON.parse_error
+ # Rails.logger.warn("Attempted to decode invalid JSON: #{some_string}")
+ # end
def parse_error
MultiJson::DecodeError
end
diff --git a/lib/active_support/json/encoding.rb b/lib/active_support/json/encoding.rb
index a50e652..9bf1ea3 100644
--- a/lib/active_support/json/encoding.rb
+++ b/lib/active_support/json/encoding.rb
@@ -1,11 +1,9 @@
require 'active_support/core_ext/object/to_json'
require 'active_support/core_ext/module/delegation'
require 'active_support/json/variable'
-require 'active_support/ordered_hash'
require 'bigdecimal'
require 'active_support/core_ext/big_decimal/conversions' # for #to_s
-require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/object/instance_variables'
@@ -19,6 +17,7 @@ module ActiveSupport
class << self
delegate :use_standard_json_time_format, :use_standard_json_time_format=,
:escape_html_entities_in_json, :escape_html_entities_in_json=,
+ :encode_big_decimal_as_string, :encode_big_decimal_as_string=,
:to => :'ActiveSupport::JSON::Encoding'
end
@@ -26,7 +25,11 @@ module ActiveSupport
# 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 object in JSON (JavaScript Object Notation). See www.json.org for more info.
+ # 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)
end
@@ -49,7 +52,7 @@ module ActiveSupport
end
end
- # like encode, but only calls as_json, without encoding to string
+ # 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
@@ -58,7 +61,8 @@ module ActiveSupport
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
+ # hashes and arrays need to get encoder in the options, so that
+ # they can detect circular references.
options.merge(:encoder => self)
else
options.dup
@@ -103,9 +107,14 @@ module ActiveSupport
'&' => '\u0026' }
class << self
- # If true, use ISO 8601 format for dates and times. Otherwise, fall back to the Active Support legacy format.
+ # 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
@@ -119,18 +128,17 @@ module ActiveSupport
end
def escape(string)
- if string.respond_to?(:force_encoding)
- string = string.encode(::Encoding::UTF_8, :undef => :replace).force_encoding(::Encoding::BINARY)
- end
+ 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) if json.respond_to?(:force_encoding)
+ json.force_encoding(::Encoding::UTF_8)
json
end
end
self.use_standard_json_time_format = true
- self.escape_html_entities_in_json = false
+ self.escape_html_entities_in_json = true
+ self.encode_big_decimal_as_string = true
end
end
end
@@ -152,32 +160,67 @@ class Struct #:nodoc:
end
class TrueClass
- def as_json(options = nil) self end #:nodoc:
- def encode_json(encoder) to_s end #:nodoc:
+ def as_json(options = nil) #:nodoc:
+ self
+ end
+
+ def encode_json(encoder) #:nodoc:
+ to_s
+ end
end
class FalseClass
- def as_json(options = nil) self end #:nodoc:
- def encode_json(encoder) to_s end #:nodoc:
+ def as_json(options = nil) #:nodoc:
+ self
+ end
+
+ def encode_json(encoder) #:nodoc:
+ to_s
+ end
end
class NilClass
- def as_json(options = nil) self end #:nodoc:
- def encode_json(encoder) 'null' end #:nodoc:
+ def as_json(options = nil) #:nodoc:
+ self
+ end
+
+ def encode_json(encoder) #:nodoc:
+ 'null'
+ end
end
class String
- def as_json(options = nil) self end #:nodoc:
- def encode_json(encoder) encoder.escape(self) end #:nodoc:
+ 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) to_s end #:nodoc:
+ def as_json(options = nil) #:nodoc:
+ to_s
+ end
end
class Numeric
- def as_json(options = nil) self end #:nodoc:
- def encode_json(encoder) to_s end #:nodoc:
+ def as_json(options = nil) #:nodoc:
+ self
+ end
+
+ def encode_json(encoder) #:nodoc:
+ to_s
+ end
+end
+
+class Float
+ # Encoding Infinity or NaN to JSON should return "null". The default returns
+ # "Infinity" or "NaN" which breaks parsing the JSON. E.g. JSON.parse('[NaN]').
+ def as_json(options = nil) #:nodoc:
+ finite? ? self : nil
+ end
end
class BigDecimal
@@ -186,14 +229,26 @@ class BigDecimal
# 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) to_s end #:nodoc:
+ # That's why a JSON string is returned. The JSON literal is not numeric, but
+ # if the other end knows by contract that the data is supposed to be a
+ # BigDecimal, it still has the chance to post-process the string and get the
+ # real value.
+ #
+ # Use <tt>ActiveSupport.use_standard_json_big_decimal_format = true</tt> to
+ # override this behavior.
+ def as_json(options = nil) #:nodoc:
+ if finite?
+ ActiveSupport.encode_big_decimal_as_string ? to_s : self
+ else
+ nil
+ end
+ end
end
class Regexp
- def as_json(options = nil) to_s end #:nodoc:
+ def as_json(options = nil) #:nodoc:
+ to_s
+ end
end
module Enumerable
@@ -202,6 +257,12 @@ module Enumerable
end
end
+class Range
+ def as_json(options = nil) #:nodoc:
+ to_s
+ end
+end
+
class Array
def as_json(options = nil) #:nodoc:
# use encoder as a proxy to call as_json on all elements, to protect from circular references
@@ -220,9 +281,9 @@ class Hash
# create a subset of the hash by applying :only or :except
subset = if options
if attrs = options[:only]
- slice(*Array.wrap(attrs))
+ slice(*Array(attrs))
elsif attrs = options[:except]
- except(*Array.wrap(attrs))
+ except(*Array(attrs))
else
self
end
@@ -232,11 +293,10 @@ class Hash
# 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)
- result = self.is_a?(ActiveSupport::OrderedHash) ? ActiveSupport::OrderedHash : Hash
- result[subset.map { |k, v| [k.to_s, encoder.as_json(v, options)] }]
+ Hash[subset.map { |k, v| [k.to_s, encoder.as_json(v, options)] }]
end
- def encode_json(encoder)
+ 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);
diff --git a/lib/active_support/json/variable.rb b/lib/active_support/json/variable.rb
index 5685ed1..d69dab6 100644
--- a/lib/active_support/json/variable.rb
+++ b/lib/active_support/json/variable.rb
@@ -1,7 +1,16 @@
+require 'active_support/deprecation'
+
module ActiveSupport
module JSON
- # A string that returns itself as its JSON-encoded form.
+ # Deprecated: A string that returns itself as its JSON-encoded form.
class Variable < String
+ def initialize(*args)
+ message = 'ActiveSupport::JSON::Variable is deprecated and will be removed in Rails 4.1. ' \
+ 'For your own custom JSON literals, define #as_json and #encode_json yourself.'
+ ActiveSupport::Deprecation.warn message
+ super
+ end
+
def as_json(options = nil) self end #:nodoc:
def encode_json(encoder) self end #:nodoc:
end
diff --git a/lib/active_support/key_generator.rb b/lib/active_support/key_generator.rb
new file mode 100644
index 0000000..37124fb
--- /dev/null
+++ b/lib/active_support/key_generator.rb
@@ -0,0 +1,75 @@
+require 'thread_safe'
+require 'openssl'
+
+module ActiveSupport
+ # KeyGenerator is a simple wrapper around OpenSSL's implementation of PBKDF2
+ # It can be used to derive a number of keys for various purposes from a given secret.
+ # This lets rails applications have a single secure secret, but avoid reusing that
+ # key in multiple incompatible contexts.
+ class KeyGenerator
+ def initialize(secret, options = {})
+ @secret = secret
+ # The default iterations are higher than required for our key derivation uses
+ # on the off chance someone uses this for password storage
+ @iterations = options[:iterations] || 2**16
+ end
+
+ # Returns a derived key suitable for use. The default key_size is chosen
+ # to be compatible with the default settings of ActiveSupport::MessageVerifier.
+ # i.e. OpenSSL::Digest::SHA1#block_length
+ def generate_key(salt, key_size=64)
+ OpenSSL::PKCS5.pbkdf2_hmac_sha1(@secret, salt, @iterations, key_size)
+ end
+ end
+
+ # CachingKeyGenerator is a wrapper around KeyGenerator which allows users to avoid
+ # re-executing the key generation process when it's called using the same salt and
+ # key_size
+ class CachingKeyGenerator
+ def initialize(key_generator)
+ @key_generator = key_generator
+ @cache_keys = ThreadSafe::Cache.new
+ end
+
+ # Returns a derived key suitable for use. The default key_size is chosen
+ # to be compatible with the default settings of ActiveSupport::MessageVerifier.
+ # i.e. OpenSSL::Digest::SHA1#block_length
+ def generate_key(salt, key_size=64)
+ @cache_keys["#{salt}#{key_size}"] ||= @key_generator.generate_key(salt, key_size)
+ end
+ end
+
+ class LegacyKeyGenerator # :nodoc:
+ SECRET_MIN_LENGTH = 30 # Characters
+
+ def initialize(secret)
+ ensure_secret_secure(secret)
+ @secret = secret
+ end
+
+ def generate_key(salt)
+ @secret
+ end
+
+ private
+
+ # To prevent users from using something insecure like "Password" we make sure that the
+ # 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"
+ 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"
+ end
+ end
+ end
+end
diff --git a/lib/active_support/lazy_load_hooks.rb b/lib/active_support/lazy_load_hooks.rb
index 182d2b7..e489512 100644
--- a/lib/active_support/lazy_load_hooks.rb
+++ b/lib/active_support/lazy_load_hooks.rb
@@ -1,23 +1,25 @@
-# lazy_load_hooks allows rails to lazily load a lot of components and thus making the app boot faster. Because of
-# this feature now there is no need to require <tt>ActiveRecord::Base</tt> at boot time purely to apply configuration. Instead
-# a hook is registered that applies configuration once <tt>ActiveRecord::Base</tt> is loaded. Here <tt>ActiveRecord::Base</tt> is used
-# as example but this feature can be applied elsewhere too.
-#
-# Here is an example where +on_load+ method is called to register a hook.
-#
-# initializer "active_record.initialize_timezone" do
-# ActiveSupport.on_load(:active_record) do
-# self.time_zone_aware_attributes = true
-# self.default_timezone = :utc
-# end
-# end
-#
-# When the entirety of +activerecord/lib/active_record/base.rb+ has been evaluated then +run_load_hooks+ is invoked.
-# The very last line of +activerecord/lib/active_record/base.rb+ is:
-#
-# ActiveSupport.run_load_hooks(:active_record, ActiveRecord::Base)
-#
module ActiveSupport
+ # lazy_load_hooks allows rails to lazily load a lot of components and thus
+ # making the app boot faster. Because of this feature now there is no need to
+ # require <tt>ActiveRecord::Base</tt> at boot time purely to apply
+ # configuration. Instead a hook is registered that applies configuration once
+ # <tt>ActiveRecord::Base</tt> is loaded. Here <tt>ActiveRecord::Base</tt> is
+ # used as example but this feature can be applied elsewhere too.
+ #
+ # Here is an example where +on_load+ method is called to register a hook.
+ #
+ # initializer 'active_record.initialize_timezone' do
+ # ActiveSupport.on_load(:active_record) do
+ # self.time_zone_aware_attributes = true
+ # self.default_timezone = :utc
+ # end
+ # end
+ #
+ # When the entirety of +activerecord/lib/active_record/base.rb+ has been
+ # evaluated then +run_load_hooks+ is invoked. The very last line of
+ # +activerecord/lib/active_record/base.rb+ is:
+ #
+ # ActiveSupport.run_load_hooks(:active_record, ActiveRecord::Base)
@load_hooks = Hash.new { |h,k| h[k] = [] }
@loaded = Hash.new { |h,k| h[k] = [] }
diff --git a/lib/active_support/locale/en.yml b/lib/active_support/locale/en.yml
index a1499bc..a4563ac 100644
--- a/lib/active_support/locale/en.yml
+++ b/lib/active_support/locale/en.yml
@@ -16,9 +16,9 @@ en:
abbr_month_names: [~, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec]
# Used in date_select and datetime_select.
order:
- - :year
- - :month
- - :day
+ - year
+ - month
+ - day
time:
formats:
@@ -34,3 +34,100 @@ en:
words_connector: ", "
two_words_connector: " and "
last_word_connector: ", and "
+ number:
+ # Used in NumberHelper.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 NumberHelper.number_to_currency()
+ currency:
+ format:
+ # Where is the currency sign? %u is the currency unit, %n the number (default: $5.00)
+ 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 NumberHelper.number_to_percentage()
+ percentage:
+ format:
+ # These five are to override number.format and are optional
+ # separator:
+ delimiter: ""
+ # precision:
+ # significant: false
+ # strip_insignificant_zeros: false
+ format: "%n%"
+
+ # Used in NumberHelper.number_to_rounded()
+ precision:
+ format:
+ # These five are to override number.format and are optional
+ # separator:
+ delimiter: ""
+ # precision:
+ # significant: false
+ # strip_insignificant_zeros: false
+
+ # Used in NumberHelper.number_to_human_size() and NumberHelper.number_to_human()
+ human:
+ format:
+ # These five are to override number.format and are optional
+ # separator:
+ 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:
+ one: "Byte"
+ other: "Bytes"
+ kb: "KB"
+ mb: "MB"
+ gb: "GB"
+ tb: "TB"
+ # Used in NumberHelper.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
diff --git a/lib/active_support/log_subscriber.rb b/lib/active_support/log_subscriber.rb
index 6296c1d..e95dc5a 100644
--- a/lib/active_support/log_subscriber.rb
+++ b/lib/active_support/log_subscriber.rb
@@ -1,12 +1,15 @@
require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/core_ext/class/attribute'
+require 'active_support/subscriber'
module ActiveSupport
- # ActiveSupport::LogSubscriber is an object set to consume ActiveSupport::Notifications
- # with the sole purpose of logging them. The log subscriber dispatches notifications to
- # a registered object based on its given namespace.
+ # ActiveSupport::LogSubscriber is an object set to consume
+ # ActiveSupport::Notifications with the sole purpose of logging them.
+ # The log subscriber dispatches notifications to a registered object based
+ # on its given namespace.
#
- # An example would be Active Record log subscriber responsible for logging queries:
+ # An example would be Active Record log subscriber responsible for logging
+ # queries:
#
# module ActiveRecord
# class LogSubscriber < ActiveSupport::LogSubscriber
@@ -20,17 +23,18 @@ module ActiveSupport
#
# ActiveRecord::LogSubscriber.attach_to :active_record
#
- # Since we need to know all instance methods before attaching the log subscriber,
- # the line above should be called after your <tt>ActiveRecord::LogSubscriber</tt> definition.
+ # Since we need to know all instance methods before attaching the log
+ # subscriber, the line above should be called after your
+ # <tt>ActiveRecord::LogSubscriber</tt> definition.
#
# After configured, whenever a "sql.active_record" notification is published,
# it will properly dispatch the event (ActiveSupport::Notifications::Event) to
# the sql method.
#
- # Log subscriber also has some helpers to deal with logging and automatically flushes
- # all logs when the request finishes (via action_dispatch.callback notification) in
- # a Rails environment.
- class LogSubscriber
+ # Log subscriber also has some helpers to deal with logging and automatically
+ # flushes all logs when the request finishes (via action_dispatch.callback
+ # notification) in a Rails environment.
+ class LogSubscriber < Subscriber
# Embed in a String to clear all previous ANSI sequences.
CLEAR = "\e[0m"
BOLD = "\e[1m"
@@ -48,73 +52,56 @@ module ActiveSupport
mattr_accessor :colorize_logging
self.colorize_logging = true
- class_attribute :logger
-
class << self
- remove_method :logger
def logger
- @logger ||= Rails.logger if defined?(Rails)
- end
-
- def attach_to(namespace, log_subscriber=new, notifier=ActiveSupport::Notifications)
- log_subscribers << log_subscriber
- @@flushable_loggers = nil
-
- log_subscriber.public_methods(false).each do |event|
- next if 'call' == event.to_s
-
- notifier.subscribe("#{event}.#{namespace}", log_subscriber)
+ @logger ||= if defined?(Rails) && Rails.respond_to?(:logger)
+ Rails.logger
end
end
- def log_subscribers
- @@log_subscribers ||= []
- end
+ attr_writer :logger
- def flushable_loggers
- @@flushable_loggers ||= begin
- loggers = log_subscribers.map(&:logger)
- loggers.uniq!
- loggers.select { |l| l.respond_to?(:flush) }
- end
+ def log_subscribers
+ subscribers
end
# Flush all log_subscribers' logger.
def flush_all!
- flushable_loggers.each { |log| log.flush }
+ logger.flush if logger.respond_to?(:flush)
end
end
- def call(message, *args)
- return unless logger
+ def logger
+ LogSubscriber.logger
+ end
+
+ def start(name, id, payload)
+ super if logger
+ end
- method = message.split('.').first
- begin
- send(method, ActiveSupport::Notifications::Event.new(message, *args))
- rescue Exception => e
- logger.error "Could not log #{message.inspect} event. #{e.class}: #{e.message}"
- end
+ def finish(name, id, payload)
+ super if logger
+ rescue Exception => e
+ logger.error "Could not log #{name.inspect} event. #{e.class}: #{e.message} #{e.backtrace}"
end
protected
%w(info debug warn error fatal unknown).each do |level|
class_eval <<-METHOD, __FILE__, __LINE__ + 1
- def #{level}(*args, &block)
- return unless logger
- logger.#{level}(*args, &block)
+ def #{level}(progname = nil, &block)
+ logger.#{level}(progname, &block) if logger
end
METHOD
end
# Set color by using a string or one of the defined constants. If a third
- # option is set to true, it also adds bold to the string. This is based
+ # option is set to +true+, it also adds bold to the string. This is based
# on the Highline implementation and will automatically append CLEAR to the
# end of the returned String.
- #
def color(text, color, bold=false)
return text unless colorize_logging
- color = self.class.const_get(color.to_s.upcase) if color.is_a?(Symbol)
+ color = self.class.const_get(color.upcase) if color.is_a?(Symbol)
bold = bold ? BOLD : ""
"#{bold}#{color}#{text}#{CLEAR}"
end
diff --git a/lib/active_support/log_subscriber/test_helper.rb b/lib/active_support/log_subscriber/test_helper.rb
index dcfcf0b..f9a9868 100644
--- a/lib/active_support/log_subscriber/test_helper.rb
+++ b/lib/active_support/log_subscriber/test_helper.rb
@@ -15,7 +15,7 @@ module ActiveSupport
# end
#
# def test_basic_query_logging
- # Developer.all
+ # Developer.all.to_a
# wait
# assert_equal 1, @logger.logged(:debug).size
# assert_match(/Developer Load/, @logger.logged(:debug).last)
@@ -23,15 +23,15 @@ module ActiveSupport
# end
# end
#
- # All you need to do is to ensure that your log subscriber is added to Rails::Subscriber,
- # as in the second line of the code above. The test helpers are responsible for setting
- # up the queue, subscriptions and turning colors in logs off.
- #
- # The messages are available in the @logger instance, which is a logger with limited
- # powers (it actually does not send anything to your output), and you can collect them
- # doing @logger.logged(level), where level is the level used in logging, like info,
- # debug, warn and so on.
+ # All you need to do is to ensure that your log subscriber is added to
+ # Rails::Subscriber, as in the second line of the code above. The test
+ # helpers are responsible for setting up the queue, subscriptions and
+ # turning colors in logs off.
#
+ # The messages are available in the @logger instance, which is a logger with
+ # limited powers (it actually does not send anything to your output), and
+ # you can collect them doing @logger.logged(level), where level is the level
+ # used in logging, like info, debug, warn and so on.
module TestHelper
def setup
@logger = MockLogger.new
@@ -50,7 +50,7 @@ module ActiveSupport
end
class MockLogger
- include ActiveSupport::BufferedLogger::Severity
+ include ActiveSupport::Logger::Severity
attr_reader :flush_count
attr_accessor :level
@@ -61,8 +61,12 @@ module ActiveSupport
@logged = Hash.new { |h,k| h[k] = [] }
end
- def method_missing(level, message)
- @logged[level] << message
+ def method_missing(level, message = nil)
+ if block_given?
+ @logged[level] << yield
+ else
+ @logged[level] << message
+ end
end
def logged(level)
@@ -73,7 +77,7 @@ module ActiveSupport
@flush_count += 1
end
- ActiveSupport::BufferedLogger::Severity.constants.each do |severity|
+ ActiveSupport::Logger::Severity.constants.each do |severity|
class_eval <<-EOT, __FILE__, __LINE__ + 1
def #{severity.downcase}?
#{severity} >= @level
@@ -87,12 +91,11 @@ module ActiveSupport
@notifier.wait
end
- # Overwrite if you use another logger in your log subscriber:
+ # Overwrite if you use another logger in your log subscriber.
#
# def logger
# ActiveRecord::Base.logger = @logger
# end
- #
def set_logger(logger)
ActiveSupport::LogSubscriber.logger = logger
end
diff --git a/lib/active_support/logger.rb b/lib/active_support/logger.rb
new file mode 100644
index 0000000..4a55bbb
--- /dev/null
+++ b/lib/active_support/logger.rb
@@ -0,0 +1,57 @@
+require 'active_support/core_ext/class/attribute_accessors'
+require 'active_support/logger_silence'
+require 'logger'
+
+module ActiveSupport
+ class Logger < ::Logger
+ include LoggerSilence
+
+ # Broadcasts logs to multiple loggers.
+ def self.broadcast(logger) # :nodoc:
+ Module.new do
+ define_method(:add) do |*args, &block|
+ logger.add(*args, &block)
+ super(*args, &block)
+ end
+
+ define_method(:<<) do |x|
+ logger << x
+ super(x)
+ end
+
+ define_method(:close) do
+ logger.close
+ super()
+ end
+
+ define_method(:progname=) do |name|
+ logger.progname = name
+ super(name)
+ end
+
+ define_method(:formatter=) do |formatter|
+ logger.formatter = formatter
+ super(formatter)
+ end
+
+ define_method(:level=) do |level|
+ logger.level = level
+ super(level)
+ end
+ end
+ end
+
+ def initialize(*args)
+ super
+ @formatter = SimpleFormatter.new
+ end
+
+ # Simple formatter which only displays the message.
+ class SimpleFormatter < ::Logger::Formatter
+ # This method is invoked when a log event occurs
+ def call(severity, timestamp, progname, msg)
+ "#{String === msg ? msg : msg.inspect}\n"
+ end
+ end
+ end
+end
diff --git a/lib/active_support/logger_silence.rb b/lib/active_support/logger_silence.rb
new file mode 100644
index 0000000..a8efdef
--- /dev/null
+++ b/lib/active_support/logger_silence.rb
@@ -0,0 +1,24 @@
+require 'active_support/concern'
+
+module LoggerSilence
+ extend ActiveSupport::Concern
+
+ included do
+ cattr_accessor :silencer
+ self.silencer = true
+ end
+
+ # Silences the logger for the duration of the block.
+ def silence(temporary_level = Logger::ERROR)
+ if silencer
+ begin
+ old_logger_level, self.level = level, temporary_level
+ yield self
+ ensure
+ self.level = old_logger_level
+ end
+ else
+ yield self
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/active_support/memoizable.rb b/lib/active_support/memoizable.rb
deleted file mode 100644
index 4c67676..0000000
--- a/lib/active_support/memoizable.rb
+++ /dev/null
@@ -1,116 +0,0 @@
-require 'active_support/core_ext/kernel/singleton_class'
-require 'active_support/core_ext/module/aliasing'
-require 'active_support/deprecation'
-
-module ActiveSupport
- module Memoizable
- def self.extended(base)
- ActiveSupport::Deprecation.warn "ActiveSupport::Memoizable is deprecated and will be removed in future releases," \
- "simply use Ruby memoization pattern instead.", caller
- super
- end
-
- def self.memoized_ivar_for(symbol)
- "@_memoized_#{symbol.to_s.sub(/\?\Z/, '_query').sub(/!\Z/, '_bang')}".to_sym
- end
-
- module InstanceMethods
- def self.included(base)
- base.class_eval do
- unless base.method_defined?(:freeze_without_memoizable)
- alias_method_chain :freeze, :memoizable
- end
- end
- end
-
- def freeze_with_memoizable
- memoize_all unless frozen?
- freeze_without_memoizable
- end
-
- def memoize_all
- prime_cache ".*"
- end
-
- def unmemoize_all
- flush_cache ".*"
- end
-
- def prime_cache(*syms)
- syms.each do |sym|
- methods.each do |m|
- if m.to_s =~ /^_unmemoized_(#{sym})/
- if method(m).arity == 0
- __send__($1)
- else
- ivar = ActiveSupport::Memoizable.memoized_ivar_for($1)
- instance_variable_set(ivar, {})
- end
- end
- end
- end
- end
-
- def flush_cache(*syms)
- syms.each do |sym|
- (methods + private_methods + protected_methods).each do |m|
- if m.to_s =~ /^_unmemoized_(#{sym.to_s.gsub(/\?\Z/, '\?')})/
- ivar = ActiveSupport::Memoizable.memoized_ivar_for($1)
- instance_variable_get(ivar).clear if instance_variable_defined?(ivar)
- end
- end
- end
- end
- end
-
- def memoize(*symbols)
- symbols.each do |symbol|
- original_method = :"_unmemoized_#{symbol}"
- memoized_ivar = ActiveSupport::Memoizable.memoized_ivar_for(symbol)
-
- class_eval <<-EOS, __FILE__, __LINE__ + 1
- include InstanceMethods # include InstanceMethods
- #
- if method_defined?(:#{original_method}) # if method_defined?(:_unmemoized_mime_type)
- raise "Already memoized #{symbol}" # raise "Already memoized mime_type"
- end # end
- alias #{original_method} #{symbol} # alias _unmemoized_mime_type mime_type
- #
- if instance_method(:#{symbol}).arity == 0 # if instance_method(:mime_type).arity == 0
- def #{symbol}(reload = false) # def mime_type(reload = false)
- if reload || !defined?(#{memoized_ivar}) || #{memoized_ivar}.empty? # if reload || !defined?(@_memoized_mime_type) || @_memoized_mime_type.empty?
- #{memoized_ivar} = [#{original_method}] # @_memoized_mime_type = [_unmemoized_mime_type]
- end # end
- #{memoized_ivar}[0] # @_memoized_mime_type[0]
- end # end
- else # else
- def #{symbol}(*args) # def mime_type(*args)
- #{memoized_ivar} ||= {} unless frozen? # @_memoized_mime_type ||= {} unless frozen?
- args_length = method(:#{original_method}).arity # args_length = method(:_unmemoized_mime_type).arity
- if args.length == args_length + 1 && # if args.length == args_length + 1 &&
- (args.last == true || args.last == :reload) # (args.last == true || args.last == :reload)
- reload = args.pop # reload = args.pop
- end # end
- #
- if defined?(#{memoized_ivar}) && #{memoized_ivar} # if defined?(@_memoized_mime_type) && @_memoized_mime_type
- if !reload && #{memoized_ivar}.has_key?(args) # if !reload && @_memoized_mime_type.has_key?(args)
- #{memoized_ivar}[args] # @_memoized_mime_type[args]
- elsif #{memoized_ivar} # elsif @_memoized_mime_type
- #{memoized_ivar}[args] = #{original_method}(*args) # @_memoized_mime_type[args] = _unmemoized_mime_type(*args)
- end # end
- else # else
- #{original_method}(*args) # _unmemoized_mime_type(*args)
- end # end
- end # end
- end # end
- #
- if private_method_defined?(#{original_method.inspect}) # if private_method_defined?(:_unmemoized_mime_type)
- private #{symbol.inspect} # private :mime_type
- elsif protected_method_defined?(#{original_method.inspect}) # elsif protected_method_defined?(:_unmemoized_mime_type)
- protected #{symbol.inspect} # protected :mime_type
- end # end
- EOS
- end
- end
- end
-end
diff --git a/lib/active_support/message_encryptor.rb b/lib/active_support/message_encryptor.rb
index abe9c93..bffdfc6 100644
--- a/lib/active_support/message_encryptor.rb
+++ b/lib/active_support/message_encryptor.rb
@@ -1,14 +1,22 @@
require 'openssl'
-require 'active_support/base64'
+require 'base64'
+require 'active_support/core_ext/array/extract_options'
module ActiveSupport
- # MessageEncryptor is a simple way to encrypt values which get stored somewhere
- # you don't trust.
+ # MessageEncryptor is a simple way to encrypt values which get stored
+ # somewhere you don't trust.
#
- # The cipher text and initialization vector are base64 encoded and returned to you.
+ # The cipher text and initialization vector are base64 encoded and returned
+ # to you.
#
- # 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.
+ # This can be used in situations similar to the <tt>MessageVerifier</tt>, but
+ # where you don't want users to be able to determine the value of the payload.
+ #
+ # salt = SecureRandom.random_bytes(64)
+ # key = ActiveSupport::KeyGenerator.new('password').generate_key(salt) # => "\x89\xE0\x156\xAC..."
+ # crypt = ActiveSupport::MessageEncryptor.new(key) # => #<ActiveSupport::MessageEncryptor ...>
+ # encrypted_data = crypt.encrypt_and_sign('my secret data') # => "NlFBTTMwOUV5UlA1QlNEN2xkY2d6eThYWWh..."
+ # crypt.decrypt_and_verify(encrypted_data) # => "my secret data"
class MessageEncryptor
module NullSerializer #:nodoc:
def self.load(value)
@@ -21,40 +29,36 @@ module ActiveSupport
end
class InvalidMessage < StandardError; end
- OpenSSLCipherError = OpenSSL::Cipher.const_defined?(:CipherError) ? OpenSSL::Cipher::CipherError : OpenSSL::CipherError
-
- def initialize(secret, options = {})
- unless options.is_a?(Hash)
- ActiveSupport::Deprecation.warn "The second parameter should be an options hash. Use :cipher => 'algorithm' to specify the cipher algorithm."
- options = { :cipher => options }
- end
-
+ OpenSSLCipherError = OpenSSL::Cipher::CipherError
+
+ # Initialize a new MessageEncryptor. +secret+ must be at least as long as
+ # the cipher key size. For the default 'aes-256-cbc' cipher, this is 256
+ # bits. If you are using a user-entered secret, you can generate a suitable
+ # key with <tt>OpenSSL::Digest::SHA256.new(user_secret).digest</tt> or
+ # similar.
+ #
+ # Options:
+ # * <tt>:cipher</tt> - Cipher to use. Can be any cipher returned by
+ # <tt>OpenSSL::Cipher.ciphers</tt>. Default is 'aes-256-cbc'.
+ # * <tt>:serializer</tt> - Object serializer to use. Default is +Marshal+.
+ def initialize(secret, *signature_key_or_options)
+ options = signature_key_or_options.extract_options!
+ sign_secret = signature_key_or_options.first
@secret = secret
+ @sign_secret = sign_secret
@cipher = options[:cipher] || 'aes-256-cbc'
- @verifier = MessageVerifier.new(@secret, :serializer => NullSerializer)
+ @verifier = MessageVerifier.new(@sign_secret || @secret, :serializer => NullSerializer)
@serializer = options[:serializer] || Marshal
end
- def encrypt(value)
- ActiveSupport::Deprecation.warn "MessageEncryptor#encrypt is deprecated as it is not safe without a signature. " \
- "Please use MessageEncryptor#encrypt_and_sign instead."
- _encrypt(value)
- end
-
- def decrypt(value)
- ActiveSupport::Deprecation.warn "MessageEncryptor#decrypt is deprecated as it is not safe without a signature. " \
- "Please use MessageEncryptor#decrypt_and_verify instead."
- _decrypt(value)
- end
-
- # Encrypt and sign a message. We need to sign the message in order to avoid padding attacks.
- # Reference: http://www.limited-entropy.com/padding-oracle-attacks
+ # Encrypt and sign a message. We need to sign the message in order to avoid
+ # padding attacks. Reference: http://www.limited-entropy.com/padding-oracle-attacks.
def encrypt_and_sign(value)
verifier.generate(_encrypt(value))
end
- # Decrypt and verify a message. We need to verify the message in order to avoid padding attacks.
- # Reference: http://www.limited-entropy.com/padding-oracle-attacks
+ # Decrypt and verify a message. We need to verify the message in order to
+ # avoid padding attacks. Reference: http://www.limited-entropy.com/padding-oracle-attacks.
def decrypt_and_verify(value)
_decrypt(verifier.verify(value))
end
@@ -63,12 +67,11 @@ module ActiveSupport
def _encrypt(value)
cipher = new_cipher
- # Rely on OpenSSL for the initialization vector
- iv = cipher.random_iv
-
cipher.encrypt
cipher.key = @secret
- cipher.iv = iv
+
+ # Rely on OpenSSL for the initialization vector
+ iv = cipher.random_iv
encrypted_data = cipher.update(@serializer.dump(value))
encrypted_data << cipher.final
diff --git a/lib/active_support/message_verifier.rb b/lib/active_support/message_verifier.rb
index 0d9580a..e0cd92a 100644
--- a/lib/active_support/message_verifier.rb
+++ b/lib/active_support/message_verifier.rb
@@ -1,13 +1,12 @@
-require 'active_support/base64'
-require 'active_support/deprecation'
+require 'base64'
require 'active_support/core_ext/object/blank'
module ActiveSupport
- # +MessageVerifier+ makes it easy to generate and verify messages which are signed
- # to prevent tampering.
+ # +MessageVerifier+ makes it easy to generate and verify messages which are
+ # signed to prevent tampering.
#
- # This is useful for cases like remember-me tokens and auto-unsubscribe links where the
- # session store isn't suitable or available.
+ # This is useful for cases like remember-me tokens and auto-unsubscribe links
+ # where the session store isn't suitable or available.
#
# Remember Me:
# cookies[:remember_me] = @verifier.generate([@user.id, 2.weeks.from_now])
@@ -19,20 +18,15 @@ module ActiveSupport
# self.current_user = User.find(id)
# end
#
- # By default it uses Marshal to serialize the message. If you want to use another
- # serialization method, you can set the serializer attribute to something that responds
- # to dump and load, e.g.:
+ # By default it uses Marshal to serialize the message. If you want to use
+ # another serialization method, you can set the serializer in the options
+ # hash upon initialization:
#
- # @verifier.serializer = YAML
+ # @verifier = ActiveSupport::MessageVerifier.new('s3Krit', serializer: YAML)
class MessageVerifier
class InvalidSignature < StandardError; end
def initialize(secret, options = {})
- unless options.is_a?(Hash)
- ActiveSupport::Deprecation.warn "The second parameter should be an options hash. Use :digest => 'algorithm' to specify the digest algorithm."
- options = { :digest => options }
- end
-
@secret = secret
@digest = options[:digest] || 'SHA1'
@serializer = options[:serializer] || Marshal
diff --git a/lib/active_support/multibyte.rb b/lib/active_support/multibyte.rb
index 57e8e24..ffebd9a 100644
--- a/lib/active_support/multibyte.rb
+++ b/lib/active_support/multibyte.rb
@@ -1,44 +1,21 @@
-# encoding: utf-8
-require 'active_support/core_ext/module/attribute_accessors'
-
module ActiveSupport #:nodoc:
module Multibyte
- autoload :EncodingError, 'active_support/multibyte/exceptions'
autoload :Chars, 'active_support/multibyte/chars'
autoload :Unicode, 'active_support/multibyte/unicode'
- # The proxy class returned when calling mb_chars. You can use this accessor to configure your own proxy
- # class so you can support other encodings. See the ActiveSupport::Multibyte::Chars implementation for
- # an example how to do this.
+ # The proxy class returned when calling mb_chars. You can use this accessor
+ # to configure your own proxy class so you can support other encodings. See
+ # the ActiveSupport::Multibyte::Chars implementation for an example how to
+ # do this.
#
- # Example:
# ActiveSupport::Multibyte.proxy_class = CharsForUTF32
def self.proxy_class=(klass)
@proxy_class = klass
end
- # Returns the current proxy class
+ # Returns the current proxy class.
def self.proxy_class
@proxy_class ||= ActiveSupport::Multibyte::Chars
end
-
- # Regular expressions that describe valid byte sequences for a character
- VALID_CHARACTER = {
- # Borrowed from the Kconv library by Shinji KONO - (also as seen on the W3C site)
- 'UTF-8' => /\A(?:
- [\x00-\x7f] |
- [\xc2-\xdf] [\x80-\xbf] |
- \xe0 [\xa0-\xbf] [\x80-\xbf] |
- [\xe1-\xef] [\x80-\xbf] [\x80-\xbf] |
- \xf0 [\x90-\xbf] [\x80-\xbf] [\x80-\xbf] |
- [\xf1-\xf3] [\x80-\xbf] [\x80-\xbf] [\x80-\xbf] |
- \xf4 [\x80-\x8f] [\x80-\xbf] [\x80-\xbf])\z /xn,
- # Quick check for valid Shift-JIS characters, disregards the odd-even pairing
- 'Shift_JIS' => /\A(?:
- [\x00-\x7e\xa1-\xdf] |
- [\x81-\x9f\xe0-\xef] [\x40-\x7e\x80-\x9e\x9f-\xfc])\z /xn
- }
end
end
-
-require 'active_support/multibyte/utils'
\ No newline at end of file
diff --git a/lib/active_support/multibyte/chars.rb b/lib/active_support/multibyte/chars.rb
index b78d92f..a42e7f6 100644
--- a/lib/active_support/multibyte/chars.rb
+++ b/lib/active_support/multibyte/chars.rb
@@ -1,25 +1,32 @@
# encoding: utf-8
+require 'active_support/json'
require 'active_support/core_ext/string/access'
require 'active_support/core_ext/string/behavior'
+require 'active_support/core_ext/module/delegation'
module ActiveSupport #:nodoc:
module Multibyte #:nodoc:
- # Chars enables you to work transparently with UTF-8 encoding in the Ruby String class without having extensive
- # knowledge about the encoding. A Chars object accepts a string upon initialization and proxies String methods in an
- # encoding safe manner. All the normal String methods are also implemented on the proxy.
+ # Chars enables you to work transparently with UTF-8 encoding in the Ruby
+ # String class without having extensive knowledge about the encoding. A
+ # Chars object accepts a string upon initialization and proxies String
+ # methods in an encoding safe manner. All the normal String methods are also
+ # implemented on the proxy.
#
- # String methods are proxied through the Chars object, and can be accessed through the +mb_chars+ method. Methods
- # which would normally return a String object now return a Chars object so methods can be chained.
+ # String methods are proxied through the Chars object, and can be accessed
+ # through the +mb_chars+ method. Methods which would normally return a
+ # String object now return a Chars object so methods can be chained.
#
- # "The Perfect String ".mb_chars.downcase.strip.normalize # => "the perfect string"
+ # 'The Perfect String '.mb_chars.downcase.strip.normalize # => "the perfect string"
#
- # Chars objects are perfectly interchangeable with String objects as long as no explicit class checks are made.
- # If certain methods do explicitly check the class, call +to_s+ before you pass chars objects to them.
+ # Chars objects are perfectly interchangeable with String objects as long as
+ # no explicit class checks are made. If certain methods do explicitly check
+ # the class, call +to_s+ before you pass chars objects to them.
#
- # bad.explicit_checking_method "T".mb_chars.downcase.to_s
+ # bad.explicit_checking_method 'T'.mb_chars.downcase.to_s
#
- # The default Chars implementation assumes that the encoding of the string is UTF-8, if you want to handle different
- # encodings you can write your own multibyte string handler and configure it through
+ # The default Chars implementation assumes that the encoding of the string
+ # is UTF-8, if you want to handle different encodings you can write your own
+ # multibyte string handler and configure it through
# ActiveSupport::Multibyte.proxy_class.
#
# class CharsForUTF32
@@ -34,327 +41,97 @@ module ActiveSupport #:nodoc:
#
# ActiveSupport::Multibyte.proxy_class = CharsForUTF32
class Chars
+ include Comparable
attr_reader :wrapped_string
alias to_s wrapped_string
alias to_str wrapped_string
- if RUBY_VERSION >= "1.9"
- # Creates a new Chars instance by wrapping _string_.
- def initialize(string)
- @wrapped_string = string
- @wrapped_string.force_encoding(Encoding::UTF_8) unless @wrapped_string.frozen?
- end
- else
- def initialize(string) #:nodoc:
- @wrapped_string = string
- end
+ delegate :<=>, :=~, :acts_like_string?, :to => :wrapped_string
+
+ # Creates a new Chars instance by wrapping _string_.
+ def initialize(string)
+ @wrapped_string = string
+ @wrapped_string.force_encoding(Encoding::UTF_8) unless @wrapped_string.frozen?
end
# Forward all undefined methods to the wrapped string.
def method_missing(method, *args, &block)
if method.to_s =~ /!$/
- @wrapped_string.__send__(method, *args, &block)
- self
+ 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
- # Returns +true+ if _obj_ responds to the given method. Private methods are included in the search
- # only if the optional second parameter evaluates to +true+.
- def respond_to?(method, include_private=false)
- super || @wrapped_string.respond_to?(method, include_private)
- end
-
- # Enable more predictable duck-typing on String-like classes. See Object#acts_like?.
- def acts_like_string?
- true
+ # Returns +true+ if _obj_ responds to the given method. Private methods
+ # are included in the search only if the optional second parameter
+ # evaluates to +true+.
+ def respond_to_missing?(method, include_private)
+ @wrapped_string.respond_to?(method, include_private)
end
- # Returns +true+ when the proxy class can handle the string. Returns +false+ otherwise.
+ # Returns +true+ when the proxy class can handle the string. Returns
+ # +false+ otherwise.
def self.consumes?(string)
- # Unpack is a little bit faster than regular expressions.
- string.unpack('U*')
- true
- rescue ArgumentError
- false
+ string.encoding == Encoding::UTF_8
end
- include Comparable
-
- # Returns -1, 0, or 1, depending on whether the Chars object is to be sorted before,
- # equal or after the object on the right side of the operation. It accepts any object
- # that implements +to_s+:
+ # Works just like <tt>String#split</tt>, with the exception that the items
+ # in the resulting list are Chars instances instead of String. This makes
+ # chaining methods easier.
#
- # 'é'.mb_chars <=> 'ü'.mb_chars # => -1
- #
- # See <tt>String#<=></tt> for more details.
- def <=>(other)
- @wrapped_string <=> other.to_s
- end
-
- if RUBY_VERSION < "1.9"
- # Returns +true+ if the Chars class can and should act as a proxy for the string _string_. Returns
- # +false+ otherwise.
- def self.wants?(string)
- $KCODE == 'UTF8' && consumes?(string)
- end
-
- # Returns a new Chars object containing the _other_ object concatenated to the string.
- #
- # Example:
- # ('Café'.mb_chars + ' périferôl').to_s # => "Café périferôl"
- def +(other)
- chars(@wrapped_string + other)
- end
-
- # Like <tt>String#=~</tt> only it returns the character offset (in codepoints) instead of the byte offset.
- #
- # Example:
- # 'Café périferôl'.mb_chars =~ /ô/ # => 12
- def =~(other)
- translate_offset(@wrapped_string =~ other)
- end
-
- # Inserts the passed string at specified codepoint offsets.
- #
- # Example:
- # 'Café'.mb_chars.insert(4, ' périferôl').to_s # => "Café périferôl"
- def insert(offset, fragment)
- unpacked = Unicode.u_unpack(@wrapped_string)
- unless offset > unpacked.length
- @wrapped_string.replace(
- Unicode.u_unpack(@wrapped_string).insert(offset, *Unicode.u_unpack(fragment)).pack('U*')
- )
- else
- raise IndexError, "index #{offset} out of string"
- end
- self
- end
-
- # Returns +true+ if contained string contains _other_. Returns +false+ otherwise.
- #
- # Example:
- # 'Café'.mb_chars.include?('é') # => true
- def include?(other)
- # We have to redefine this method because Enumerable defines it.
- @wrapped_string.include?(other)
- end
-
- # Returns the position _needle_ in the string, counting in codepoints. Returns +nil+ if _needle_ isn't found.
- #
- # Example:
- # 'Café périferôl'.mb_chars.index('ô') # => 12
- # 'Café périferôl'.mb_chars.index(/\w/u) # => 0
- def index(needle, offset=0)
- wrapped_offset = first(offset).wrapped_string.length
- index = @wrapped_string.index(needle, wrapped_offset)
- index ? (Unicode.u_unpack(@wrapped_string.slice(0...index)).size) : nil
- end
-
- # Returns the position _needle_ in the string, counting in
- # codepoints, searching backward from _offset_ or the end of the
- # string. Returns +nil+ if _needle_ isn't found.
- #
- # Example:
- # 'Café périferôl'.mb_chars.rindex('é') # => 6
- # 'Café périferôl'.mb_chars.rindex(/\w/u) # => 13
- def rindex(needle, offset=nil)
- offset ||= length
- wrapped_offset = first(offset).wrapped_string.length
- index = @wrapped_string.rindex(needle, wrapped_offset)
- index ? (Unicode.u_unpack(@wrapped_string.slice(0...index)).size) : nil
- end
-
- # Returns the number of codepoints in the string
- def size
- Unicode.u_unpack(@wrapped_string).size
- end
- alias_method :length, :size
-
- # Strips entire range of Unicode whitespace from the right of the string.
- def rstrip
- chars(@wrapped_string.gsub(Unicode::TRAILERS_PAT, ''))
- end
-
- # Strips entire range of Unicode whitespace from the left of the string.
- def lstrip
- chars(@wrapped_string.gsub(Unicode::LEADERS_PAT, ''))
- end
-
- # Strips entire range of Unicode whitespace from the right and left of the string.
- def strip
- rstrip.lstrip
- end
-
- # Returns the codepoint of the first character in the string.
- #
- # Example:
- # 'こんにちは'.mb_chars.ord # => 12371
- def ord
- Unicode.u_unpack(@wrapped_string)[0]
- end
-
- # Works just like <tt>String#rjust</tt>, only integer specifies characters instead of bytes.
- #
- # Example:
- #
- # "¾ cup".mb_chars.rjust(8).to_s
- # # => " ¾ cup"
- #
- # "¾ cup".mb_chars.rjust(8, " ").to_s # Use non-breaking whitespace
- # # => " ¾ cup"
- def rjust(integer, padstr=' ')
- justify(integer, :right, padstr)
- end
-
- # Works just like <tt>String#ljust</tt>, only integer specifies characters instead of bytes.
- #
- # Example:
- #
- # "¾ cup".mb_chars.rjust(8).to_s
- # # => "¾ cup "
- #
- # "¾ cup".mb_chars.rjust(8, " ").to_s # Use non-breaking whitespace
- # # => "¾ cup "
- def ljust(integer, padstr=' ')
- justify(integer, :left, padstr)
- end
-
- # Works just like <tt>String#center</tt>, only integer specifies characters instead of bytes.
- #
- # Example:
- #
- # "¾ cup".mb_chars.center(8).to_s
- # # => " ¾ cup "
- #
- # "¾ cup".mb_chars.center(8, " ").to_s # Use non-breaking whitespace
- # # => " ¾ cup "
- def center(integer, padstr=' ')
- justify(integer, :center, padstr)
- end
-
- else
- def =~(other)
- @wrapped_string =~ other
- end
- end
-
- # Works just like <tt>String#split</tt>, with the exception that the items in the resulting list are Chars
- # instances instead of String. This makes chaining methods easier.
- #
- # Example:
# 'Café périferôl'.mb_chars.split(/é/).map { |part| part.upcase.to_s } # => ["CAF", " P", "RIFERÔL"]
def split(*args)
- @wrapped_string.split(*args).map { |i| i.mb_chars }
+ @wrapped_string.split(*args).map { |i| self.class.new(i) }
end
- # Like <tt>String#[]=</tt>, except instead of byte offsets you specify character offsets.
- #
- # Example:
- #
- # s = "Müller"
- # s.mb_chars[2] = "e" # Replace character with offset 2
- # s
- # # => "Müeler"
- #
- # s = "Müller"
- # s.mb_chars[1, 2] = "ö" # Replace 2 characters at character offset 1
- # s
- # # => "Möler"
- def []=(*args)
- replace_by = args.pop
- # Indexed replace with regular expressions already works
- if args.first.is_a?(Regexp)
- @wrapped_string[*args] = replace_by
- else
- result = Unicode.u_unpack(@wrapped_string)
- case args.first
- when Fixnum
- raise IndexError, "index #{args[0]} out of string" if args[0] >= result.length
- min = args[0]
- max = args[1].nil? ? min : (min + args[1] - 1)
- range = Range.new(min, max)
- replace_by = [replace_by].pack('U') if replace_by.is_a?(Fixnum)
- when Range
- raise RangeError, "#{args[0]} out of range" if args[0].min >= result.length
- range = args[0]
- else
- needle = args[0].to_s
- min = index(needle)
- max = min + Unicode.u_unpack(needle).length - 1
- range = Range.new(min, max)
- end
- result[range] = Unicode.u_unpack(replace_by)
- @wrapped_string.replace(result.pack('U*'))
- end
+ # Works like like <tt>String#slice!</tt>, but returns an instance of
+ # Chars, or nil if the string was not modified.
+ def slice!(*args)
+ chars(@wrapped_string.slice!(*args))
end
# Reverses all characters in the string.
#
- # Example:
# 'Café'.mb_chars.reverse.to_s # => 'éfaC'
def reverse
- chars(Unicode.g_unpack(@wrapped_string).reverse.flatten.pack('U*'))
+ chars(Unicode.unpack_graphemes(@wrapped_string).reverse.flatten.pack('U*'))
end
- # Implements Unicode-aware slice with codepoints. Slicing on one point returns the codepoints for that
- # character.
+ # Limits the byte size of the string to a number of bytes without breaking
+ # characters. Usable when the storage for a string is limited for some
+ # reason.
#
- # Example:
- # 'こんにちは'.mb_chars.slice(2..3).to_s # => "にち"
- def slice(*args)
- if args.size > 2
- raise ArgumentError, "wrong number of arguments (#{args.size} for 1)" # Do as if we were native
- elsif (args.size == 2 && !(args.first.is_a?(Numeric) || args.first.is_a?(Regexp)))
- raise TypeError, "cannot convert #{args.first.class} into Integer" # Do as if we were native
- elsif (args.size == 2 && !args[1].is_a?(Numeric))
- raise TypeError, "cannot convert #{args[1].class} into Integer" # Do as if we were native
- elsif args[0].kind_of? Range
- cps = Unicode.u_unpack(@wrapped_string).slice(*args)
- result = cps.nil? ? nil : cps.pack('U*')
- elsif args[0].kind_of? Regexp
- result = @wrapped_string.slice(*args)
- elsif args.size == 1 && args[0].kind_of?(Numeric)
- character = Unicode.u_unpack(@wrapped_string)[args[0]]
- result = character && [character].pack('U')
- else
- cps = Unicode.u_unpack(@wrapped_string).slice(*args)
- result = cps && cps.pack('U*')
- end
- result && chars(result)
- end
- alias_method :[], :slice
-
- # Limit the byte size of the string to a number of bytes without breaking characters. Usable
- # when the storage for a string is limited for some reason.
- #
- # Example:
# 'こんにちは'.mb_chars.limit(7).to_s # => "こん"
def limit(limit)
slice(0...translate_offset(limit))
end
- # Convert characters in the string to uppercase.
+ # Converts characters in the string to uppercase.
#
- # Example:
# 'Laurent, où sont les tests ?'.mb_chars.upcase.to_s # => "LAURENT, OÙ SONT LES TESTS ?"
def upcase
- chars(Unicode.apply_mapping @wrapped_string, :uppercase_mapping)
+ chars Unicode.upcase(@wrapped_string)
end
- # Convert characters in the string to lowercase.
+ # Converts characters in the string to lowercase.
#
- # Example:
# 'VĚDA A VÝZKUM'.mb_chars.downcase.to_s # => "věda a výzkum"
def downcase
- chars(Unicode.apply_mapping @wrapped_string, :lowercase_mapping)
+ chars Unicode.downcase(@wrapped_string)
+ end
+
+ # Converts characters in the string to the opposite case.
+ #
+ # 'El Cañón".mb_chars.swapcase.to_s # => "eL cAÑÓN"
+ def swapcase
+ chars Unicode.swapcase(@wrapped_string)
end
# Converts the first character to uppercase and the remainder to lowercase.
#
- # Example:
# 'über'.mb_chars.capitalize.to_s # => "Über"
def capitalize
(slice(0) || chars('')).upcase + (slice(1..-1) || chars('')).downcase
@@ -362,16 +139,16 @@ module ActiveSupport #:nodoc:
# Capitalizes the first letter of every word, when possible.
#
- # Example:
# "ÉL QUE SE ENTERÓ".mb_chars.titleize # => "Él Que Se Enteró"
# "日本語".mb_chars.titleize # => "日本語"
def titleize
- chars(downcase.to_s.gsub(/\b('?[\S])/u) { Unicode.apply_mapping $1, :uppercase_mapping })
+ chars(downcase.to_s.gsub(/\b('?\S)/u) { Unicode.upcase($1)})
end
alias_method :titlecase, :titleize
- # Returns the KC normalization of the string by default. NFKC is considered the best normalization form for
- # passing strings to databases and validations.
+ # Returns the KC normalization of the string by default. NFKC is
+ # considered the best normalization form for passing strings to databases
+ # and validations.
#
# * <tt>form</tt> - The form you want to normalize in. Should be one of the following:
# <tt>:c</tt>, <tt>:kc</tt>, <tt>:d</tt>, or <tt>:kd</tt>. Default is
@@ -382,46 +159,45 @@ module ActiveSupport #:nodoc:
# Performs canonical decomposition on all the characters.
#
- # Example:
# 'é'.length # => 2
# 'é'.mb_chars.decompose.to_s.length # => 3
def decompose
- chars(Unicode.decompose_codepoints(:canonical, Unicode.u_unpack(@wrapped_string)).pack('U*'))
+ chars(Unicode.decompose(:canonical, @wrapped_string.codepoints.to_a).pack('U*'))
end
# Performs composition on all the characters.
#
- # Example:
# 'é'.length # => 3
# 'é'.mb_chars.compose.to_s.length # => 2
def compose
- chars(Unicode.compose_codepoints(Unicode.u_unpack(@wrapped_string)).pack('U*'))
+ chars(Unicode.compose(@wrapped_string.codepoints.to_a).pack('U*'))
end
# Returns the number of grapheme clusters in the string.
#
- # Example:
# 'क्षि'.mb_chars.length # => 4
- # 'क्षि'.mb_chars.g_length # => 3
- def g_length
- Unicode.g_unpack(@wrapped_string).length
+ # 'क्षि'.mb_chars.grapheme_length # => 3
+ def grapheme_length
+ Unicode.unpack_graphemes(@wrapped_string).length
end
- # Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent resulting in a valid UTF-8 string.
+ # 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.
+ # Passing +true+ will forcibly tidy all bytes, assuming that the string's
+ # encoding is entirely CP1252 or ISO-8859-1.
def tidy_bytes(force = false)
chars(Unicode.tidy_bytes(@wrapped_string, force))
end
- %w(capitalize downcase lstrip reverse rstrip slice strip tidy_bytes upcase).each do |method|
- # Only define a corresponding bang method for methods defined in the proxy; On 1.9 the proxy will
- # exclude lstrip!, rstrip! and strip! because they are already work as expected on multibyte strings.
- if public_method_defined?(method)
- define_method("#{method}!") do |*args|
- @wrapped_string = send(args.nil? ? method : method, *args).to_s
- self
- end
+ def as_json(options = nil) #:nodoc:
+ to_s.as_json(options)
+ end
+
+ %w(capitalize downcase reverse tidy_bytes upcase).each do |method|
+ define_method("#{method}!") do |*args|
+ @wrapped_string = send(method, *args).to_s
+ self
end
end
@@ -431,43 +207,14 @@ module ActiveSupport #:nodoc:
return nil if byte_offset.nil?
return 0 if @wrapped_string == ''
- if @wrapped_string.respond_to?(:force_encoding)
- @wrapped_string = @wrapped_string.dup.force_encoding(Encoding::ASCII_8BIT)
- end
-
begin
- @wrapped_string[0...byte_offset].unpack('U*').length
+ @wrapped_string.byteslice(0...byte_offset).unpack('U*').length
rescue ArgumentError
byte_offset -= 1
retry
end
end
- def justify(integer, way, padstr=' ') #:nodoc:
- raise ArgumentError, "zero width padding" if padstr.length == 0
- padsize = integer - size
- padsize = padsize > 0 ? padsize : 0
- case way
- when :right
- result = @wrapped_string.dup.insert(0, padding(padsize, padstr))
- when :left
- result = @wrapped_string.dup.insert(-1, padding(padsize, padstr))
- when :center
- lpad = padding((padsize / 2.0).floor, padstr)
- rpad = padding((padsize / 2.0).ceil, padstr)
- result = @wrapped_string.dup.insert(0, lpad).insert(-1, rpad)
- end
- chars(result)
- end
-
- def padding(padsize, padstr=' ') #:nodoc:
- if padsize != 0
- chars(padstr * ((padsize / Unicode.u_unpack(padstr).size) + 1)).slice(0, padsize)
- else
- ''
- end
- end
-
def chars(string) #:nodoc:
self.class.new(string)
end
diff --git a/lib/active_support/multibyte/exceptions.rb b/lib/active_support/multibyte/exceptions.rb
deleted file mode 100644
index 62066e3..0000000
--- a/lib/active_support/multibyte/exceptions.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-# encoding: utf-8
-
-module ActiveSupport #:nodoc:
- module Multibyte #:nodoc:
- # Raised when a problem with the encoding was found.
- class EncodingError < StandardError; end
- end
-end
\ No newline at end of file
diff --git a/lib/active_support/multibyte/unicode.rb b/lib/active_support/multibyte/unicode.rb
index 754ca92..bb8910a 100644
--- a/lib/active_support/multibyte/unicode.rb
+++ b/lib/active_support/multibyte/unicode.rb
@@ -5,17 +5,18 @@ module ActiveSupport
extend self
- # A list of all available normalization forms. See http://www.unicode.org/reports/tr15/tr15-29.html for more
+ # A list of all available normalization forms.
+ # See http://www.unicode.org/reports/tr15/tr15-29.html for more
# information about normalization.
NORMALIZATION_FORMS = [:c, :kc, :d, :kd]
# The Unicode version that is supported by the implementation
- UNICODE_VERSION = '5.2.0'
+ UNICODE_VERSION = '6.2.0'
- # The default normalization used for operations that require normalization. It can be set to any of the
- # normalizations in NORMALIZATION_FORMS.
+ # The default normalization used for operations that require
+ # normalization. It can be set to any of the normalizations
+ # in NORMALIZATION_FORMS.
#
- # Example:
# ActiveSupport::Multibyte::Unicode.default_normalization_form = :c
attr_accessor :default_normalization_form
@default_normalization_form = :kc
@@ -50,32 +51,22 @@ module ActiveSupport
0x3000, # White_Space # Zs IDEOGRAPHIC SPACE
].flatten.freeze
- # BOM (byte order mark) can also be seen as whitespace, it's a non-rendering character used to distinguish
- # between little and big endian. This is not an issue in utf-8, so it must be ignored.
+ # BOM (byte order mark) can also be seen as whitespace, it's a
+ # non-rendering character used to distinguish between little and big
+ # endian. This is not an issue in utf-8, so it must be ignored.
LEADERS_AND_TRAILERS = WHITESPACE + [65279] # ZERO-WIDTH NO-BREAK SPACE aka BOM
- # Returns a regular expression pattern that matches the passed Unicode codepoints
+ # Returns a regular expression pattern that matches the passed Unicode
+ # codepoints.
def self.codepoints_to_pattern(array_of_codepoints) #:nodoc:
array_of_codepoints.collect{ |e| [e].pack 'U*' }.join('|')
end
TRAILERS_PAT = /(#{codepoints_to_pattern(LEADERS_AND_TRAILERS)})+\Z/u
LEADERS_PAT = /\A(#{codepoints_to_pattern(LEADERS_AND_TRAILERS)})+/u
- # Unpack the string at codepoints boundaries. Raises an EncodingError when the encoding of the string isn't
- # valid UTF-8.
- #
- # Example:
- # Unicode.u_unpack('Café') # => [67, 97, 102, 233]
- def u_unpack(string)
- begin
- string.unpack 'U*'
- rescue ArgumentError
- raise EncodingError, 'malformed UTF-8 character'
- end
- end
-
- # Detect whether the codepoint is in a certain character class. Returns +true+ when it's in the specified
- # character class and +false+ otherwise. Valid character classes are: <tt>:cr</tt>, <tt>:lf</tt>, <tt>:l</tt>,
+ # Detect whether the codepoint is in a certain character class. Returns
+ # +true+ when it's in the specified character class and +false+ otherwise.
+ # Valid character classes are: <tt>:cr</tt>, <tt>:lf</tt>, <tt>:l</tt>,
# <tt>:v</tt>, <tt>:lv</tt>, <tt>:lvt</tt> and <tt>:t</tt>.
#
# Primarily used by the grapheme cluster support.
@@ -83,13 +74,13 @@ module ActiveSupport
classes.detect { |c| database.boundary[c] === codepoint } ? true : false
end
- # Unpack the string at grapheme boundaries. Returns a list of character lists.
+ # Unpack the string at grapheme boundaries. Returns a list of character
+ # lists.
#
- # Example:
- # Unicode.g_unpack('क्षि') # => [[2325, 2381], [2359], [2367]]
- # Unicode.g_unpack('Café') # => [[67], [97], [102], [233]]
- def g_unpack(string)
- codepoints = u_unpack(string)
+ # Unicode.unpack_graphemes('क्षि') # => [[2325, 2381], [2359], [2367]]
+ # Unicode.unpack_graphemes('Café') # => [[67], [97], [102], [233]]
+ def unpack_graphemes(string)
+ codepoints = string.codepoints.to_a
unpacked = []
pos = 0
marker = 0
@@ -118,12 +109,11 @@ module ActiveSupport
unpacked
end
- # Reverse operation of g_unpack.
+ # Reverse operation of unpack_graphemes.
#
- # Example:
- # Unicode.g_pack(Unicode.g_unpack('क्षि')) # => 'क्षि'
- def g_pack(unpacked)
- (unpacked.flatten).pack('U*')
+ # Unicode.pack_graphemes(Unicode.unpack_graphemes('क्षि')) # => 'क्षि'
+ def pack_graphemes(unpacked)
+ unpacked.flatten.pack('U*')
end
# Re-order codepoints so the string becomes canonical.
@@ -143,7 +133,7 @@ module ActiveSupport
end
# Decompose composed characters to the decomposed form.
- def decompose_codepoints(type, codepoints)
+ def decompose(type, codepoints)
codepoints.inject([]) do |decomposed, cp|
# if it's a hangul syllable starter character
if HANGUL_SBASE <= cp and cp < HANGUL_SLAST
@@ -155,8 +145,8 @@ module ActiveSupport
ncp << (HANGUL_TBASE + tindex) unless tindex == 0
decomposed.concat ncp
# if the codepoint is decomposable in with the current decomposition type
- elsif (ncp = database.codepoints[cp].decomp_mapping) and (!database.codepoints[cp].decomp_type || type == :compatability)
- decomposed.concat decompose_codepoints(type, ncp.dup)
+ elsif (ncp = database.codepoints[cp].decomp_mapping) and (!database.codepoints[cp].decomp_type || type == :compatibility)
+ decomposed.concat decompose(type, ncp.dup)
else
decomposed << cp
end
@@ -164,7 +154,7 @@ module ActiveSupport
end
# Compose decomposed characters to the composed form.
- def compose_codepoints(codepoints)
+ def compose(codepoints)
pos = 0
eoa = codepoints.length - 1
starter_pos = 0
@@ -222,9 +212,11 @@ module ActiveSupport
codepoints
end
- # Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent resulting in a valid UTF-8 string.
+ # 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.
+ # 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)
if force
return string.unpack("C*").map do |b|
@@ -273,48 +265,54 @@ module ActiveSupport
bytes.empty? ? "" : bytes.flatten.compact.pack("C*").unpack("U*").pack("U*")
end
- # Returns the KC normalization of the string by default. NFKC is considered the best normalization form for
- # passing strings to databases and validations.
+ # Returns the KC normalization of the string by default. NFKC is
+ # considered the best normalization form for passing strings to databases
+ # and validations.
#
# * <tt>string</tt> - The string to perform normalization on.
- # * <tt>form</tt> - The form you want to normalize in. Should be one of the following:
- # <tt>:c</tt>, <tt>:kc</tt>, <tt>:d</tt>, or <tt>:kd</tt>. Default is
- # ActiveSupport::Multibyte.default_normalization_form
+ # * <tt>form</tt> - The form you want to normalize in. Should be one of
+ # the following: <tt>:c</tt>, <tt>:kc</tt>, <tt>:d</tt>, or <tt>:kd</tt>.
+ # Default is ActiveSupport::Multibyte.default_normalization_form.
def normalize(string, form=nil)
form ||= @default_normalization_form
# See http://www.unicode.org/reports/tr15, Table 1
- codepoints = u_unpack(string)
+ codepoints = string.codepoints.to_a
case form
when :d
- reorder_characters(decompose_codepoints(:canonical, codepoints))
+ reorder_characters(decompose(:canonical, codepoints))
when :c
- compose_codepoints(reorder_characters(decompose_codepoints(:canonical, codepoints)))
+ compose(reorder_characters(decompose(:canonical, codepoints)))
when :kd
- reorder_characters(decompose_codepoints(:compatability, codepoints))
+ reorder_characters(decompose(:compatibility, codepoints))
when :kc
- compose_codepoints(reorder_characters(decompose_codepoints(:compatability, codepoints)))
+ compose(reorder_characters(decompose(:compatibility, codepoints)))
else
raise ArgumentError, "#{form} is not a valid normalization variant", caller
end.pack('U*')
end
- def apply_mapping(string, mapping) #:nodoc:
- u_unpack(string).map do |codepoint|
- cp = database.codepoints[codepoint]
- if cp and (ncp = cp.send(mapping)) and ncp > 0
- ncp
- else
- codepoint
- end
- end.pack('U*')
+ def downcase(string)
+ apply_mapping string, :lowercase_mapping
+ end
+
+ def upcase(string)
+ apply_mapping string, :uppercase_mapping
end
- # Holds data about a codepoint in the Unicode database
+ def swapcase(string)
+ apply_mapping string, :swapcase_mapping
+ end
+
+ # Holds data about a codepoint in the Unicode database.
class Codepoint
attr_accessor :code, :combining_class, :decomp_type, :decomp_mapping, :uppercase_mapping, :lowercase_mapping
+
+ def swapcase_mapping
+ uppercase_mapping > 0 ? uppercase_mapping : lowercase_mapping
+ end
end
- # Holds static data from the Unicode database
+ # Holds static data from the Unicode database.
class UnicodeDatabase
ATTRIBUTES = :codepoints, :composition_exclusion, :composition_map, :boundary, :cp1252
@@ -338,11 +336,12 @@ module ActiveSupport
EOS
end
- # Loads the Unicode database and returns all the internal objects of UnicodeDatabase.
+ # Loads the Unicode database and returns all the internal objects of
+ # UnicodeDatabase.
def load
begin
@codepoints, @composition_exclusion, @composition_map, @boundary, @cp1252 = File.open(self.class.filename, 'rb') { |f| Marshal.load f.read }
- rescue Exception => e
+ rescue => e
raise IOError.new("Couldn't load the Unicode tables for UTF8Handler (#{e.message}), ActiveSupport::Multibyte is unusable")
end
@@ -361,12 +360,12 @@ module ActiveSupport
end
end
- # Returns the directory in which the data files are stored
+ # Returns the directory in which the data files are stored.
def self.dirname
File.dirname(__FILE__) + '/../values/'
end
- # Returns the filename for the data file for this version
+ # Returns the filename for the data file for this version.
def self.filename
File.expand_path File.join(dirname, "unicode_tables.dat")
end
@@ -374,6 +373,17 @@ module ActiveSupport
private
+ def apply_mapping(string, mapping) #:nodoc:
+ string.each_codepoint.map do |codepoint|
+ cp = database.codepoints[codepoint]
+ if cp and (ncp = cp.send(mapping)) and ncp > 0
+ ncp
+ else
+ codepoint
+ end
+ end.pack('U*')
+ end
+
def tidy_byte(byte)
if byte < 160
[database.cp1252[byte] || byte].pack("U").unpack("C*")
diff --git a/lib/active_support/multibyte/utils.rb b/lib/active_support/multibyte/utils.rb
deleted file mode 100644
index 94b393c..0000000
--- a/lib/active_support/multibyte/utils.rb
+++ /dev/null
@@ -1,60 +0,0 @@
-# encoding: utf-8
-
-module ActiveSupport #:nodoc:
- module Multibyte #:nodoc:
- if Kernel.const_defined?(:Encoding)
- # Returns a regular expression that matches valid characters in the current encoding
- def self.valid_character
- VALID_CHARACTER[Encoding.default_external.to_s]
- end
- else
- def self.valid_character
- case $KCODE
- when 'UTF8'
- VALID_CHARACTER['UTF-8']
- when 'SJIS'
- VALID_CHARACTER['Shift_JIS']
- end
- end
- end
-
- if 'string'.respond_to?(:valid_encoding?)
- # Verifies the encoding of a string
- def self.verify(string)
- string.valid_encoding?
- end
- else
- def self.verify(string)
- if expression = valid_character
- # Splits the string on character boundaries, which are determined based on $KCODE.
- string.split(//).all? { |c| expression =~ c }
- else
- true
- end
- end
- end
-
- # Verifies the encoding of the string and raises an exception when it's not valid
- def self.verify!(string)
- raise EncodingError.new("Found characters with invalid encoding") unless verify(string)
- end
-
- if 'string'.respond_to?(:force_encoding)
- # Removes all invalid characters from the string.
- #
- # Note: this method is a no-op in Ruby 1.9
- def self.clean(string)
- string
- end
- else
- def self.clean(string)
- if expression = valid_character
- # Splits the string on character boundaries, which are determined based on $KCODE.
- string.split(//).grep(expression).join
- else
- string
- end
- end
- end
- end
-end
diff --git a/lib/active_support/notifications.rb b/lib/active_support/notifications.rb
index 13f675c..c45358b 100644
--- a/lib/active_support/notifications.rb
+++ b/lib/active_support/notifications.rb
@@ -1,19 +1,24 @@
+require 'active_support/notifications/instrumenter'
+require 'active_support/notifications/fanout'
+require 'active_support/per_thread_registry'
+
module ActiveSupport
# = Notifications
#
- # +ActiveSupport::Notifications+ provides an instrumentation API for Ruby.
+ # <tt>ActiveSupport::Notifications</tt> provides an instrumentation API for
+ # Ruby.
#
# == Instrumenters
#
# To instrument an event you just need to do:
#
- # ActiveSupport::Notifications.instrument("render", :extra => :information) do
- # render :text => "Foo"
+ # ActiveSupport::Notifications.instrument('render', extra: :information) do
+ # render text: 'Foo'
# end
#
# That executes the block first and notifies all subscribers once done.
#
- # In the example above "render" is the name of the event, and the rest is called
+ # In the example above +render+ is the name of the event, and the rest is called
# the _payload_. The payload is a mechanism that allows instrumenters to pass
# extra information to subscribers. Payloads consist of a hash whose contents
# are arbitrary and generally depend on the event.
@@ -21,46 +26,83 @@ module ActiveSupport
# == Subscribers
#
# You can consume those events and the information they provide by registering
- # a subscriber. For instance, let's store all "render" events in an array:
+ # a subscriber.
+ #
+ # ActiveSupport::Notifications.subscribe('render') do |name, start, finish, id, payload|
+ # name # => String, name of the event (such as 'render' from above)
+ # start # => Time, when the instrumented block started execution
+ # finish # => Time, when the instrumented block ended execution
+ # id # => String, unique ID for this notification
+ # payload # => Hash, the payload
+ # end
+ #
+ # For instance, let's store all "render" events in an array:
#
# events = []
#
- # ActiveSupport::Notifications.subscribe("render") do |*args|
+ # ActiveSupport::Notifications.subscribe('render') do |*args|
# events << ActiveSupport::Notifications::Event.new(*args)
# end
#
# That code returns right away, you are just subscribing to "render" events.
- # The block will be called asynchronously whenever someone instruments "render":
+ # The block is saved and will be called whenever someone instruments "render":
#
- # ActiveSupport::Notifications.instrument("render", :extra => :information) do
- # render :text => "Foo"
+ # ActiveSupport::Notifications.instrument('render', extra: :information) do
+ # render text: 'Foo'
# end
#
# event = events.first
# event.name # => "render"
# event.duration # => 10 (in milliseconds)
- # event.payload # => { :extra => :information }
+ # event.payload # => { extra: :information }
#
- # The block in the +subscribe+ call gets the name of the event, start
+ # The block in the <tt>subscribe</tt> call gets the name of the event, start
# timestamp, end timestamp, a string with a unique identifier for that event
# (something like "535801666f04d0298cd6"), and a hash with the payload, in
# that order.
#
# If an exception happens during that particular instrumentation the payload will
- # have a key +:exception+ with an array of two elements as value: a string with
+ # have a key <tt>:exception</tt> with an array of two elements as value: a string with
# the name of the exception class, and the exception message.
#
- # As the previous example depicts, the class +ActiveSupport::Notifications::Event+
+ # As the previous example depicts, the class <tt>ActiveSupport::Notifications::Event</tt>
# is able to take the arguments as they come and provide an object-oriented
# interface to that data.
#
+ # It is also possible to pass an object as the second parameter passed to the
+ # <tt>subscribe</tt> method instead of a block:
+ #
+ # module ActionController
+ # class PageRequest
+ # def call(name, started, finished, unique_id, payload)
+ # Rails.logger.debug ['notification:', name, started, finished, unique_id, payload].join(' ')
+ # end
+ # end
+ # end
+ #
+ # ActiveSupport::Notifications.subscribe('process_action.action_controller', ActionController::PageRequest.new)
+ #
+ # resulting in the following output within the logs including a hash with the payload:
+ #
+ # notification: process_action.action_controller 2012-04-13 01:08:35 +0300 2012-04-13 01:08:35 +0300 af358ed7fab884532ec7 {
+ # controller: "Devise::SessionsController",
+ # action: "new",
+ # params: {"action"=>"new", "controller"=>"devise/sessions"},
+ # format: :html,
+ # method: "GET",
+ # path: "/login/sign_in",
+ # status: 200,
+ # view_runtime: 279.3080806732178,
+ # db_runtime: 40.053
+ # }
+ #
# You can also subscribe to all events whose name matches a certain regexp:
#
# ActiveSupport::Notifications.subscribe(/render/) do |*args|
# ...
# end
#
- # and even pass no argument to +subscribe+, in which case you are subscribing
+ # and even pass no argument to <tt>subscribe</tt>, in which case you are subscribing
# to all events.
#
# == Temporary Subscriptions
@@ -105,12 +147,6 @@ module ActiveSupport
# to log subscribers in a thread. You can use any queue implementation you want.
#
module Notifications
- autoload :Instrumenter, 'active_support/notifications/instrumenter'
- autoload :Event, 'active_support/notifications/instrumenter'
- autoload :Fanout, 'active_support/notifications/fanout'
-
- @instrumenters = Hash.new { |h,k| h[k] = notifier.listening?(k) }
-
class << self
attr_accessor :notifier
@@ -119,7 +155,7 @@ module ActiveSupport
end
def instrument(name, payload = {})
- if @instrumenters[name]
+ if notifier.listening?(name)
instrumenter.instrument(name, payload) { yield payload if block_given? }
else
yield payload if block_given?
@@ -127,9 +163,7 @@ module ActiveSupport
end
def subscribe(*args, &block)
- notifier.subscribe(*args, &block).tap do
- @instrumenters.clear
- end
+ notifier.subscribe(*args, &block)
end
def subscribed(callback, *args, &block)
@@ -141,11 +175,30 @@ module ActiveSupport
def unsubscribe(args)
notifier.unsubscribe(args)
- @instrumenters.clear
end
def instrumenter
- Thread.current[:"instrumentation_#{notifier.object_id}"] ||= Instrumenter.new(notifier)
+ InstrumentationRegistry.instrumenter_for(notifier)
+ end
+ end
+
+ # This class is a registry which holds all of the +Instrumenter+ objects
+ # in a particular thread local. To access the +Instrumenter+ object for a
+ # particular +notifier+, you can call the following method:
+ #
+ # InstrumentationRegistry.instrumenter_for(notifier)
+ #
+ # The instrumenters for multiple notifiers are held in a single instance of
+ # this class.
+ class InstrumentationRegistry # :nodoc:
+ extend ActiveSupport::PerThreadRegistry
+
+ def initialize
+ @registry = {}
+ end
+
+ def instrumenter_for(notifier)
+ @registry[notifier] ||= Instrumenter.new(notifier)
end
end
diff --git a/lib/active_support/notifications/fanout.rb b/lib/active_support/notifications/fanout.rb
index a9aa546..99fe03e 100644
--- a/lib/active_support/notifications/fanout.rb
+++ b/lib/active_support/notifications/fanout.rb
@@ -1,24 +1,43 @@
+require 'mutex_m'
+require 'thread_safe'
+
module ActiveSupport
module Notifications
# This is a default queue implementation that ships with Notifications.
# It just pushes events to all registered log subscribers.
+ #
+ # This class is thread safe. All methods are reentrant.
class Fanout
+ include Mutex_m
+
def initialize
@subscribers = []
- @listeners_for = {}
+ @listeners_for = ThreadSafe::Cache.new
+ super
end
def subscribe(pattern = nil, block = Proc.new)
- subscriber = Subscriber.new(pattern, block).tap do |s|
- @subscribers << s
+ subscriber = Subscribers.new pattern, block
+ synchronize do
+ @subscribers << subscriber
+ @listeners_for.clear
end
- @listeners_for.clear
subscriber
end
def unsubscribe(subscriber)
- @subscribers.reject! {|s| s.matches?(subscriber)}
- @listeners_for.clear
+ synchronize do
+ @subscribers.reject! { |s| s.matches?(subscriber) }
+ @listeners_for.clear
+ end
+ end
+
+ def start(name, id, payload)
+ listeners_for(name).each { |s| s.start(name, id, payload) }
+ end
+
+ def finish(name, id, payload)
+ listeners_for(name).each { |s| s.finish(name, id, payload) }
end
def publish(name, *args)
@@ -26,7 +45,11 @@ module ActiveSupport
end
def listeners_for(name)
- @listeners_for[name] ||= @subscribers.select { |s| s.subscribed_to?(name) }
+ # this is correctly done double-checked locking (ThreadSafe::Cache's lookups have volatile semantics)
+ @listeners_for[name] || synchronize do
+ # use synchronisation when accessing @subscribers
+ @listeners_for[name] ||= @subscribers.select { |s| s.subscribed_to?(name) }
+ end
end
def listening?(name)
@@ -37,23 +60,94 @@ module ActiveSupport
def wait
end
- class Subscriber #:nodoc:
- def initialize(pattern, delegate)
- @pattern = pattern
- @delegate = delegate
+ module Subscribers # :nodoc:
+ def self.new(pattern, listener)
+ if listener.respond_to?(:start) and listener.respond_to?(:finish)
+ subscriber = Evented.new pattern, listener
+ else
+ subscriber = Timed.new pattern, listener
+ end
+
+ unless pattern
+ AllMessages.new(subscriber)
+ else
+ subscriber
+ end
end
- def publish(message, *args)
- @delegate.call(message, *args)
+ class Evented #:nodoc:
+ def initialize(pattern, delegate)
+ @pattern = pattern
+ @delegate = delegate
+ @can_publish = delegate.respond_to?(:publish)
+ end
+
+ def publish(name, *args)
+ if @can_publish
+ @delegate.publish name, *args
+ end
+ end
+
+ def start(name, id, payload)
+ @delegate.start name, id, payload
+ end
+
+ def finish(name, id, payload)
+ @delegate.finish name, id, payload
+ end
+
+ def subscribed_to?(name)
+ @pattern === name.to_s
+ end
+
+ def matches?(subscriber_or_name)
+ self === subscriber_or_name ||
+ @pattern && @pattern === subscriber_or_name
+ end
end
- def subscribed_to?(name)
- !@pattern || @pattern === name.to_s
+ 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
+ end
+
+ def finish(name, id, payload)
+ started = @timestack.pop
+ @delegate.call(name, started, Time.now, id, payload)
+ end
end
- def matches?(subscriber_or_name)
- self === subscriber_or_name ||
- @pattern && @pattern === subscriber_or_name
+ class AllMessages # :nodoc:
+ def initialize(delegate)
+ @delegate = delegate
+ end
+
+ def start(name, id, payload)
+ @delegate.start name, id, payload
+ end
+
+ def finish(name, id, payload)
+ @delegate.finish name, id, payload
+ end
+
+ def publish(name, *args)
+ @delegate.publish name, *args
+ end
+
+ def subscribed_to?(name)
+ true
+ end
+
+ alias :matches? :===
end
end
end
diff --git a/lib/active_support/notifications/instrumenter.rb b/lib/active_support/notifications/instrumenter.rb
index 3941c28..0c9a729 100644
--- a/lib/active_support/notifications/instrumenter.rb
+++ b/lib/active_support/notifications/instrumenter.rb
@@ -1,39 +1,51 @@
-require 'active_support/core_ext/module/delegation'
+require 'securerandom'
module ActiveSupport
module Notifications
+ # Instrumenters are stored in a thread local.
class Instrumenter
attr_reader :id
def initialize(notifier)
- @id = unique_id
+ @id = unique_id
@notifier = notifier
end
# Instrument the given block by measuring the time taken to execute it
# and publish it. Notice that events get sent even if an error occurs
- # in the passed-in block
+ # in the passed-in block.
def instrument(name, payload={})
- started = Time.now
-
+ start name, payload
begin
- yield
+ yield payload
rescue Exception => e
payload[:exception] = [e.class.name, e.message]
raise e
ensure
- @notifier.publish(name, started, Time.now, @id, payload)
+ finish name, payload
end
end
+ # Send a start notification with +name+ and +payload+.
+ def start(name, payload)
+ @notifier.start name, @id, payload
+ end
+
+ # Send a finish notification with +name+ and +payload+.
+ def finish(name, payload)
+ @notifier.finish name, @id, payload
+ end
+
private
- def unique_id
- SecureRandom.hex(10)
- end
+
+ def unique_id
+ SecureRandom.hex(10)
+ end
end
class Event
- attr_reader :name, :time, :end, :transaction_id, :payload, :duration
+ attr_reader :name, :time, :transaction_id, :payload, :children
+ attr_accessor :end
def initialize(name, start, ending, transaction_id, payload)
@name = name
@@ -41,12 +53,19 @@ module ActiveSupport
@time = start
@transaction_id = transaction_id
@end = ending
- @duration = 1000.0 * (@end - @time)
+ @children = []
+ end
+
+ def duration
+ 1000.0 * (self.end - time)
+ end
+
+ def <<(event)
+ @children << event
end
def parent_of?(event)
- start = (time - event.time) * 1000
- start <= 0 && (start + duration >= event.duration)
+ @children.include? event
end
end
end
diff --git a/lib/active_support/number_helper.rb b/lib/active_support/number_helper.rb
new file mode 100644
index 0000000..c9c0eff
--- /dev/null
+++ b/lib/active_support/number_helper.rb
@@ -0,0 +1,637 @@
+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 }
+
+ STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb]
+
+ # Formats a +number+ into a US phone number (e.g., (555)
+ # 123-9876). You can customize the format in the +options+ hash.
+ #
+ # ==== Options
+ #
+ # * <tt>:area_code</tt> - Adds parentheses around the area code.
+ # * <tt>:delimiter</tt> - Specifies the delimiter to use
+ # (defaults to "-").
+ # * <tt>:extension</tt> - Specifies an extension to add to the
+ # end of the generated number.
+ # * <tt>:country_code</tt> - Sets the country code for the phone
+ # number.
+ # ==== Examples
+ #
+ # number_to_phone(5551234) # => 555-1234
+ # number_to_phone('5551234') # => 555-1234
+ # number_to_phone(1235551234) # => 123-555-1234
+ # number_to_phone(1235551234, area_code: true) # => (123) 555-1234
+ # number_to_phone(1235551234, delimiter: ' ') # => 123 555 1234
+ # number_to_phone(1235551234, area_code: true, extension: 555) # => (123) 555-1234 x 555
+ # number_to_phone(1235551234, country_code: 1) # => +1-123-555-1234
+ # number_to_phone('123a456') # => 123a456
+ #
+ # 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
+ end
+
+ # Formats a +number+ into a currency string (e.g., $13.65). You
+ # can customize the format in the +options+ hash.
+ #
+ # ==== Options
+ #
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
+ # (defaults to current locale).
+ # * <tt>:precision</tt> - Sets the level of precision (defaults
+ # to 2).
+ # * <tt>:unit</tt> - Sets the denomination of the currency
+ # (defaults to "$").
+ # * <tt>:separator</tt> - Sets the separator between the units
+ # (defaults to ".").
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
+ # to ",").
+ # * <tt>:format</tt> - Sets the format for non-negative numbers
+ # (defaults to "%u%n"). Fields are <tt>%u</tt> for the
+ # currency, and <tt>%n</tt> for the number.
+ # * <tt>:negative_format</tt> - Sets the format for negative
+ # numbers (defaults to prepending an hyphen to the formatted
+ # number given by <tt>:format</tt>). Accepts the same fields
+ # than <tt>:format</tt>, except <tt>%n</tt> is here the
+ # absolute value of the number.
+ #
+ # ==== Examples
+ #
+ # number_to_currency(1234567890.50) # => $1,234,567,890.50
+ # number_to_currency(1234567890.506) # => $1,234,567,890.51
+ # number_to_currency(1234567890.506, precision: 3) # => $1,234,567,890.506
+ # number_to_currency(1234567890.506, locale: :fr) # => 1 234 567 890,51 €
+ # number_to_currency('123a456') # => $123a456
+ #
+ # number_to_currency(-1234567890.50, negative_format: '(%u%n)')
+ # # => ($1,234,567,890.50)
+ # number_to_currency(1234567890.50, unit: '£', separator: ',', delimiter: '')
+ # # => £1234567890,50
+ # number_to_currency(1234567890.50, unit: '£', separator: ',', delimiter: '', format: '%n %u')
+ # # => 1234567890,50 £
+ 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)
+ end
+
+ # Formats a +number+ as a percentage string (e.g., 65%). You can
+ # customize the format in the +options+ hash.
+ #
+ # ==== Options
+ #
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
+ # (defaults to current locale).
+ # * <tt>:precision</tt> - Sets the precision of the number
+ # (defaults to 3).
+ # * <tt>:significant</tt> - If +true+, precision will be the #
+ # of significant_digits. If +false+, the # of fractional
+ # digits (defaults to +false+).
+ # * <tt>:separator</tt> - Sets the separator between the
+ # fractional and integer digits (defaults to ".").
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
+ # to "").
+ # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
+ # insignificant zeros after the decimal separator (defaults to
+ # +false+).
+ # * <tt>:format</tt> - Specifies the format of the percentage
+ # string The number field is <tt>%n</tt> (defaults to "%n%").
+ #
+ # ==== Examples
+ #
+ # number_to_percentage(100) # => 100.000%
+ # number_to_percentage('98') # => 98.000%
+ # number_to_percentage(100, precision: 0) # => 100%
+ # number_to_percentage(1000, delimiter: '.', separator: ',') # => 1.000,000%
+ # number_to_percentage(302.24398923423, precision: 5) # => 302.24399%
+ # number_to_percentage(1000, locale: :fr) # => 1 000,000%
+ # number_to_percentage('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))
+ end
+
+ # Formats a +number+ with grouped thousands using +delimiter+
+ # (e.g., 12,324). You can customize the format in the +options+
+ # hash.
+ #
+ # ==== Options
+ #
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
+ # (defaults to current locale).
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
+ # to ",").
+ # * <tt>:separator</tt> - Sets the separator between the
+ # fractional and integer digits (defaults to ".").
+ #
+ # ==== Examples
+ #
+ # number_to_delimited(12345678) # => 12,345,678
+ # number_to_delimited('123456') # => 123,456
+ # number_to_delimited(12345678.05) # => 12,345,678.05
+ # number_to_delimited(12345678, delimiter: '.') # => 12.345.678
+ # number_to_delimited(12345678, delimiter: ',') # => 12,345,678
+ # number_to_delimited(12345678.05, separator: ' ') # => 12,345,678 05
+ # number_to_delimited(12345678.05, locale: :fr) # => 12 345 678,05
+ # number_to_delimited('112a') # => 112a
+ # 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])
+ end
+
+ # Formats a +number+ with the specified level of
+ # <tt>:precision</tt> (e.g., 112.32 has a precision of 2 if
+ # +:significant+ is +false+, and 5 if +:significant+ is +true+).
+ # You can customize the format in the +options+ hash.
+ #
+ # ==== Options
+ #
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
+ # (defaults to current locale).
+ # * <tt>:precision</tt> - Sets the precision of the number
+ # (defaults to 3).
+ # * <tt>:significant</tt> - If +true+, precision will be the #
+ # of significant_digits. If +false+, the # of fractional
+ # digits (defaults to +false+).
+ # * <tt>:separator</tt> - Sets the separator between the
+ # fractional and integer digits (defaults to ".").
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
+ # to "").
+ # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
+ # insignificant zeros after the decimal separator (defaults to
+ # +false+).
+ #
+ # ==== Examples
+ #
+ # number_to_rounded(111.2345) # => 111.235
+ # number_to_rounded(111.2345, precision: 2) # => 111.23
+ # number_to_rounded(13, precision: 5) # => 13.00000
+ # number_to_rounded(389.32314, precision: 0) # => 389
+ # number_to_rounded(111.2345, significant: true) # => 111
+ # number_to_rounded(111.2345, precision: 1, significant: true) # => 100
+ # number_to_rounded(13, precision: 5, significant: true) # => 13.000
+ # number_to_rounded(111.234, locale: :fr) # => 111,234
+ #
+ # number_to_rounded(13, precision: 5, significant: true, strip_insignificant_zeros: true)
+ # # => 13
+ #
+ # number_to_rounded(389.32314, precision: 4, significant: true) # => 389.3
+ # 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
+ end
+
+ # Formats the bytes in +number+ into a more understandable
+ # representation (e.g., giving it 1500 yields 1.5 KB). This
+ # method is useful for reporting file sizes to users. You can
+ # customize the format in the +options+ hash.
+ #
+ # See <tt>number_to_human</tt> if you want to pretty-print a
+ # generic number.
+ #
+ # ==== Options
+ #
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
+ # (defaults to current locale).
+ # * <tt>:precision</tt> - Sets the precision of the number
+ # (defaults to 3).
+ # * <tt>:significant</tt> - If +true+, precision will be the #
+ # of significant_digits. If +false+, the # of fractional
+ # digits (defaults to +true+)
+ # * <tt>:separator</tt> - Sets the separator between the
+ # fractional and integer digits (defaults to ".").
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
+ # to "").
+ # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
+ # insignificant zeros after the decimal separator (defaults to
+ # +true+)
+ # * <tt>:prefix</tt> - If +:si+ formats the number using the SI
+ # prefix (defaults to :binary)
+ #
+ # ==== Examples
+ #
+ # number_to_human_size(123) # => 123 Bytes
+ # number_to_human_size(1234) # => 1.21 KB
+ # number_to_human_size(12345) # => 12.1 KB
+ # number_to_human_size(1234567) # => 1.18 MB
+ # number_to_human_size(1234567890) # => 1.15 GB
+ # number_to_human_size(1234567890123) # => 1.12 TB
+ # number_to_human_size(1234567, precision: 2) # => 1.2 MB
+ # number_to_human_size(483989, precision: 2) # => 470 KB
+ # number_to_human_size(1234567, precision: 2, separator: ',') # => 1,2 MB
+ #
+ # Non-significant zeros after the fractional separator are stripped out by
+ # default (set <tt>:strip_insignificant_zeros</tt> to +false+ to change that):
+ #
+ # number_to_human_size(1234567890123, precision: 5) # => "1.1229 TB"
+ # number_to_human_size(524288000, precision: 5) # => "500 MB"
+ 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
+ end
+
+ # Pretty prints (formats and approximates) a number in a way it
+ # is more readable by humans (eg.: 1200000000 becomes "1.2
+ # Billion"). This is useful for numbers that can get very large
+ # (and too hard to read).
+ #
+ # See <tt>number_to_human_size</tt> if you want to print a file
+ # size.
+ #
+ # 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
+ # (centi, deci, mili, etc).
+ #
+ # ==== Options
+ #
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
+ # (defaults to current locale).
+ # * <tt>:precision</tt> - Sets the precision of the number
+ # (defaults to 3).
+ # * <tt>:significant</tt> - If +true+, precision will be the #
+ # of significant_digits. If +false+, the # of fractional
+ # digits (defaults to +true+)
+ # * <tt>:separator</tt> - Sets the separator between the
+ # fractional and integer digits (defaults to ".").
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
+ # to "").
+ # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
+ # insignificant zeros after the decimal separator (defaults to
+ # +true+)
+ # * <tt>:units</tt> - A Hash of unit quantifier names. Or a
+ # string containing an i18n scope where to find this hash. It
+ # might have the following keys:
+ # * *integers*: <tt>:unit</tt>, <tt>:ten</tt>,
+ # *<tt>:hundred</tt>, <tt>:thousand</tt>, <tt>:million</tt>,
+ # *<tt>:billion</tt>, <tt>:trillion</tt>,
+ # *<tt>:quadrillion</tt>
+ # * *fractionals*: <tt>:deci</tt>, <tt>:centi</tt>,
+ # *<tt>:mili</tt>, <tt>:micro</tt>, <tt>:nano</tt>,
+ # *<tt>:pico</tt>, <tt>:femto</tt>
+ # * <tt>:format</tt> - Sets the format of the output string
+ # (defaults to "%n %u"). The field types are:
+ # * %u - The quantifier (ex.: 'thousand')
+ # * %n - The number
+ #
+ # ==== Examples
+ #
+ # number_to_human(123) # => "123"
+ # number_to_human(1234) # => "1.23 Thousand"
+ # number_to_human(12345) # => "12.3 Thousand"
+ # number_to_human(1234567) # => "1.23 Million"
+ # number_to_human(1234567890) # => "1.23 Billion"
+ # number_to_human(1234567890123) # => "1.23 Trillion"
+ # number_to_human(1234567890123456) # => "1.23 Quadrillion"
+ # number_to_human(1234567890123456789) # => "1230 Quadrillion"
+ # number_to_human(489939, precision: 2) # => "490 Thousand"
+ # number_to_human(489939, precision: 4) # => "489.9 Thousand"
+ # number_to_human(1234567, precision: 4,
+ # significant: false) # => "1.2346 Million"
+ # number_to_human(1234567, precision: 1,
+ # separator: ',',
+ # significant: false) # => "1,2 Million"
+ #
+ # Non-significant zeros after the decimal separator are stripped
+ # out by default (set <tt>:strip_insignificant_zeros</tt> to
+ # +false+ to change that):
+ #
+ # number_to_human(12345012345, significant_digits: 6) # => "12.345 Billion"
+ # number_to_human(500000000, precision: 5) # => "500 Million"
+ #
+ # ==== Custom Unit Quantifiers
+ #
+ # You can also use your own custom unit quantifiers:
+ # number_to_human(500000, units: { unit: 'ml', thousand: 'lt' }) # => "500 lt"
+ #
+ # If in your I18n locale you have:
+ #
+ # distance:
+ # centi:
+ # one: "centimeter"
+ # other: "centimeters"
+ # unit:
+ # one: "meter"
+ # other: "meters"
+ # thousand:
+ # one: "kilometer"
+ # other: "kilometers"
+ # billion: "gazillion-distance"
+ #
+ # Then you could do:
+ #
+ # number_to_human(543934, units: :distance) # => "544 kilometers"
+ # number_to_human(54393498, units: :distance) # => "54400 kilometers"
+ # number_to_human(54393498000, units: :distance) # => "54.4 gazillion-distance"
+ # number_to_human(343, units: :distance, precision: 1) # => "300 meters"
+ # 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
+ end
+ private_module_and_instance_method :valid_float?
+ end
+end
diff --git a/lib/active_support/ordered_hash.rb b/lib/active_support/ordered_hash.rb
index b0d4f2b..1a3693f 100644
--- a/lib/active_support/ordered_hash.rb
+++ b/lib/active_support/ordered_hash.rb
@@ -1,8 +1,3 @@
-begin
- require 'psych'
-rescue LoadError
-end
-
require 'yaml'
YAML.add_builtin_type("omap") do |type, val|
@@ -10,16 +5,20 @@ YAML.add_builtin_type("omap") do |type, val|
end
module ActiveSupport
- # The order of iteration over hashes in Ruby 1.8 is undefined. For example, you do not know the
- # order in which +keys+ will return keys, or +each+ yield pairs. <tt>ActiveSupport::OrderedHash</tt>
- # implements a hash that preserves insertion order, as in Ruby 1.9:
+ # <tt>ActiveSupport::OrderedHash</tt> implements a hash that preserves
+ # insertion order.
#
# oh = ActiveSupport::OrderedHash.new
# oh[:a] = 1
# oh[:b] = 2
# oh.keys # => [:a, :b], this order is guaranteed
#
- # <tt>ActiveSupport::OrderedHash</tt> is namespaced to prevent conflicts with other implementations.
+ # Also, maps the +omap+ feature for YAML files
+ # (See http://yaml.org/type/omap.html) to support ordered items
+ # when loading from yaml.
+ #
+ # <tt>ActiveSupport::OrderedHash</tt> is namespaced to prevent conflicts
+ # with other implementations.
class OrderedHash < ::Hash
def to_yaml_type
"!tag:yaml.org,2002:omap"
@@ -29,20 +28,6 @@ module ActiveSupport
coder.represent_seq '!omap', map { |k,v| { k => v } }
end
- def to_yaml(opts = {})
- if YAML.const_defined?(:ENGINE) && !YAML::ENGINE.syck?
- return super
- end
-
- YAML.quick_emit(self, opts) do |out|
- out.seq(taguri) do |seq|
- each do |k, v|
- seq.add(k => v)
- end
- end
- end
- end
-
def nested_under_indifferent_access
self
end
@@ -51,172 +36,5 @@ module ActiveSupport
def extractable_options?
true
end
-
- # Hash is ordered in Ruby 1.9!
- if RUBY_VERSION < '1.9'
-
- # In MRI the Hash class is core and written in C. In particular, methods are
- # programmed with explicit C function calls and polymorphism is not honored.
- #
- # For example, []= is crucial in this implementation to maintain the @keys
- # array but hash.c invokes rb_hash_aset() originally. This prevents method
- # reuse through inheritance and forces us to reimplement stuff.
- #
- # For instance, we cannot use the inherited #merge! because albeit the algorithm
- # itself would work, our []= is not being called at all by the C code.
-
- def initialize(*args, &block)
- super
- @keys = []
- end
-
- def self.[](*args)
- ordered_hash = new
-
- if (args.length == 1 && args.first.is_a?(Array))
- args.first.each do |key_value_pair|
- next unless (key_value_pair.is_a?(Array))
- ordered_hash[key_value_pair[0]] = key_value_pair[1]
- end
-
- return ordered_hash
- end
-
- unless (args.size % 2 == 0)
- raise ArgumentError.new("odd number of arguments for Hash")
- end
-
- args.each_with_index do |val, ind|
- next if (ind % 2 != 0)
- ordered_hash[val] = args[ind + 1]
- end
-
- ordered_hash
- end
-
- def initialize_copy(other)
- super
- # make a deep copy of keys
- @keys = other.keys
- end
-
- def []=(key, value)
- @keys << key unless has_key?(key)
- super
- end
-
- def delete(key)
- if has_key? key
- index = @keys.index(key)
- @keys.delete_at index
- end
- super
- end
-
- def delete_if
- super
- sync_keys!
- self
- end
-
- def reject!
- super
- sync_keys!
- self
- end
-
- def reject(&block)
- dup.reject!(&block)
- end
-
- def keys
- @keys.dup
- end
-
- def values
- @keys.collect { |key| self[key] }
- end
-
- def to_hash
- self
- end
-
- def to_a
- @keys.map { |key| [ key, self[key] ] }
- end
-
- def each_key
- return to_enum(:each_key) unless block_given?
- @keys.each { |key| yield key }
- self
- end
-
- def each_value
- return to_enum(:each_value) unless block_given?
- @keys.each { |key| yield self[key]}
- self
- end
-
- def each
- return to_enum(:each) unless block_given?
- @keys.each {|key| yield [key, self[key]]}
- self
- end
-
- def each_pair
- return to_enum(:each_pair) unless block_given?
- @keys.each {|key| yield key, self[key]}
- self
- end
-
- alias_method :select, :find_all
-
- def clear
- super
- @keys.clear
- self
- end
-
- def shift
- k = @keys.first
- v = delete(k)
- [k, v]
- end
-
- def merge!(other_hash)
- if block_given?
- other_hash.each { |k, v| self[k] = key?(k) ? yield(k, self[k], v) : v }
- else
- other_hash.each { |k, v| self[k] = v }
- end
- self
- end
-
- alias_method :update, :merge!
-
- def merge(other_hash, &block)
- dup.merge!(other_hash, &block)
- end
-
- # When replacing with another hash, the initial order of our keys must come from the other hash -ordered or not.
- def replace(other)
- super
- @keys = other.keys
- self
- end
-
- def invert
- OrderedHash[self.to_a.map!{|key_value_pair| key_value_pair.reverse}]
- end
-
- def inspect
- "#<OrderedHash #{super}>"
- end
-
- private
- def sync_keys!
- @keys.delete_if {|k| !has_key?(k)}
- end
- end
end
end
diff --git a/lib/active_support/ordered_options.rb b/lib/active_support/ordered_options.rb
index bf81567..c9518bd 100644
--- a/lib/active_support/ordered_options.rb
+++ b/lib/active_support/ordered_options.rb
@@ -1,23 +1,20 @@
-require 'active_support/ordered_hash'
-
-# Usually key value pairs are handled something like this:
-#
-# h = {}
-# h[:boy] = 'John'
-# h[:girl] = 'Mary'
-# h[:boy] # => 'John'
-# h[:girl] # => 'Mary'
-#
-# Using <tt>OrderedOptions</tt>, the above code could be reduced to:
-#
-# h = ActiveSupport::OrderedOptions.new
-# h.boy = 'John'
-# h.girl = 'Mary'
-# h.boy # => 'John'
-# h.girl # => 'Mary'
-#
-module ActiveSupport #:nodoc:
- class OrderedOptions < OrderedHash
+module ActiveSupport
+ # Usually key value pairs are handled something like this:
+ #
+ # h = {}
+ # h[:boy] = 'John'
+ # h[:girl] = 'Mary'
+ # h[:boy] # => 'John'
+ # h[:girl] # => 'Mary'
+ #
+ # Using +OrderedOptions+, the above code could be reduced to:
+ #
+ # h = ActiveSupport::OrderedOptions.new
+ # h.boy = 'John'
+ # h.girl = 'Mary'
+ # h.boy # => 'John'
+ # h.girl # => 'Mary'
+ class OrderedOptions < Hash
alias_method :_get, :[] # preserve the original #[] method
protected :_get # make it protected
@@ -30,14 +27,15 @@ module ActiveSupport #:nodoc:
end
def method_missing(name, *args)
- if name.to_s =~ /(.*)=$/
- self[$1] = args.first
+ name_string = name.to_s
+ if name_string.chomp!('=')
+ self[name_string] = args.first
else
self[name]
end
end
- def respond_to?(name)
+ def respond_to_missing?(name, include_private)
true
end
end
diff --git a/lib/active_support/per_thread_registry.rb b/lib/active_support/per_thread_registry.rb
new file mode 100644
index 0000000..aa682fb
--- /dev/null
+++ b/lib/active_support/per_thread_registry.rb
@@ -0,0 +1,52 @@
+module ActiveSupport
+ # This module is used to encapsulate access to thread local variables.
+ #
+ # Instead of polluting the thread locals namespace:
+ #
+ # Thread.current[:connection_handler]
+ #
+ # you define a class that extends this module:
+ #
+ # module ActiveRecord
+ # class RuntimeRegistry
+ # extend ActiveSupport::PerThreadRegistry
+ #
+ # attr_accessor :connection_handler
+ # end
+ # end
+ #
+ # and invoke the declared instance accessors as class methods. So
+ #
+ # ActiveRecord::RuntimeRegistry.connection_handler = connection_handler
+ #
+ # sets a connection handler local to the current thread, and
+ #
+ # ActiveRecord::RuntimeRegistry.connection_handler
+ #
+ # returns a connection handler local to the current thread.
+ #
+ # This feature is accomplished by instantiating the class and storing the
+ # instance as a thread local keyed by the class name. In the example above
+ # a key "ActiveRecord::RuntimeRegistry" is stored in <tt>Thread.current</tt>.
+ # The class methods proxy to said thread local instance.
+ #
+ # If the class has an initializer, it must accept no arguments.
+ module PerThreadRegistry
+ 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)
+ end
+
+ send(name, *args, &block)
+ end
+
+ private
+
+ def per_thread_registry_instance
+ Thread.current[name] ||= new
+ end
+ end
+end
diff --git a/lib/active_support/proxy_object.rb b/lib/active_support/proxy_object.rb
new file mode 100644
index 0000000..20a0fd8
--- /dev/null
+++ b/lib/active_support/proxy_object.rb
@@ -0,0 +1,13 @@
+module ActiveSupport
+ # A class with no predefined methods that behaves similarly to Builder's
+ # BlankSlate. Used for proxy classes.
+ class ProxyObject < ::BasicObject
+ undef_method :==
+ undef_method :equal?
+
+ # Let ActiveSupport::ProxyObject at least raise exceptions.
+ def raise(*args)
+ ::Object.send(:raise, *args)
+ end
+ end
+end
diff --git a/lib/active_support/rails.rb b/lib/active_support/rails.rb
new file mode 100644
index 0000000..b05c3ff
--- /dev/null
+++ b/lib/active_support/rails.rb
@@ -0,0 +1,27 @@
+# This is private interface.
+#
+# Rails components cherry pick from Active Support as needed, but there are a
+# few features that are used for sure some way or another and it is not worth
+# to put individual requires absolutely everywhere. Think blank? for example.
+#
+# This file is loaded by every Rails component except Active Support itself,
+# but it does not belong to the Rails public interface. It is internal to
+# Rails and can change anytime.
+
+# Defines Object#blank? and Object#present?.
+require 'active_support/core_ext/object/blank'
+
+# Rails own autoload, eager_load, etc.
+require 'active_support/dependencies/autoload'
+
+# Support for ClassMethods and the included macro.
+require 'active_support/concern'
+
+# Defines Class#class_attribute.
+require 'active_support/core_ext/class/attribute'
+
+# Defines Module#delegate.
+require 'active_support/core_ext/module/delegation'
+
+# Defines ActiveSupport::Deprecation.
+require 'active_support/deprecation'
diff --git a/lib/active_support/railtie.rb b/lib/active_support/railtie.rb
index 1638512..133aa6a 100644
--- a/lib/active_support/railtie.rb
+++ b/lib/active_support/railtie.rb
@@ -2,42 +2,14 @@ require "active_support"
require "active_support/i18n_railtie"
module ActiveSupport
- class Railtie < Rails::Railtie
+ class Railtie < Rails::Railtie # :nodoc:
config.active_support = ActiveSupport::OrderedOptions.new
- # Loads support for "whiny nil" (noisy warnings when methods are invoked
- # on +nil+ values) if Configuration#whiny_nils is true.
- initializer "active_support.initialize_whiny_nils" do |app|
- require 'active_support/whiny_nil' if app.config.whiny_nils
- end
+ config.eager_load_namespaces << ActiveSupport
initializer "active_support.deprecation_behavior" do |app|
if deprecation = app.config.active_support.deprecation
ActiveSupport::Deprecation.behavior = deprecation
- else
- defaults = {"development" => :log,
- "production" => :notify,
- "test" => :stderr}
-
- env = Rails.env
-
- if defaults.key?(env)
- msg = "You did not specify how you would like Rails to report " \
- "deprecation notices for your #{env} environment, please " \
- "set config.active_support.deprecation to :#{defaults[env]} " \
- "at config/environments/#{env}.rb"
-
- warn msg
- ActiveSupport::Deprecation.behavior = defaults[env]
- else
- msg = "You did not specify how you would like Rails to report " \
- "deprecation notices for your #{env} environment, please " \
- "set config.active_support.deprecation to :log, :notify or " \
- ":stderr at config/environments/#{env}.rb"
-
- warn msg
- ActiveSupport::Deprecation.behavior = :stderr
- end
end
end
@@ -48,12 +20,27 @@ module ActiveSupport
zone_default = Time.find_zone!(app.config.time_zone)
unless zone_default
- raise \
- 'Value assigned to config.time_zone not recognized.' +
+ raise 'Value assigned to config.time_zone not recognized. ' \
'Run "rake -D time" for a list of tasks for finding appropriate time zone names.'
end
Time.zone_default = zone_default
end
+
+ # Sets the default week start
+ # If assigned value is not a valid day symbol (e.g. :sunday, :monday, ...), an exception will be raised.
+ initializer "active_support.initialize_beginning_of_week" do |app|
+ require 'active_support/core_ext/date/calculations'
+ beginning_of_week_default = Date.find_beginning_of_week!(app.config.beginning_of_week)
+
+ Date.beginning_of_week_default = beginning_of_week_default
+ end
+
+ initializer "active_support.set_configs" do |app|
+ app.config.active_support.each do |k, v|
+ k = "#{k}="
+ ActiveSupport.send(k, v) if ActiveSupport.respond_to? k
+ end
+ end
end
end
diff --git a/lib/active_support/rescuable.rb b/lib/active_support/rescuable.rb
index 0f4a064..a7eba91 100644
--- a/lib/active_support/rescuable.rb
+++ b/lib/active_support/rescuable.rb
@@ -1,6 +1,5 @@
require 'active_support/concern'
require 'active_support/core_ext/class/attribute'
-require 'active_support/core_ext/proc'
require 'active_support/core_ext/string/inflections'
require 'active_support/core_ext/array/extract_options'
@@ -31,11 +30,11 @@ module ActiveSupport
# any.
#
# class ApplicationController < ActionController::Base
- # rescue_from User::NotAuthorized, :with => :deny_access # self defined exception
- # rescue_from ActiveRecord::RecordInvalid, :with => :show_errors
+ # rescue_from User::NotAuthorized, with: :deny_access # self defined exception
+ # rescue_from ActiveRecord::RecordInvalid, with: :show_errors
#
# rescue_from 'MyAppError::Base' do |exception|
- # render :xml => exception, :status => 500
+ # render xml: exception, status: 500
# end
#
# protected
@@ -48,6 +47,7 @@ module ActiveSupport
# end
# end
#
+ # Exceptions raised inside exception handlers are not propagated up.
def rescue_from(*klasses, &block)
options = klasses.extract_options!
@@ -108,7 +108,11 @@ module ActiveSupport
when Symbol
method(rescuer)
when Proc
- rescuer.bind(self)
+ if rescuer.arity == 0
+ Proc.new { instance_exec(&rescuer) }
+ else
+ Proc.new { |_exception| instance_exec(_exception, &rescuer) }
+ end
end
end
end
diff --git a/lib/active_support/ruby/shim.rb b/lib/active_support/ruby/shim.rb
deleted file mode 100644
index 608b3fe..0000000
--- a/lib/active_support/ruby/shim.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-# Backported Ruby builtins so you can code with the latest & greatest
-# but still run on any Ruby 1.8.x.
-#
-# Date next_year, next_month
-# DateTime to_date, to_datetime, xmlschema
-# Enumerable group_by, each_with_object, none?
-# Process Process.daemon
-# REXML security fix
-# String ord
-# Time to_date, to_time, to_datetime
-require 'active_support'
-require 'active_support/core_ext/date/calculations'
-require 'active_support/core_ext/date_time/conversions'
-require 'active_support/core_ext/enumerable'
-require 'active_support/core_ext/process/daemon'
-require 'active_support/core_ext/string/conversions'
-require 'active_support/core_ext/string/interpolation'
-require 'active_support/core_ext/string/encoding'
-require 'active_support/core_ext/rexml'
-require 'active_support/core_ext/time/conversions'
-require 'active_support/core_ext/file/path'
-require 'active_support/core_ext/module/method_names'
\ No newline at end of file
diff --git a/lib/active_support/string_inquirer.rb b/lib/active_support/string_inquirer.rb
index e6b1f39..45271c9 100644
--- a/lib/active_support/string_inquirer.rb
+++ b/lib/active_support/string_inquirer.rb
@@ -3,19 +3,24 @@ module ActiveSupport
# for equality. The value returned by <tt>Rails.env</tt> is wrapped
# in a StringInquirer object so instead of calling this:
#
- # Rails.env == "production"
+ # Rails.env == 'production'
#
# you can call this:
#
# Rails.env.production?
- #
class StringInquirer < String
- def method_missing(method_name, *arguments)
- if method_name.to_s[-1,1] == "?"
- self == method_name.to_s[0..-2]
- else
- super
+ private
+
+ def respond_to_missing?(method_name, include_private = false)
+ method_name[-1] == '?'
+ end
+
+ def method_missing(method_name, *arguments)
+ if method_name[-1] == '?'
+ self == method_name[0..-2]
+ else
+ super
+ end
end
- end
end
end
diff --git a/lib/active_support/subscriber.rb b/lib/active_support/subscriber.rb
new file mode 100644
index 0000000..34c6f90
--- /dev/null
+++ b/lib/active_support/subscriber.rb
@@ -0,0 +1,93 @@
+require 'active_support/per_thread_registry'
+
+module ActiveSupport
+ # ActiveSupport::Subscriber is an object set to consume
+ # ActiveSupport::Notifications. The subscriber dispatches notifications to
+ # a registered object based on its given namespace.
+ #
+ # An example would be Active Record subscriber responsible for collecting
+ # statistics about queries:
+ #
+ # module ActiveRecord
+ # class StatsSubscriber < ActiveSupport::Subscriber
+ # def sql(event)
+ # Statsd.timing("sql.#{event.payload[:name]}", event.duration)
+ # end
+ # end
+ # end
+ #
+ # And it's finally registered as:
+ #
+ # ActiveRecord::StatsSubscriber.attach_to :active_record
+ #
+ # Since we need to know all instance methods before attaching the log
+ # subscriber, the line above should be called after your subscriber definition.
+ #
+ # After configured, whenever a "sql.active_record" notification is published,
+ # it will properly dispatch the event (ActiveSupport::Notifications::Event) to
+ # the +sql+ method.
+ class Subscriber
+ class << self
+
+ # Attach the subscriber to a namespace.
+ def attach_to(namespace, subscriber=new, notifier=ActiveSupport::Notifications)
+ subscribers << subscriber
+
+ subscriber.public_methods(false).each do |event|
+ next if %w{ start finish }.include?(event.to_s)
+
+ notifier.subscribe("#{event}.#{namespace}", subscriber)
+ end
+ end
+
+ def subscribers
+ @@subscribers ||= []
+ end
+ end
+
+ def initialize
+ @queue_key = [self.class.name, object_id].join "-"
+ super
+ end
+
+ def start(name, id, payload)
+ e = ActiveSupport::Notifications::Event.new(name, Time.now, nil, id, payload)
+ parent = event_stack.last
+ parent << e if parent
+
+ event_stack.push e
+ end
+
+ def finish(name, id, payload)
+ finished = Time.now
+ event = event_stack.pop
+ event.end = finished
+ event.payload.merge!(payload)
+
+ method = name.split('.').first
+ send(method, event)
+ end
+
+ private
+
+ def event_stack
+ SubscriberQueueRegistry.get_queue(@queue_key)
+ end
+ end
+
+ # This is a registry for all the event stacks kept for subscribers.
+ #
+ # See the documentation of <tt>ActiveSupport::PerThreadRegistry</tt>
+ # for further details.
+ class SubscriberQueueRegistry # :nodoc:
+ extend PerThreadRegistry
+
+ def initialize
+ @registry = {}
+ end
+
+ def get_queue(queue_key)
+ @registry[queue_key] ||= []
+ end
+ end
+end
diff --git a/lib/active_support/tagged_logging.rb b/lib/active_support/tagged_logging.rb
index 7e7f7ec..18bc919 100644
--- a/lib/active_support/tagged_logging.rb
+++ b/lib/active_support/tagged_logging.rb
@@ -1,80 +1,75 @@
require 'active_support/core_ext/object/blank'
-require 'active_support/deprecation'
require 'logger'
+require 'active_support/logger'
module ActiveSupport
- # Wraps any standard Logger class to provide tagging capabilities. Examples:
+ # Wraps any standard Logger object to provide tagging capabilities.
#
- # Logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT))
- # Logger.tagged("BCX") { Logger.info "Stuff" } # Logs "[BCX] Stuff"
- # Logger.tagged("BCX", "Jason") { Logger.info "Stuff" } # Logs "[BCX] [Jason] Stuff"
- # Logger.tagged("BCX") { Logger.tagged("Jason") { Logger.info "Stuff" } } # Logs "[BCX] [Jason] Stuff"
+ # logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT))
+ # logger.tagged('BCX') { logger.info 'Stuff' } # Logs "[BCX] Stuff"
+ # logger.tagged('BCX', "Jason") { logger.info 'Stuff' } # Logs "[BCX] [Jason] Stuff"
+ # logger.tagged('BCX') { logger.tagged('Jason') { logger.info 'Stuff' } } # Logs "[BCX] [Jason] Stuff"
#
- # This is used by the default Rails.logger as configured by Railties to make it easy to stamp log lines
- # with subdomains, request ids, and anything else to aid debugging of multi-user production applications.
- class TaggedLogging
- def initialize(logger)
- @logger = logger
- end
+ # This is used by the default Rails.logger as configured by Railties to make
+ # it easy to stamp log lines with subdomains, request ids, and anything else
+ # to aid debugging of multi-user production applications.
+ module TaggedLogging
+ module Formatter # :nodoc:
+ # This method is invoked when a log event occurs.
+ def call(severity, timestamp, progname, msg)
+ super(severity, timestamp, progname, "#{tags_text}#{msg}")
+ end
- def tagged(*tags)
- new_tags = push_tags(*tags)
- yield self
- ensure
- pop_tags(new_tags.size)
- end
+ def tagged(*tags)
+ new_tags = push_tags(*tags)
+ yield self
+ ensure
+ pop_tags(new_tags.size)
+ end
- def push_tags(*tags)
- tags.flatten.reject(&:blank?).tap do |new_tags|
- current_tags.concat new_tags
+ def push_tags(*tags)
+ tags.flatten.reject(&:blank?).tap do |new_tags|
+ current_tags.concat new_tags
+ end
end
- end
- def pop_tags(size = 1)
- current_tags.pop size
- end
+ def pop_tags(size = 1)
+ current_tags.pop size
+ end
- def clear_tags!
- current_tags.clear
- end
+ def clear_tags!
+ current_tags.clear
+ end
+
+ def current_tags
+ Thread.current[:activesupport_tagged_logging_tags] ||= []
+ end
- def silence(temporary_level = Logger::ERROR, &block)
- @logger.silence(temporary_level, &block)
+ private
+ def tags_text
+ tags = current_tags
+ if tags.any?
+ tags.collect { |tag| "[#{tag}] " }.join
+ end
+ end
end
- deprecate :silence
- def add(severity, message = nil, progname = nil, &block)
- message = (block_given? ? block.call : progname) if message.nil?
- @logger.add(severity, "#{tags_text}#{message}", progname)
+ def self.new(logger)
+ # Ensure we set a default formatter so we aren't extending nil!
+ logger.formatter ||= ActiveSupport::Logger::SimpleFormatter.new
+ logger.formatter.extend Formatter
+ logger.extend(self)
end
- %w( fatal error warn info debug unknown ).each do |severity|
- eval <<-EOM, nil, __FILE__, __LINE__ + 1
- def #{severity}(progname = nil, &block)
- add(Logger::#{severity.upcase}, nil, progname, &block)
- end
- EOM
+ delegate :push_tags, :pop_tags, :clear_tags!, to: :formatter
+
+ def tagged(*tags)
+ formatter.tagged(*tags) { yield self }
end
def flush
clear_tags!
- @logger.flush if @logger.respond_to?(:flush)
- end
-
- def method_missing(method, *args)
- @logger.send(method, *args)
+ super if defined?(super)
end
-
- private
- def tags_text
- tags = current_tags
- if tags.any?
- tags.collect { |tag| "[#{tag}] " }.join
- end
- end
-
- def current_tags
- Thread.current[:activesupport_tagged_logging_tags] ||= []
- end
end
end
diff --git a/lib/active_support/test_case.rb b/lib/active_support/test_case.rb
index 573736e..8b392c3 100644
--- a/lib/active_support/test_case.rb
+++ b/lib/active_support/test_case.rb
@@ -1,34 +1,67 @@
-require 'test/unit/testcase'
+gem 'minitest' # make sure we get the gem, not stdlib
+require 'minitest/unit'
+require 'active_support/testing/tagged_logging'
require 'active_support/testing/setup_and_teardown'
require 'active_support/testing/assertions'
require 'active_support/testing/deprecation'
-require 'active_support/testing/declarative'
require 'active_support/testing/pending'
+require 'active_support/testing/declarative'
require 'active_support/testing/isolation'
-require 'active_support/testing/mochaing'
+require 'active_support/testing/constant_lookup'
require 'active_support/core_ext/kernel/reporting'
+require 'active_support/deprecation'
-module ActiveSupport
- class TestCase < ::Test::Unit::TestCase
- if defined?(MiniTest::Assertions) && TestCase < MiniTest::Assertions
- Assertion = MiniTest::Assertion
- alias_method :method_name, :name if method_defined? :name
- alias_method :method_name, :__name__ if method_defined? :__name__
- else
- Assertion = Test::Unit::AssertionFailedError
+begin
+ silence_warnings { require 'mocha/setup' }
+rescue LoadError
+end
- undef :default_test
- end
+module ActiveSupport
+ class TestCase < ::MiniTest::Unit::TestCase
+ Assertion = MiniTest::Assertion
+ alias_method :method_name, :__name__
$tags = {}
def self.for_tag(tag)
yield if $tags[tag]
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
+
+ include ActiveSupport::Testing::TaggedLogging
include ActiveSupport::Testing::SetupAndTeardown
include ActiveSupport::Testing::Assertions
include ActiveSupport::Testing::Deprecation
include ActiveSupport::Testing::Pending
extend ActiveSupport::Testing::Declarative
+
+ # test/unit backwards compatibility methods
+ alias :assert_raise :assert_raises
+ alias :assert_not_empty :refute_empty
+ alias :assert_not_equal :refute_equal
+ alias :assert_not_in_delta :refute_in_delta
+ alias :assert_not_in_epsilon :refute_in_epsilon
+ alias :assert_not_includes :refute_includes
+ alias :assert_not_instance_of :refute_instance_of
+ alias :assert_not_kind_of :refute_kind_of
+ alias :assert_no_match :refute_match
+ alias :assert_not_nil :refute_nil
+ alias :assert_not_operator :refute_operator
+ alias :assert_not_predicate :refute_predicate
+ alias :assert_not_respond_to :refute_respond_to
+ alias :assert_not_same :refute_same
+
+ # Fails if the block raises an exception.
+ #
+ # assert_nothing_raised do
+ # ...
+ # end
+ def assert_nothing_raised(*args)
+ yield
+ end
end
end
diff --git a/lib/active_support/testing/assertions.rb b/lib/active_support/testing/assertions.rb
index f3629ad..175f7ff 100644
--- a/lib/active_support/testing/assertions.rb
+++ b/lib/active_support/testing/assertions.rb
@@ -1,51 +1,67 @@
-require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/object/blank'
module ActiveSupport
module Testing
module Assertions
- # Test numeric difference between the return value of an expression as a result of what is evaluated
- # in the yielded block.
+ # Assert that an expression is not truthy. Passes if <tt>object</tt> is
+ # +nil+ or +false+. "Truthy" means "considered true in a conditional"
+ # like <tt>if foo</tt>.
+ #
+ # assert_not nil # => true
+ # assert_not false # => true
+ # assert_not 'foo' # => 'foo' is not nil or false
+ #
+ # An error message can be specified.
+ #
+ # assert_not foo, 'foo should be false'
+ def assert_not(object, message = nil)
+ message ||= "Expected #{mu_pp(object)} to be nil or false"
+ assert !object, message
+ end
+
+ # Test numeric difference between the return value of an expression as a
+ # result of what is evaluated in the yielded block.
#
# assert_difference 'Article.count' do
- # post :create, :article => {...}
+ # post :create, article: {...}
# end
#
# An arbitrary expression is passed in and evaluated.
#
# assert_difference 'assigns(:article).comments(:reload).size' do
- # post :create, :comment => {...}
+ # post :create, comment: {...}
# end
#
- # An arbitrary positive or negative difference can be specified. The default is +1.
+ # An arbitrary positive or negative difference can be specified.
+ # The default is <tt>1</tt>.
#
# assert_difference 'Article.count', -1 do
- # post :delete, :id => ...
+ # post :delete, id: ...
# end
#
# An array of expressions can also be passed in and evaluated.
#
- # assert_difference [ 'Article.count', 'Post.count' ], +2 do
- # post :create, :article => {...}
+ # assert_difference [ 'Article.count', 'Post.count' ], 2 do
+ # post :create, article: {...}
# end
#
# A lambda or a list of lambdas can be passed in and evaluated:
#
- # assert_difference lambda { Article.count }, 2 do
- # post :create, :article => {...}
+ # assert_difference ->{ Article.count }, 2 do
+ # post :create, article: {...}
# end
#
# assert_difference [->{ Article.count }, ->{ Post.count }], 2 do
- # post :create, :article => {...}
+ # post :create, article: {...}
# end
#
- # A error message can be specified.
+ # An error message can be specified.
#
- # assert_difference 'Article.count', -1, "An Article should be destroyed" do
- # post :delete, :id => ...
+ # assert_difference 'Article.count', -1, 'An Article should be destroyed' do
+ # post :delete, id: ...
# end
def assert_difference(expression, difference = 1, message = nil, &block)
- expressions = Array.wrap expression
+ expressions = Array(expression)
exps = expressions.map { |e|
e.respond_to?(:call) ? e : lambda { eval(e, block.binding) }
@@ -61,34 +77,48 @@ module ActiveSupport
end
end
- # Assertion that the numeric result of evaluating an expression is not changed before and after
- # invoking the passed in block.
+ # Assertion that the numeric result of evaluating an expression is not
+ # changed before and after invoking the passed in block.
#
# assert_no_difference 'Article.count' do
- # post :create, :article => invalid_attributes
+ # post :create, article: invalid_attributes
# end
#
- # A error message can be specified.
+ # An error message can be specified.
#
- # assert_no_difference 'Article.count', "An Article should not be created" do
- # post :create, :article => invalid_attributes
+ # assert_no_difference 'Article.count', 'An Article should not be created' do
+ # post :create, article: invalid_attributes
# end
def assert_no_difference(expression, message = nil, &block)
assert_difference expression, 0, message, &block
end
- # Test if an expression is blank. Passes if object.blank? is true.
+ # Test if an expression is blank. Passes if <tt>object.blank?</tt>
+ # is +true+.
#
- # assert_blank [] # => true
+ # assert_blank [] # => true
+ # assert_blank [[]] # => [[]] is not blank
+ #
+ # An error message can be specified.
+ #
+ # assert_blank [], 'this should be blank'
def assert_blank(object, message=nil)
+ ActiveSupport::Deprecation.warn('"assert_blank" is deprecated. Please use "assert object.blank?" instead')
message ||= "#{object.inspect} is not blank"
assert object.blank?, message
end
- # Test if an expression is not blank. Passes if object.present? is true.
+ # Test if an expression is not blank. Passes if <tt>object.present?</tt>
+ # is +true+.
+ #
+ # assert_present({ data: 'x' }) # => true
+ # assert_present({}) # => {} is blank
+ #
+ # An error message can be specified.
#
- # assert_present {:data => 'x' } # => true
+ # assert_present({ data: 'x' }, 'this should not be blank')
def assert_present(object, message=nil)
+ ActiveSupport::Deprecation.warn('"assert_present" is deprecated. Please use "assert object.present?" instead')
message ||= "#{object.inspect} is blank"
assert object.present?, message
end
diff --git a/lib/active_support/testing/autorun.rb b/lib/active_support/testing/autorun.rb
new file mode 100644
index 0000000..c446adc
--- /dev/null
+++ b/lib/active_support/testing/autorun.rb
@@ -0,0 +1,5 @@
+gem 'minitest'
+
+require 'minitest/unit'
+
+MiniTest::Unit.autorun
diff --git a/lib/active_support/testing/constant_lookup.rb b/lib/active_support/testing/constant_lookup.rb
new file mode 100644
index 0000000..1b2a75c
--- /dev/null
+++ b/lib/active_support/testing/constant_lookup.rb
@@ -0,0 +1,54 @@
+require "active_support/concern"
+require "active_support/inflector"
+
+module ActiveSupport
+ module Testing
+ # Resolves a constant from a minitest spec name.
+ #
+ # Given the following spec-style test:
+ #
+ # describe WidgetsController, :index do
+ # describe "authenticated user" do
+ # describe "returns widgets" do
+ # it "has a controller that exists" do
+ # assert_kind_of WidgetsController, @controller
+ # end
+ # end
+ # end
+ # end
+ #
+ # The test will have the following name:
+ #
+ # "WidgetsController::index::authenticated user::returns widgets"
+ #
+ # The constant WidgetsController can be resolved from the name.
+ # The following code will resolve the constant:
+ #
+ # controller = determine_constant_from_test_name(name) do |constant|
+ # Class === constant && constant < ::ActionController::Metal
+ # end
+ module ConstantLookup
+ extend ::ActiveSupport::Concern
+
+ module ClassMethods # :nodoc:
+ def determine_constant_from_test_name(test_name)
+ names = test_name.split "::"
+ while names.size > 0 do
+ names.last.sub!(/Test$/, "")
+ begin
+ constant = names.join("::").constantize
+ break(constant) if yield(constant)
+ rescue NoMethodError # subclass of NameError
+ raise
+ rescue NameError
+ # Constant wasn't found, move on
+ ensure
+ names.pop
+ end
+ end
+ end
+ end
+
+ end
+ end
+end
diff --git a/lib/active_support/testing/declarative.rb b/lib/active_support/testing/declarative.rb
index 1c05d45..508e372 100644
--- a/lib/active_support/testing/declarative.rb
+++ b/lib/active_support/testing/declarative.rb
@@ -2,7 +2,7 @@ module ActiveSupport
module Testing
module Declarative
- def self.extended(klass)
+ def self.extended(klass) #:nodoc:
klass.class_eval do
unless method_defined?(:describe)
diff --git a/lib/active_support/testing/deprecation.rb b/lib/active_support/testing/deprecation.rb
index 3682413..a834290 100644
--- a/lib/active_support/testing/deprecation.rb
+++ b/lib/active_support/testing/deprecation.rb
@@ -34,22 +34,3 @@ module ActiveSupport
end
end
end
-
-begin
- require 'test/unit/error'
-rescue LoadError
- # Using miniunit, ignore.
-else
- module Test
- module Unit
- class Error #:nodoc:
- # Silence warnings when reporting test errors.
- def message_with_silenced_deprecation
- ::ActiveSupport::Deprecation.silence { message_without_silenced_deprecation }
- end
- alias_method :message_without_silenced_deprecation, :message
- alias_method :message, :message_with_silenced_deprecation
- end
- end
- end
-end
diff --git a/lib/active_support/testing/isolation.rb b/lib/active_support/testing/isolation.rb
index 77c0475..e16b73a 100644
--- a/lib/active_support/testing/isolation.rb
+++ b/lib/active_support/testing/isolation.rb
@@ -1,4 +1,6 @@
require 'rbconfig'
+require 'minitest/parallel_each'
+
module ActiveSupport
module Testing
class RemoteError < StandardError
@@ -38,64 +40,48 @@ module ActiveSupport
def method_missing(name, *args)
@calls << [name, args]
end
+
+ def info_signal
+ Signal.list['INFO']
+ end
end
module Isolation
- def self.forking_env?
- !ENV["NO_FORK"] && ((RbConfig::CONFIG['host_os'] !~ /mswin|mingw/) && (RUBY_PLATFORM !~ /java/))
- end
+ require 'thread'
- def self.included(base)
- if defined?(::MiniTest) && base < ::MiniTest::Unit::TestCase
- base.send :include, MiniTest
- elsif defined?(Test::Unit)
- base.send :include, TestUnit
- end
+ def self.included(klass) #:nodoc:
+ klass.extend(Module.new {
+ def test_methods
+ ParallelEach.new super
+ end
+ })
end
- def _run_class_setup # class setup method should only happen in parent
- unless defined?(@@ran_class_setup) || ENV['ISOLATION_TEST']
- self.class.setup if self.class.respond_to?(:setup)
- @@ran_class_setup = true
- end
+ def self.forking_env?
+ !ENV["NO_FORK"] && ((RbConfig::CONFIG['host_os'] !~ /mswin|mingw/) && (RUBY_PLATFORM !~ /java/))
end
- module TestUnit
- def run(result)
- _run_class_setup
-
- yield(Test::Unit::TestCase::STARTED, name)
+ @@class_setup_mutex = Mutex.new
- @_result = result
-
- serialized = run_in_isolation do |proxy|
- begin
- super(proxy) { }
- rescue Exception => e
- proxy.add_error(Test::Unit::Error.new(name, e))
- end
+ def _run_class_setup # class setup method should only happen in parent
+ @@class_setup_mutex.synchronize do
+ unless defined?(@@ran_class_setup) || ENV['ISOLATION_TEST']
+ self.class.setup if self.class.respond_to?(:setup)
+ @@ran_class_setup = true
end
-
- retval, proxy = Marshal.load(serialized)
- proxy.__replay__(@_result)
-
- yield(Test::Unit::TestCase::FINISHED, name)
- retval
end
end
- module MiniTest
- def run(runner)
- _run_class_setup
-
- serialized = run_in_isolation do |isolated_runner|
- super(isolated_runner)
- end
+ def run(runner)
+ _run_class_setup
- retval, proxy = Marshal.load(serialized)
- proxy.__replay__(runner)
- retval
+ serialized = run_in_isolation do |isolated_runner|
+ super(isolated_runner)
end
+
+ retval, proxy = Marshal.load(serialized)
+ proxy.__replay__(runner)
+ retval
end
module Forking
@@ -153,13 +139,3 @@ module ActiveSupport
end
end
end
-
-# Only in subprocess for windows / jruby.
-if ENV['ISOLATION_TEST']
- require "test/unit/collector/objectspace"
- class Test::Unit::Collector::ObjectSpace
- def include?(test)
- super && test.method_name == ENV['ISOLATION_TEST']
- end
- end
-end
diff --git a/lib/active_support/testing/mochaing.rb b/lib/active_support/testing/mochaing.rb
deleted file mode 100644
index ae4e7e9..0000000
--- a/lib/active_support/testing/mochaing.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-begin
- silence_warnings { require 'mocha/setup' }
-rescue LoadError
- # Fake Mocha::ExpectationError so we can rescue it in #run. Bleh.
- Object.const_set :Mocha, Module.new
- Mocha.const_set :ExpectationError, Class.new(StandardError)
-end
diff --git a/lib/active_support/testing/pending.rb b/lib/active_support/testing/pending.rb
index feac7bc..b04bbbb 100644
--- a/lib/active_support/testing/pending.rb
+++ b/lib/active_support/testing/pending.rb
@@ -1,52 +1,14 @@
-# Some code from jeremymcanally's "pending"
-# https://github.com/jeremymcanally/pending/tree/master
+require 'active_support/deprecation'
module ActiveSupport
module Testing
- module Pending
-
- unless defined?(Spec)
-
- @@pending_cases = []
- @@at_exit = false
-
+ module Pending # :nodoc:
+ unless defined?(Spec)
def pending(description = "", &block)
- if defined?(::MiniTest)
- skip(description.blank? ? nil : description)
- else
- if description.is_a?(Symbol)
- is_pending = $tags[description]
- return block.call unless is_pending
- end
-
- if block_given?
- failed = false
-
- begin
- block.call
- rescue Exception
- failed = true
- end
-
- flunk("<#{description}> did not fail.") unless failed
- end
-
- caller[0] =~ (/(.*):(.*):in `(.*)'/)
- @@pending_cases << "#{$3} at #{$1}, line #{$2}"
- print "P"
-
- @@at_exit ||= begin
- at_exit do
- puts "\nPending Cases:"
- @@pending_cases.each do |test_case|
- puts test_case
- end
- end
- end
- end
+ ActiveSupport::Deprecation.warn("#pending is deprecated and will be removed in Rails 4.1, please use #skip instead.")
+ skip(description.blank? ? nil : description)
end
end
-
end
end
end
diff --git a/lib/active_support/testing/performance.rb b/lib/active_support/testing/performance.rb
deleted file mode 100644
index dd23f8d..0000000
--- a/lib/active_support/testing/performance.rb
+++ /dev/null
@@ -1,317 +0,0 @@
-require 'fileutils'
-require 'rails/version'
-require 'active_support/concern'
-require 'active_support/core_ext/class/delegating_attributes'
-require 'active_support/core_ext/string/inflections'
-require 'action_view/helpers/number_helper'
-
-module ActiveSupport
- module Testing
- module Performance
- extend ActiveSupport::Concern
-
- included do
- superclass_delegating_accessor :profile_options
- self.profile_options = {}
-
- if defined?(MiniTest::Assertions) && TestCase < MiniTest::Assertions
- include ForMiniTest
- else
- include ForClassicTestUnit
- end
- end
-
- # each implementation should define metrics and freeze the defaults
- DEFAULTS =
- if ARGV.include?('--benchmark') # HAX for rake test
- { :runs => 4,
- :output => 'tmp/performance',
- :benchmark => true }
- else
- { :runs => 1,
- :output => 'tmp/performance',
- :benchmark => false }
- end
-
- def full_profile_options
- DEFAULTS.merge(profile_options)
- end
-
- def full_test_name
- "#{self.class.name}##{method_name}"
- end
-
- module ForMiniTest
- def run(runner)
- @runner = runner
-
- run_warmup
- if full_profile_options && metrics = full_profile_options[:metrics]
- metrics.each do |metric_name|
- if klass = Metrics[metric_name.to_sym]
- run_profile(klass.new)
- end
- end
- end
-
- return
- end
-
- def run_test(metric, mode)
- result = '.'
- begin
- run_callbacks :setup
- setup
- metric.send(mode) { __send__ method_name }
- rescue Exception => e
- result = @runner.puke(self.class, method_name, e)
- ensure
- begin
- teardown
- run_callbacks :teardown, :enumerator => :reverse_each
- rescue Exception => e
- result = @runner.puke(self.class, method_name, e)
- end
- end
- result
- end
- end
-
- module ForClassicTestUnit
- def run(result)
- return if method_name =~ /^default_test$/
-
- yield(self.class::STARTED, name)
- @_result = result
-
- run_warmup
- if full_profile_options && metrics = full_profile_options[:metrics]
- metrics.each do |metric_name|
- if klass = Metrics[metric_name.to_sym]
- run_profile(klass.new)
- result.add_run
- else
- puts '%20s: unsupported' % metric_name
- end
- end
- end
-
- yield(self.class::FINISHED, name)
- end
-
- def run_test(metric, mode)
- run_callbacks :setup
- setup
- metric.send(mode) { __send__ @method_name }
- rescue ::Test::Unit::AssertionFailedError => e
- add_failure(e.message, e.backtrace)
- rescue StandardError, ScriptError => e
- add_error(e)
- ensure
- begin
- teardown
- run_callbacks :teardown, :enumerator => :reverse_each
- rescue ::Test::Unit::AssertionFailedError => e
- add_failure(e.message, e.backtrace)
- rescue StandardError, ScriptError => e
- add_error(e)
- end
- end
- end
-
- protected
- # overridden by each implementation
- def run_gc; end
-
- def run_warmup
- run_gc
-
- time = Metrics::Time.new
- run_test(time, :benchmark)
- puts "%s (%s warmup)" % [full_test_name, time.format(time.total)]
-
- run_gc
- end
-
- def run_profile(metric)
- klass = full_profile_options[:benchmark] ? Benchmarker : Profiler
- performer = klass.new(self, metric)
-
- performer.run
- puts performer.report
- performer.record
- end
-
- class Performer
- delegate :run_test, :full_profile_options, :full_test_name, :to => :@harness
-
- def initialize(harness, metric)
- @harness, @metric, @supported = harness, metric, false
- end
-
- def report
- if @supported
- rate = @total / full_profile_options[:runs]
- '%20s: %s' % [@metric.name, @metric.format(rate)]
- else
- '%20s: unsupported' % @metric.name
- end
- end
-
- protected
- def output_filename
- "#{full_profile_options[:output]}/#{full_test_name}_#{@metric.name}"
- end
- end
-
- # overridden by each implementation
- class Profiler < Performer
- def time_with_block
- before = Time.now
- yield
- Time.now - before
- end
-
- def run; end
- def record; end
- end
-
- class Benchmarker < Performer
- def initialize(*args)
- super
- @supported = @metric.respond_to?('measure')
- end
-
- def run
- return unless @supported
-
- full_profile_options[:runs].to_i.times { run_test(@metric, :benchmark) }
- @total = @metric.total
- end
-
- def record
- avg = @metric.total / full_profile_options[:runs].to_i
- now = Time.now.utc.xmlschema
- with_output_file do |file|
- file.puts "#{avg},#{now},#{environment}"
- end
- end
-
- def environment
- unless defined? @env
- app = "#{$1}.#{$2}" if File.directory?('.git') && `git branch -v` =~ /^\* (\S+)\s+(\S+)/
-
- rails = Rails::VERSION::STRING
- if File.directory?('vendor/rails/.git')
- Dir.chdir('vendor/rails') do
- rails += ".#{$1}.#{$2}" if `git branch -v` =~ /^\* (\S+)\s+(\S+)/
- end
- end
-
- ruby = defined?(RUBY_ENGINE) ? RUBY_ENGINE : 'ruby'
- ruby += "-#{RUBY_VERSION}.#{RUBY_PATCHLEVEL}"
-
- @env = [app, rails, ruby, RUBY_PLATFORM] * ','
- end
-
- @env
- end
-
- protected
- HEADER = 'measurement,created_at,app,rails,ruby,platform'
-
- def with_output_file
- fname = output_filename
-
- if new = !File.exist?(fname)
- FileUtils.mkdir_p(File.dirname(fname))
- end
-
- File.open(fname, 'ab') do |file|
- file.puts(HEADER) if new
- yield file
- end
- end
-
- def output_filename
- "#{super}.csv"
- end
- end
-
- module Metrics
- def self.[](name)
- const_get(name.to_s.camelize)
- rescue NameError
- nil
- end
-
- class Base
- include ActionView::Helpers::NumberHelper
-
- attr_reader :total
-
- def initialize
- @total = 0
- end
-
- def name
- @name ||= self.class.name.demodulize.underscore
- end
-
- def benchmark
- with_gc_stats do
- before = measure
- yield
- @total += (measure - before)
- end
- end
-
- # overridden by each implementation
- def profile; end
-
- protected
- # overridden by each implementation
- def with_gc_stats; end
- end
-
- class Time < Base
- def measure
- ::Time.now.to_f
- end
-
- def format(measurement)
- if measurement < 1
- '%d ms' % (measurement * 1000)
- else
- '%.2f sec' % measurement
- end
- end
- end
-
- class Amount < Base
- def format(measurement)
- number_with_delimiter(measurement.floor)
- end
- end
-
- class DigitalInformationUnit < Base
- def format(measurement)
- number_to_human_size(measurement, :precision => 2)
- end
- end
-
- # each implementation provides its own metrics like ProcessTime, Memory or GcRuns
- end
- end
- end
-end
-
-RUBY_ENGINE = 'ruby' unless defined?(RUBY_ENGINE) # mri 1.8
-case RUBY_ENGINE
- when 'ruby' then require 'active_support/testing/performance/ruby'
- when 'rbx' then require 'active_support/testing/performance/rubinius'
- when 'jruby' then require 'active_support/testing/performance/jruby'
- else
- $stderr.puts 'Your ruby interpreter is not supported for benchmarking.'
- exit
-end
diff --git a/lib/active_support/testing/performance/jruby.rb b/lib/active_support/testing/performance/jruby.rb
deleted file mode 100644
index b347539..0000000
--- a/lib/active_support/testing/performance/jruby.rb
+++ /dev/null
@@ -1,115 +0,0 @@
-require 'jruby/profiler'
-require 'java'
-java_import java.lang.management.ManagementFactory
-
-module ActiveSupport
- module Testing
- module Performance
- DEFAULTS.merge!(
- if ARGV.include?('--benchmark')
- {:metrics => [:wall_time, :user_time, :memory, :gc_runs, :gc_time]}
- else
- { :metrics => [:wall_time],
- :formats => [:flat, :graph] }
- end).freeze
-
- protected
- def run_gc
- ManagementFactory.memory_mx_bean.gc
- end
-
- class Profiler < Performer
- def initialize(*args)
- super
- @supported = @metric.is_a?(Metrics::WallTime)
- end
-
- def run
- return unless @supported
-
- @total = time_with_block do
- @data = JRuby::Profiler.profile do
- full_profile_options[:runs].to_i.times { run_test(@metric, :profile) }
- end
- end
- end
-
- def record
- return unless @supported
-
- klasses = full_profile_options[:formats].map { |f| JRuby::Profiler.const_get("#{f.to_s.camelize}ProfilePrinter") }.compact
-
- klasses.each do |klass|
- fname = output_filename(klass)
- FileUtils.mkdir_p(File.dirname(fname))
- file = File.open(fname, 'wb') do |file|
- klass.new(@data).printProfile(file)
- end
- end
- end
-
- protected
- def output_filename(printer_class)
- suffix =
- case printer_class.name.demodulize
- when 'FlatProfilePrinter'; 'flat.txt'
- when 'GraphProfilePrinter'; 'graph.txt'
- else printer_class.name.sub(/ProfilePrinter$/, '').underscore
- end
-
- "#{super()}_#{suffix}"
- end
- end
-
- module Metrics
- class Base
- def profile
- yield
- end
-
- protected
- def with_gc_stats
- ManagementFactory.memory_mx_bean.gc
- yield
- end
- end
-
- class WallTime < Time
- def measure
- super
- end
- end
-
- class CpuTime < Time
- def measure
- ManagementFactory.thread_mx_bean.get_current_thread_cpu_time / 1000 / 1000 / 1000.0 # seconds
- end
- end
-
- class UserTime < Time
- def measure
- ManagementFactory.thread_mx_bean.get_current_thread_user_time / 1000 / 1000 / 1000.0 # seconds
- end
- end
-
- class Memory < DigitalInformationUnit
- def measure
- ManagementFactory.memory_mx_bean.non_heap_memory_usage.used + ManagementFactory.memory_mx_bean.heap_memory_usage.used
- end
- end
-
- class GcRuns < Amount
- def measure
- ManagementFactory.garbage_collector_mx_beans.inject(0) { |total_runs, current_gc| total_runs += current_gc.collection_count }
- end
- end
-
- class GcTime < Time
- def measure
- ManagementFactory.garbage_collector_mx_beans.inject(0) { |total_time, current_gc| total_time += current_gc.collection_time } / 1000.0 # seconds
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_support/testing/performance/rubinius.rb b/lib/active_support/testing/performance/rubinius.rb
deleted file mode 100644
index d9ebfbe..0000000
--- a/lib/active_support/testing/performance/rubinius.rb
+++ /dev/null
@@ -1,113 +0,0 @@
-require 'rubinius/agent'
-
-module ActiveSupport
- module Testing
- module Performance
- DEFAULTS.merge!(
- if ARGV.include?('--benchmark')
- {:metrics => [:wall_time, :memory, :objects, :gc_runs, :gc_time]}
- else
- { :metrics => [:wall_time],
- :formats => [:flat, :graph] }
- end).freeze
-
- protected
- def run_gc
- GC.run(true)
- end
-
- class Performer; end
-
- class Profiler < Performer
- def initialize(*args)
- super
- @supported = @metric.is_a?(Metrics::WallTime)
- end
-
- def run
- return unless @supported
-
- @profiler = Rubinius::Profiler::Instrumenter.new
-
- @total = time_with_block do
- @profiler.profile(false) do
- full_profile_options[:runs].to_i.times { run_test(@metric, :profile) }
- end
- end
- end
-
- def record
- return unless @supported
-
- if(full_profile_options[:formats].include?(:flat))
- create_path_and_open_file(:flat) do |file|
- @profiler.show(file)
- end
- end
-
- if(full_profile_options[:formats].include?(:graph))
- create_path_and_open_file(:graph) do |file|
- @profiler.show(file)
- end
- end
- end
-
- protected
- def create_path_and_open_file(printer_name)
- fname = "#{output_filename}_#{printer_name}.txt"
- FileUtils.mkdir_p(File.dirname(fname))
- File.open(fname, 'wb') do |file|
- yield(file)
- end
- end
- end
-
- module Metrics
- class Base
- attr_reader :loopback
-
- def profile
- yield
- end
-
- protected
- def with_gc_stats
- @loopback = Rubinius::Agent.loopback
- GC.run(true)
- yield
- end
- end
-
- class WallTime < Time
- def measure
- super
- end
- end
-
- class Memory < DigitalInformationUnit
- def measure
- loopback.get("system.memory.counter.bytes").last
- end
- end
-
- class Objects < Amount
- def measure
- loopback.get("system.memory.counter.objects").last
- end
- end
-
- class GcRuns < Amount
- def measure
- loopback.get("system.gc.full.count").last + loopback.get("system.gc.young.count").last
- end
- end
-
- class GcTime < Time
- def measure
- (loopback.get("system.gc.full.wallclock").last + loopback.get("system.gc.young.wallclock").last) / 1000.0
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_support/testing/performance/ruby.rb b/lib/active_support/testing/performance/ruby.rb
deleted file mode 100644
index 50c4852..0000000
--- a/lib/active_support/testing/performance/ruby.rb
+++ /dev/null
@@ -1,152 +0,0 @@
-begin
- require 'ruby-prof'
-rescue LoadError
- $stderr.puts 'Specify ruby-prof as application\'s dependency in Gemfile to run benchmarks.'
- exit
-end
-
-module ActiveSupport
- module Testing
- module Performance
- DEFAULTS.merge!(
- if ARGV.include?('--benchmark')
- { :metrics => [:wall_time, :memory, :objects, :gc_runs, :gc_time] }
- else
- { :min_percent => 0.01,
- :metrics => [:process_time, :memory, :objects],
- :formats => [:flat, :graph_html, :call_tree, :call_stack] }
- end).freeze
-
- protected
- def run_gc
- GC.start
- end
-
- class Profiler < Performer
- def initialize(*args)
- super
- @supported = @metric.measure_mode rescue false
- end
-
- def run
- return unless @supported
-
- RubyProf.measure_mode = @metric.measure_mode
- RubyProf.start
- RubyProf.pause
- full_profile_options[:runs].to_i.times { run_test(@metric, :profile) }
- @data = RubyProf.stop
- @total = @data.threads.sum(0) { |thread| thread.methods.max.total_time }
- end
-
- def record
- return unless @supported
-
- klasses = full_profile_options[:formats].map { |f| RubyProf.const_get("#{f.to_s.camelize}Printer") }.compact
-
- klasses.each do |klass|
- fname = output_filename(klass)
- FileUtils.mkdir_p(File.dirname(fname))
- File.open(fname, 'wb') do |file|
- klass.new(@data).print(file, full_profile_options.slice(:min_percent))
- end
- end
- end
-
- protected
- def output_filename(printer_class)
- suffix =
- case printer_class.name.demodulize
- when 'FlatPrinter'; 'flat.txt'
- when 'FlatPrinterWithLineNumbers'; 'flat_line_numbers.txt'
- when 'GraphPrinter'; 'graph.txt'
- when 'GraphHtmlPrinter'; 'graph.html'
- when 'GraphYamlPrinter'; 'graph.yml'
- when 'CallTreePrinter'; 'tree.txt'
- when 'CallStackPrinter'; 'stack.html'
- when 'DotPrinter'; 'graph.dot'
- else printer_class.name.sub(/Printer$/, '').underscore
- end
-
- "#{super()}_#{suffix}"
- end
- end
-
- module Metrics
- class Base
- def measure_mode
- self.class::Mode
- end
-
- def profile
- RubyProf.resume
- yield
- ensure
- RubyProf.pause
- end
-
- protected
- # overridden by each implementation
- def with_gc_stats
- yield
- end
- end
-
- class ProcessTime < Time
- Mode = RubyProf::PROCESS_TIME if RubyProf.const_defined?(:PROCESS_TIME)
-
- def measure
- RubyProf.measure_process_time
- end
- end
-
- class WallTime < Time
- Mode = RubyProf::WALL_TIME if RubyProf.const_defined?(:WALL_TIME)
-
- def measure
- RubyProf.measure_wall_time
- end
- end
-
- class CpuTime < Time
- Mode = RubyProf::CPU_TIME if RubyProf.const_defined?(:CPU_TIME)
-
- def initialize(*args)
- # FIXME: yeah my CPU is 2.33 GHz
- RubyProf.cpu_frequency = 2.33e9 unless RubyProf.cpu_frequency > 0
- super
- end
-
- def measure
- RubyProf.measure_cpu_time
- end
- end
-
- class Memory < DigitalInformationUnit
- Mode = RubyProf::MEMORY if RubyProf.const_defined?(:MEMORY)
- end
-
- class Objects < Amount
- Mode = RubyProf::ALLOCATIONS if RubyProf.const_defined?(:ALLOCATIONS)
- end
-
- class GcRuns < Amount
- Mode = RubyProf::GC_RUNS if RubyProf.const_defined?(:GC_RUNS)
- end
-
- class GcTime < Time
- Mode = RubyProf::GC_TIME if RubyProf.const_defined?(:GC_TIME)
- end
- end
- end
- end
-end
-
-if RUBY_VERSION.between?('1.9.2', '2.0.0')
- require 'active_support/testing/performance/ruby/yarv'
-elsif RUBY_VERSION.between?('1.8.6', '1.9')
- require 'active_support/testing/performance/ruby/mri'
-else
- $stderr.puts 'Update your ruby interpreter to be able to run benchmarks.'
- exit
-end
diff --git a/lib/active_support/testing/performance/ruby/mri.rb b/lib/active_support/testing/performance/ruby/mri.rb
deleted file mode 100644
index 142279d..0000000
--- a/lib/active_support/testing/performance/ruby/mri.rb
+++ /dev/null
@@ -1,57 +0,0 @@
-module ActiveSupport
- module Testing
- module Performance
- module Metrics
- class Base
- protected
- # Ruby 1.8 + ruby-prof wrapper (enable/disable stats for Benchmarker)
- if GC.respond_to?(:enable_stats)
- def with_gc_stats
- GC.enable_stats
- GC.start
- yield
- ensure
- GC.disable_stats
- end
- end
- end
-
- class Memory < DigitalInformationUnit
- # Ruby 1.8 + ruby-prof wrapper
- if RubyProf.respond_to?(:measure_memory)
- def measure
- RubyProf.measure_memory
- end
- end
- end
-
- class Objects < Amount
- # Ruby 1.8 + ruby-prof wrapper
- if RubyProf.respond_to?(:measure_allocations)
- def measure
- RubyProf.measure_allocations
- end
- end
- end
-
- class GcRuns < Amount
- # Ruby 1.8 + ruby-prof wrapper
- if RubyProf.respond_to?(:measure_gc_runs)
- def measure
- RubyProf.measure_gc_runs
- end
- end
- end
-
- class GcTime < Time
- # Ruby 1.8 + ruby-prof wrapper
- if RubyProf.respond_to?(:measure_gc_time)
- def measure
- RubyProf.measure_gc_time / 1000.0 / 1000.0
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_support/testing/performance/ruby/yarv.rb b/lib/active_support/testing/performance/ruby/yarv.rb
deleted file mode 100644
index 7873262..0000000
--- a/lib/active_support/testing/performance/ruby/yarv.rb
+++ /dev/null
@@ -1,57 +0,0 @@
-module ActiveSupport
- module Testing
- module Performance
- module Metrics
- class Base
- protected
- # Ruby 1.9 with GC::Profiler
- if defined?(GC::Profiler)
- def with_gc_stats
- GC::Profiler.enable
- GC.start
- yield
- ensure
- GC::Profiler.disable
- end
- end
- end
-
- class Memory < DigitalInformationUnit
- # Ruby 1.9 + GCdata patch
- if GC.respond_to?(:malloc_allocated_size)
- def measure
- GC.malloc_allocated_size
- end
- end
- end
-
- class Objects < Amount
- # Ruby 1.9 + GCdata patch
- if GC.respond_to?(:malloc_allocations)
- def measure
- GC.malloc_allocations
- end
- end
- end
-
- class GcRuns < Amount
- # Ruby 1.9
- if GC.respond_to?(:count)
- def measure
- GC.count
- end
- end
- end
-
- class GcTime < Time
- # Ruby 1.9 with GC::Profiler
- if defined?(GC::Profiler) && GC::Profiler.respond_to?(:total_time)
- def measure
- GC::Profiler.total_time
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_support/testing/setup_and_teardown.rb b/lib/active_support/testing/setup_and_teardown.rb
index e5353f6..a65148c 100644
--- a/lib/active_support/testing/setup_and_teardown.rb
+++ b/lib/active_support/testing/setup_and_teardown.rb
@@ -9,12 +9,6 @@ module ActiveSupport
included do
include ActiveSupport::Callbacks
define_callbacks :setup, :teardown
-
- if defined?(MiniTest::Assertions) && TestCase < MiniTest::Assertions
- include ForMiniTest
- else
- include ForClassicTestUnit
- end
end
module ClassMethods
@@ -27,94 +21,14 @@ module ActiveSupport
end
end
- module ForMiniTest
- PASSTHROUGH_EXCEPTIONS = MiniTest::Unit::TestCase::PASSTHROUGH_EXCEPTIONS rescue [NoMemoryError, SignalException, Interrupt, SystemExit]
- def run(runner)
- result = '.'
- begin
- run_callbacks :setup do
- result = super
- end
- rescue *PASSTHROUGH_EXCEPTIONS => e
- raise e
- rescue Exception => e
- result = runner.puke(self.class, method_name, e)
- ensure
- begin
- run_callbacks :teardown
- rescue *PASSTHROUGH_EXCEPTIONS => e
- raise e
- rescue Exception => e
- result = runner.puke(self.class, method_name, e)
- end
- end
- result
- end
+ def before_setup
+ super
+ run_callbacks :setup
end
- module ForClassicTestUnit
- # For compatibility with Ruby < 1.8.6
- PASSTHROUGH_EXCEPTIONS = Test::Unit::TestCase::PASSTHROUGH_EXCEPTIONS rescue [NoMemoryError, SignalException, Interrupt, SystemExit]
-
- # This redefinition is unfortunate but test/unit shows us no alternative.
- # Doubly unfortunate: hax to support Mocha's hax.
- def run(result)
- return if @method_name.to_s == "default_test"
-
- mocha_counter = retrieve_mocha_counter(self, result)
- yield(Test::Unit::TestCase::STARTED, name)
- @_result = result
-
- begin
- begin
- run_callbacks :setup do
- setup
- __send__(@method_name)
- mocha_verify(mocha_counter) if mocha_counter
- end
- rescue Mocha::ExpectationError => e
- add_failure(e.message, e.backtrace)
- rescue Test::Unit::AssertionFailedError => e
- add_failure(e.message, e.backtrace)
- rescue Exception => e
- raise if PASSTHROUGH_EXCEPTIONS.include?(e.class)
- add_error(e)
- ensure
- begin
- teardown
- run_callbacks :teardown
- rescue Mocha::ExpectationError => e
- add_failure(e.message, e.backtrace)
- rescue Test::Unit::AssertionFailedError => e
- add_failure(e.message, e.backtrace)
- rescue Exception => e
- raise if PASSTHROUGH_EXCEPTIONS.include?(e.class)
- add_error(e)
- end
- end
- ensure
- mocha_teardown if mocha_counter
- end
-
- result.add_run
- yield(Test::Unit::TestCase::FINISHED, name)
- end
-
- protected
-
- def retrieve_mocha_counter(test_case, result) #:nodoc:
- if respond_to?(:mocha_verify) # using mocha
- if defined?(Mocha::TestCaseAdapter::AssertionCounter)
- Mocha::TestCaseAdapter::AssertionCounter.new(result)
- elsif defined?(Mocha::Integration::TestUnit::AssertionCounter)
- Mocha::Integration::TestUnit::AssertionCounter.new(result)
- elsif defined?(Mocha::MonkeyPatching::TestUnit::AssertionCounter)
- Mocha::MonkeyPatching::TestUnit::AssertionCounter.new(result)
- else
- Mocha::Integration::AssertionCounter.new(test_case)
- end
- end
- end
+ def after_teardown
+ run_callbacks :teardown
+ super
end
end
end
diff --git a/lib/active_support/testing/tagged_logging.rb b/lib/active_support/testing/tagged_logging.rb
new file mode 100644
index 0000000..9d43eb1
--- /dev/null
+++ b/lib/active_support/testing/tagged_logging.rb
@@ -0,0 +1,25 @@
+module ActiveSupport
+ module Testing
+ # Logs a "PostsControllerTest: test name" heading before each test to
+ # make test.log easier to search and follow along with.
+ module TaggedLogging #:nodoc:
+ attr_writer :tagged_logger
+
+ def before_setup
+ if tagged_logger
+ heading = "#{self.class}: #{__name__}"
+ divider = '-' * heading.size
+ tagged_logger.info divider
+ tagged_logger.info heading
+ tagged_logger.info divider
+ end
+ super
+ end
+
+ private
+ def tagged_logger
+ @tagged_logger ||= (defined?(Rails.logger) && Rails.logger)
+ end
+ end
+ end
+end
diff --git a/lib/active_support/time.rb b/lib/active_support/time.rb
index 86f057d..92a5939 100644
--- a/lib/active_support/time.rb
+++ b/lib/active_support/time.rb
@@ -4,32 +4,17 @@ module ActiveSupport
autoload :Duration, 'active_support/duration'
autoload :TimeWithZone, 'active_support/time_with_zone'
autoload :TimeZone, 'active_support/values/time_zone'
-
- on_load_all do
- [Duration, TimeWithZone, TimeZone]
- end
end
require 'date'
require 'time'
-require 'active_support/core_ext/time/publicize_conversion_methods'
-require 'active_support/core_ext/time/marshal'
-require 'active_support/core_ext/time/acts_like'
-require 'active_support/core_ext/time/calculations'
-require 'active_support/core_ext/time/conversions'
-require 'active_support/core_ext/time/zones'
-
-require 'active_support/core_ext/date/acts_like'
-require 'active_support/core_ext/date/freeze'
-require 'active_support/core_ext/date/calculations'
-require 'active_support/core_ext/date/conversions'
-require 'active_support/core_ext/date/zones'
-
-require 'active_support/core_ext/date_time/acts_like'
-require 'active_support/core_ext/date_time/calculations'
-require 'active_support/core_ext/date_time/conversions'
-require 'active_support/core_ext/date_time/zones'
+require 'active_support/core_ext/time'
+require 'active_support/core_ext/date'
+require 'active_support/core_ext/date_time'
require 'active_support/core_ext/integer/time'
require 'active_support/core_ext/numeric/time'
+
+require 'active_support/core_ext/string/conversions'
+require 'active_support/core_ext/string/zones'
diff --git a/lib/active_support/time/autoload.rb b/lib/active_support/time/autoload.rb
deleted file mode 100644
index c9a7731..0000000
--- a/lib/active_support/time/autoload.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-module ActiveSupport
- autoload :Duration, 'active_support/duration'
- autoload :TimeWithZone, 'active_support/time_with_zone'
- autoload :TimeZone, 'active_support/values/time_zone'
-end
diff --git a/lib/active_support/time_with_zone.rb b/lib/active_support/time_with_zone.rb
index f39b58a..95b9b8e 100644
--- a/lib/active_support/time_with_zone.rb
+++ b/lib/active_support/time_with_zone.rb
@@ -1,14 +1,14 @@
-require "active_support/values/time_zone"
+require 'active_support/values/time_zone'
require 'active_support/core_ext/object/acts_like'
-require 'active_support/core_ext/object/inclusion'
module ActiveSupport
- # A Time-like class that can represent a time in any time zone. Necessary because standard Ruby Time instances are
- # limited to UTC and the system's <tt>ENV['TZ']</tt> zone.
+ # A Time-like class that can represent a time in any time zone. Necessary
+ # because standard Ruby Time instances are limited to UTC and the
+ # system's <tt>ENV['TZ']</tt> zone.
#
- # You shouldn't ever need to create a TimeWithZone instance directly via <tt>new</tt> . Instead use methods
- # +local+, +parse+, +at+ and +now+ on TimeZone instances, and +in_time_zone+ on Time and DateTime instances.
- # Examples:
+ # You shouldn't ever need to create a TimeWithZone instance directly via +new+.
+ # Instead use methods +local+, +parse+, +at+ and +now+ on TimeZone instances,
+ # and +in_time_zone+ on Time and DateTime instances.
#
# Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)'
# Time.zone.local(2007, 2, 10, 15, 30, 45) # => Sat, 10 Feb 2007 15:30:45 EST -05:00
@@ -19,8 +19,8 @@ module ActiveSupport
#
# See Time and TimeZone for further documentation of these methods.
#
- # TimeWithZone instances implement the same API as Ruby Time instances, so that Time and TimeWithZone instances are interchangeable.
- # Examples:
+ # TimeWithZone instances implement the same API as Ruby Time instances, so
+ # that Time and TimeWithZone instances are interchangeable.
#
# t = Time.zone.now # => Sun, 18 May 2008 13:27:25 EDT -04:00
# t.hour # => 13
@@ -33,10 +33,11 @@ module ActiveSupport
# t > Time.utc(1999) # => true
# t.is_a?(Time) # => true
# t.is_a?(ActiveSupport::TimeWithZone) # => true
- #
class TimeWithZone
+
+ # Report class name as 'Time' to thwart type checking.
def self.name
- 'Time' # Report class name as 'Time' to thwart type checking
+ 'Time'
end
include Comparable
@@ -72,33 +73,56 @@ module ActiveSupport
utc.in_time_zone(new_zone)
end
- # Returns a <tt>Time.local()</tt> instance of the simultaneous time in your system's <tt>ENV['TZ']</tt> zone
+ # Returns a <tt>Time.local()</tt> instance of the simultaneous time in your
+ # system's <tt>ENV['TZ']</tt> zone.
def localtime
utc.respond_to?(:getlocal) ? utc.getlocal : utc.to_time.getlocal
end
alias_method :getlocal, :localtime
+ # Returns true if the current time is within Daylight Savings Time for the
+ # specified time zone.
+ #
+ # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)'
+ # Time.zone.parse("2012-5-30").dst? # => true
+ # Time.zone.parse("2012-11-30").dst? # => false
def dst?
period.dst?
end
alias_method :isdst, :dst?
+ # Returns true if the current time zone is set to UTC.
+ #
+ # Time.zone = 'UTC' # => 'UTC'
+ # Time.zone.now.utc? # => true
+ # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)'
+ # Time.zone.now.utc? # => false
def utc?
time_zone.name == 'UTC'
end
alias_method :gmt?, :utc?
+ # Returns the offset from current time to UTC time in seconds.
def utc_offset
period.utc_total_offset
end
alias_method :gmt_offset, :utc_offset
alias_method :gmtoff, :utc_offset
+ # Returns a formatted string of the offset from UTC, or an alternative
+ # string if the time zone is already UTC.
+ #
+ # Time.zone = 'Eastern Time (US & Canada)' # => "Eastern Time (US & Canada)"
+ # Time.zone.now.formatted_offset(true) # => "-05:00"
+ # Time.zone.now.formatted_offset(false) # => "-0500"
+ # Time.zone = 'UTC' # => "UTC"
+ # Time.zone.now.formatted_offset(true, "0") # => "0"
def formatted_offset(colon = true, alternate_utc_string = nil)
utc? && alternate_utc_string || TimeZone.seconds_to_utc_offset(utc_offset, colon)
end
- # Time uses +zone+ to display the time zone abbreviation, so we're duck-typing it.
+ # Time uses +zone+ to display the time zone abbreviation, so we're
+ # duck-typing it.
def zone
period.zone_identifier.to_s
end
@@ -116,11 +140,10 @@ module ActiveSupport
end
alias_method :iso8601, :xmlschema
- # Coerces time to a string for JSON encoding. The default format is ISO 8601. You can get
- # %Y/%m/%d %H:%M:%S +offset style by setting <tt>ActiveSupport::JSON::Encoding.use_standard_json_time_format</tt>
- # to false.
- #
- # ==== Examples
+ # Coerces time to a string for JSON encoding. The default format is ISO 8601.
+ # You can get %Y/%m/%d %H:%M:%S +offset style by setting
+ # <tt>ActiveSupport::JSON::Encoding.use_standard_json_time_format</tt>
+ # 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
@@ -129,10 +152,9 @@ module ActiveSupport
# # 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"
- #
def as_json(options = nil)
if ActiveSupport::JSON::Encoding.use_standard_json_time_format
- xmlschema
+ xmlschema(3)
else
%(#{time.strftime("%Y/%m/%d %H:%M:%S")} #{formatted_offset(false)})
end
@@ -146,16 +168,18 @@ module ActiveSupport
end
end
- def to_yaml(options = {})
- return super if defined?(YAML::ENGINE) && !YAML::ENGINE.syck?
-
- utc.to_yaml(options)
- end
-
+ # Returns a string of the object's date and time in the format used by
+ # HTTP requests.
+ #
+ # Time.zone.now.httpdate # => "Tue, 01 Jan 2013 04:39:43 GMT"
def httpdate
utc.httpdate
end
+ # Returns a string of the object's date and time in the RFC 2822 standard
+ # format.
+ #
+ # Time.zone.now.rfc2822 # => "Tue, 01 Jan 2013 04:51:39 +0000"
def rfc2822
to_s(:rfc822)
end
@@ -174,14 +198,14 @@ module ActiveSupport
end
alias_method :to_formatted_s, :to_s
- # Replaces <tt>%Z</tt> and <tt>%z</tt> directives with +zone+ and +formatted_offset+, respectively, before passing to
- # Time#strftime, so that zone information is correct
+ # Replaces <tt>%Z</tt> and <tt>%z</tt> directives with +zone+ and
+ # +formatted_offset+, respectively, before passing to Time#strftime, so
+ # that zone information is correct
def strftime(format)
- format = format.gsub('%Z', zone).
- gsub('%z', formatted_offset(false)).
- gsub('%:z', formatted_offset(true)).
- gsub('%::z', formatted_offset(true) + ":00")
-
+ format = format.gsub('%Z', zone)
+ .gsub('%z', formatted_offset(false))
+ .gsub('%:z', formatted_offset(true))
+ .gsub('%::z', formatted_offset(true) + ":00")
time.strftime(format)
end
@@ -190,18 +214,24 @@ module ActiveSupport
utc <=> other
end
+ # Returns true if the current object's time is within the specified
+ # +min+ and +max+ time.
def between?(min, max)
utc.between?(min, max)
end
+ # Returns true if the current object's time is in the past.
def past?
utc.past?
end
+ # Returns true if the current object's time falls within
+ # the current day.
def today?
time.today?
end
+ # Returns true if the current object's time is in the future.
def future?
utc.future?
end
@@ -262,7 +292,7 @@ module ActiveSupport
end
end
- %w(year mon month day mday wday yday hour min sec to_date).each do |method_name|
+ %w(year mon month day mday wday yday hour min sec usec nsec to_date).each do |method_name|
class_eval <<-EOV, __FILE__, __LINE__ + 1
def #{method_name} # def month
time.#{method_name} # time.month
@@ -270,10 +300,6 @@ module ActiveSupport
EOV
end
- def usec
- time.respond_to?(:usec) ? time.usec : 0
- end
-
def to_a
[time.sec, time.min, time.hour, time.day, time.mon, time.year, time.wday, time.yday, dst?, zone]
end
@@ -287,9 +313,13 @@ module ActiveSupport
end
alias_method :tv_sec, :to_i
- # A TimeWithZone acts like a Time, so just return +self+.
+ def to_r
+ utc.to_r
+ end
+
+ # Return an instance of Time in the system timezone.
def to_time
- utc
+ utc.to_time
end
def to_datetime
@@ -320,16 +350,20 @@ module ActiveSupport
initialize(variables[0].utc, ::Time.find_zone(variables[1]), variables[2].utc)
end
- # Ensure proxy class responds to all methods that underlying time instance responds to.
- def respond_to?(sym, include_priv = false)
+ # Ensure proxy class responds to all methods that underlying time instance
+ # responds to.
+ def respond_to_missing?(sym, include_priv)
# consistently respond false to acts_like?(:date), regardless of whether #time is a Time or DateTime
- return false if sym.to_s == 'acts_like_date?'
- super || time.respond_to?(sym, include_priv)
+ return false if sym.to_sym == :acts_like_date?
+ time.respond_to?(sym, include_priv)
end
- # Send the missing method to +time+ instance, and wrap result in a new TimeWithZone with the existing +time_zone+.
+ # Send the missing method to +time+ instance, and wrap result in a new
+ # TimeWithZone with the existing +time_zone+.
def method_missing(sym, *args, &block)
wrap_with_time_zone time.__send__(sym, *args, &block)
+ rescue NoMethodError => e
+ raise e, e.message.sub(time.inspect, self.inspect), e.backtrace
end
private
@@ -347,12 +381,11 @@ module ActiveSupport
end
def transfer_time_values_to_utc_constructor(time)
- usec = time.respond_to?(:nsec) ? Rational(time.nsec, 1000) : (time.respond_to?(:usec) ? time.usec : 0)
- ::Time.utc_time(time.year, time.month, time.day, time.hour, time.min, time.sec, usec)
+ ::Time.utc(time.year, time.month, time.day, time.hour, time.min, time.sec, Rational(time.nsec, 1000))
end
def duration_of_variable_length?(obj)
- ActiveSupport::Duration === obj && obj.parts.any? {|p| p[0].in?([:years, :months, :days]) }
+ ActiveSupport::Duration === obj && obj.parts.any? {|p| [:years, :months, :days].include?(p[0]) }
end
def wrap_with_time_zone(time)
diff --git a/lib/active_support/values/time_zone.rb b/lib/active_support/values/time_zone.rb
index c7d8fc2..3cf82a2 100644
--- a/lib/active_support/values/time_zone.rb
+++ b/lib/active_support/values/time_zone.rb
@@ -1,30 +1,37 @@
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/object/try'
-# The TimeZone class serves as a wrapper around TZInfo::Timezone instances. It allows us to do the following:
-#
-# * Limit the set of zones provided by TZInfo to a meaningful subset of 142 zones.
-# * Retrieve and display zones with a friendlier name (e.g., "Eastern Time (US & Canada)" instead of "America/New_York").
-# * Lazily load TZInfo::Timezone instances only when they're needed.
-# * Create ActiveSupport::TimeWithZone instances via TimeZone's +local+, +parse+, +at+ and +now+ methods.
-#
-# If you set <tt>config.time_zone</tt> in the Rails Application, you can access this TimeZone object via <tt>Time.zone</tt>:
-#
-# # application.rb:
-# class Application < Rails::Application
-# config.time_zone = "Eastern Time (US & Canada)"
-# end
-#
-# Time.zone # => #<TimeZone:0x514834...>
-# Time.zone.name # => "Eastern Time (US & Canada)"
-# Time.zone.now # => Sun, 18 May 2008 14:30:44 EDT -04:00
-#
-# The version of TZInfo bundled with Active Support only includes the definitions necessary to support the zones
-# defined by the TimeZone class. If you need to use zones that aren't defined by TimeZone, you'll need to install the TZInfo gem
-# (if a recent version of the gem is installed locally, this will be used instead of the bundled version.)
module ActiveSupport
+ # The TimeZone class serves as a wrapper around TZInfo::Timezone instances.
+ # It allows us to do the following:
+ #
+ # * Limit the set of zones provided by TZInfo to a meaningful subset of 146
+ # zones.
+ # * Retrieve and display zones with a friendlier name
+ # (e.g., "Eastern Time (US & Canada)" instead of "America/New_York").
+ # * Lazily load TZInfo::Timezone instances only when they're needed.
+ # * Create ActiveSupport::TimeWithZone instances via TimeZone's +local+,
+ # +parse+, +at+ and +now+ methods.
+ #
+ # If you set <tt>config.time_zone</tt> in the Rails Application, you can
+ # access this TimeZone object via <tt>Time.zone</tt>:
+ #
+ # # application.rb:
+ # class Application < Rails::Application
+ # config.time_zone = 'Eastern Time (US & Canada)'
+ # end
+ #
+ # Time.zone # => #<TimeZone:0x514834...>
+ # Time.zone.name # => "Eastern Time (US & Canada)"
+ # Time.zone.now # => Sun, 18 May 2008 14:30:44 EDT -04:00
+ #
+ # The version of TZInfo bundled with Active Support only includes the
+ # definitions necessary to support the zones defined by the TimeZone class.
+ # If you need to use zones that aren't defined by TimeZone, you'll need to
+ # install the TZInfo gem (if a recent version of the gem is installed locally,
+ # this will be used instead of the bundled version.)
class TimeZone
- # Keys are Rails TimeZone names, values are TZInfo identifiers
+ # Keys are Rails TimeZone names, values are TZInfo identifiers.
MAPPING = {
"International Date Line West" => "Pacific/Midway",
"Midway Island" => "Pacific/Midway",
@@ -55,6 +62,7 @@ module ActiveSupport
"Newfoundland" => "America/St_Johns",
"Brasilia" => "America/Sao_Paulo",
"Buenos Aires" => "America/Argentina/Buenos_Aires",
+ "Montevideo" => "America/Montevideo",
"Georgetown" => "America/Guyana",
"Greenland" => "America/Godthab",
"Mid-Atlantic" => "Atlantic/South_Georgia",
@@ -143,7 +151,7 @@ module ActiveSupport
"Taipei" => "Asia/Taipei",
"Perth" => "Australia/Perth",
"Irkutsk" => "Asia/Irkutsk",
- "Ulaan Bataar" => "Asia/Ulaanbaatar",
+ "Ulaanbaatar" => "Asia/Ulaanbaatar",
"Seoul" => "Asia/Seoul",
"Osaka" => "Asia/Tokyo",
"Sapporo" => "Asia/Tokyo",
@@ -160,7 +168,7 @@ module ActiveSupport
"Guam" => "Pacific/Guam",
"Port Moresby" => "Pacific/Port_Moresby",
"Magadan" => "Asia/Magadan",
- "Solomon Is." => "Asia/Magadan",
+ "Solomon Is." => "Pacific/Guadalcanal",
"New Caledonia" => "Pacific/Noumea",
"Fiji" => "Pacific/Fiji",
"Kamchatka" => "Asia/Kamchatka",
@@ -169,15 +177,15 @@ module ActiveSupport
"Wellington" => "Pacific/Auckland",
"Nuku'alofa" => "Pacific/Tongatapu",
"Tokelau Is." => "Pacific/Fakaofo",
+ "Chatham Is." => "Pacific/Chatham",
"Samoa" => "Pacific/Apia"
- }.each { |name, zone| name.freeze; zone.freeze }
- MAPPING.freeze
+ }
UTC_OFFSET_WITH_COLON = '%s%02d:%02d'
UTC_OFFSET_WITHOUT_COLON = UTC_OFFSET_WITH_COLON.sub(':', '')
- # Assumes self represents an offset from UTC in seconds (as returned from Time#utc_offset)
- # and turns this into an +HH:MM formatted string. Example:
+ # Assumes self represents an offset from UTC in seconds (as returned from
+ # Time#utc_offset) and turns this into an +HH:MM formatted string.
#
# TimeZone.seconds_to_utc_offset(-21_600) # => "-06:00"
def self.seconds_to_utc_offset(seconds, colon = true)
@@ -194,8 +202,8 @@ module ActiveSupport
# Create a new TimeZone object with the given name and offset. The
# offset is the number of seconds that this time zone is offset from UTC
- # (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).
+ # (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)
@@ -205,6 +213,7 @@ module ActiveSupport
@current_period = nil
end
+ # Returns the offset of this time zone from UTC in seconds.
def utc_offset
if @utc_offset
@utc_offset
@@ -228,10 +237,10 @@ module ActiveSupport
result
end
- # Compare #name and TZInfo identifier to a supplied regexp, returning true
+ # Compare #name and TZInfo identifier to a supplied regexp, returning +true+
# if a match is found.
def =~(re)
- return true if name =~ re || MAPPING[name] =~ re
+ re === name || re === MAPPING[name]
end
# Returns a textual representation of this time zone.
@@ -239,52 +248,65 @@ module ActiveSupport
"(GMT#{formatted_offset}) #{name}"
end
- # Method for creating new ActiveSupport::TimeWithZone instance in time zone of +self+ from given values. Example:
+ # Method for creating new ActiveSupport::TimeWithZone instance in time zone
+ # of +self+ from given values.
#
- # Time.zone = "Hawaii" # => "Hawaii"
- # Time.zone.local(2007, 2, 1, 15, 30, 45) # => Thu, 01 Feb 2007 15:30:45 HST -10:00
+ # Time.zone = 'Hawaii' # => "Hawaii"
+ # Time.zone.local(2007, 2, 1, 15, 30, 45) # => Thu, 01 Feb 2007 15:30:45 HST -10:00
def local(*args)
- time = Time.utc_time(*args)
+ time = Time.utc(*args)
ActiveSupport::TimeWithZone.new(nil, self, time)
end
- # Method for creating new ActiveSupport::TimeWithZone instance in time zone of +self+ from number of seconds since the Unix epoch. Example:
+ # Method for creating new ActiveSupport::TimeWithZone instance in time zone
+ # of +self+ from number of seconds since the Unix epoch.
#
- # Time.zone = "Hawaii" # => "Hawaii"
+ # Time.zone = 'Hawaii' # => "Hawaii"
# Time.utc(2000).to_f # => 946684800.0
# Time.zone.at(946684800.0) # => Fri, 31 Dec 1999 14:00:00 HST -10:00
def at(secs)
- utc = Time.at(secs).utc rescue DateTime.civil(1970).since(secs)
- utc.in_time_zone(self)
+ Time.at(secs).utc.in_time_zone(self)
end
- # Method for creating new ActiveSupport::TimeWithZone instance in time zone of +self+ from parsed string. Example:
+ # Method for creating new ActiveSupport::TimeWithZone instance in time zone
+ # of +self+ from parsed string.
#
- # Time.zone = "Hawaii" # => "Hawaii"
- # Time.zone.parse('1999-12-31 14:00:00') # => Fri, 31 Dec 1999 14:00:00 HST -10:00
+ # Time.zone = 'Hawaii' # => "Hawaii"
+ # Time.zone.parse('1999-12-31 14:00:00') # => Fri, 31 Dec 1999 14:00:00 HST -10:00
#
- # If upper components are missing from the string, they are supplied from TimeZone#now:
+ # If upper components are missing from the string, they are supplied from
+ # TimeZone#now:
#
- # Time.zone.now # => Fri, 31 Dec 1999 14:00:00 HST -10:00
- # Time.zone.parse('22:30:00') # => Fri, 31 Dec 1999 22:30:00 HST -10:00
+ # Time.zone.now # => Fri, 31 Dec 1999 14:00:00 HST -10:00
+ # Time.zone.parse('22:30:00') # => Fri, 31 Dec 1999 22:30:00 HST -10:00
def parse(str, now=now)
- date_parts = Date._parse(str)
- return if date_parts.blank?
- time = Time.parse(str, now) rescue DateTime.parse(str)
- if date_parts[:offset].nil?
- ActiveSupport::TimeWithZone.new(nil, self, time)
+ parts = Date._parse(str, false)
+ return if parts.empty?
+
+ time = Time.new(
+ parts.fetch(:year, now.year),
+ parts.fetch(:mon, now.month),
+ parts.fetch(:mday, now.day),
+ parts.fetch(:hour, 0),
+ parts.fetch(:min, 0),
+ parts.fetch(:sec, 0) + parts.fetch(:sec_fraction, 0),
+ parts.fetch(:offset, 0)
+ )
+
+ if parts[:offset]
+ TimeWithZone.new(time.utc, self)
else
- time.in_time_zone(self)
+ TimeWithZone.new(nil, self, time)
end
end
- # Returns an ActiveSupport::TimeWithZone instance representing the current time
- # in the time zone represented by +self+. Example:
+ # Returns an ActiveSupport::TimeWithZone instance representing the current
+ # time in the time zone represented by +self+.
#
# Time.zone = 'Hawaii' # => "Hawaii"
# Time.zone.now # => Wed, 23 Jan 2008 20:24:27 HST -10:00
def now
- Time.now.utc.in_time_zone(self)
+ time_now.utc.in_time_zone(self)
end
# Return the current date in this time zone.
@@ -292,23 +314,27 @@ module ActiveSupport
tzinfo.now.to_date
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.
+ # 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.
def utc_to_local(time)
tzinfo.utc_to_local(time)
end
- # Adjust the given time to the simultaneous time in UTC. Returns a Time.utc() instance.
+ # Adjust the given time to the simultaneous time in UTC. Returns a
+ # Time.utc() instance.
def local_to_utc(time, dst=true)
tzinfo.local_to_utc(time, dst)
end
- # Available so that TimeZone instances respond like TZInfo::Timezone instances
+ # Available so that TimeZone instances respond like TZInfo::Timezone
+ # instances.
def period_for_utc(time)
tzinfo.period_for_utc(time)
end
- # Available so that TimeZone instances respond like TZInfo::Timezone instances
+ # Available so that TimeZone instances respond like TZInfo::Timezone
+ # instances.
def period_for_local(time, dst=true)
tzinfo.period_for_local(time, dst)
end
@@ -393,5 +419,11 @@ module ActiveSupport
end
end
end
+
+ private
+
+ def time_now
+ Time.now
+ end
end
end
diff --git a/lib/active_support/values/unicode_tables.dat b/lib/active_support/values/unicode_tables.dat
index 4fe0268..2571faa 100644
Binary files a/lib/active_support/values/unicode_tables.dat and b/lib/active_support/values/unicode_tables.dat differ
diff --git a/lib/active_support/version.rb b/lib/active_support/version.rb
index 03b1e51..046494b 100644
--- a/lib/active_support/version.rb
+++ b/lib/active_support/version.rb
@@ -1,10 +1,11 @@
module ActiveSupport
- module VERSION #:nodoc:
- MAJOR = 3
- MINOR = 2
- TINY = 13
- PRE = nil
+ # Returns the version of the currently loaded ActiveSupport as a Gem::Version
+ def self.version
+ Gem::Version.new "4.0.2"
+ end
- STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
+ module VERSION #:nodoc:
+ MAJOR, MINOR, TINY, PRE = ActiveSupport.version.segments
+ STRING = ActiveSupport.version.to_s
end
end
diff --git a/lib/active_support/whiny_nil.rb b/lib/active_support/whiny_nil.rb
deleted file mode 100644
index adf9a35..0000000
--- a/lib/active_support/whiny_nil.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-require 'active_support/deprecation'
-
-# Extensions to +nil+ which allow for more helpful error messages for people who
-# are new to Rails.
-#
-# NilClass#id exists in Ruby 1.8 (though it is deprecated). Since +id+ is a fundamental
-# method of Active Record models NilClass#id is redefined as well to raise a RuntimeError
-# and warn the user. She probably wanted a model database identifier and the 4
-# returned by the original method could result in obscure bugs.
-#
-# The flag <tt>config.whiny_nils</tt> determines whether this feature is enabled.
-# By default it is on in development and test modes, and it is off in production
-# mode.
-class NilClass
- def self.add_whiner(klass)
- ActiveSupport::Deprecation.warn "NilClass.add_whiner is deprecated and this functionality is " \
- "removed from Rails versions as it affects Ruby 1.9 performance.", caller
- end
-
- # Raises a RuntimeError when you attempt to call +id+ on +nil+.
- def id
- raise RuntimeError, "Called id for nil, which would mistakenly be #{object_id} -- if you really wanted the id of nil, use object_id", caller
- end
-end
diff --git a/lib/active_support/xml_mini.rb b/lib/active_support/xml_mini.rb
index a4ac1d7..d082a0a 100644
--- a/lib/active_support/xml_mini.rb
+++ b/lib/active_support/xml_mini.rb
@@ -1,5 +1,5 @@
require 'time'
-require 'active_support/base64'
+require 'base64'
require 'active_support/core_ext/module/delegation'
require 'active_support/core_ext/string/inflections'
@@ -39,8 +39,8 @@ module ActiveSupport
"TrueClass" => "boolean",
"FalseClass" => "boolean",
"Date" => "date",
- "DateTime" => "datetime",
- "Time" => "datetime",
+ "DateTime" => "dateTime",
+ "Time" => "dateTime",
"Array" => "array",
"Hash" => "hash"
} unless defined?(TYPE_NAMES)
@@ -48,7 +48,7 @@ module ActiveSupport
FORMATTING = {
"symbol" => Proc.new { |symbol| symbol.to_s },
"date" => Proc.new { |date| date.to_s(:db) },
- "datetime" => Proc.new { |time| time.xmlschema },
+ "dateTime" => Proc.new { |time| time.xmlschema },
"binary" => Proc.new { |binary| ::Base64.encode64(binary) },
"yaml" => Proc.new { |yaml| yaml.to_yaml }
} unless defined?(FORMATTING)
@@ -76,23 +76,24 @@ module ActiveSupport
)
end
- attr_reader :backend
delegate :parse, :to => :backend
+ def backend
+ current_thread_backend || @backend
+ end
+
def backend=(name)
- if name.is_a?(Module)
- @backend = name
- else
- require "active_support/xml_mini/#{name.to_s.downcase}"
- @backend = ActiveSupport.const_get("XmlMini_#{name}")
- end
+ backend = name && cast_backend_name_to_module(name)
+ self.current_thread_backend = backend if current_thread_backend
+ @backend = backend
end
def with_backend(name)
- old_backend, self.backend = backend, name
+ old_backend = current_thread_backend
+ self.current_thread_backend = name && cast_backend_name_to_module(name)
yield
ensure
- self.backend = old_backend
+ self.current_thread_backend = old_backend
end
def to_tag(key, value, options)
@@ -111,6 +112,7 @@ module ActiveSupport
type_name ||= TYPE_NAMES[value.class.name]
type_name ||= value.class.name if value && !value.respond_to?(:to_str)
type_name = type_name.to_s if type_name
+ type_name = "dateTime" if type_name == "datetime"
key = rename_key(key.to_s, options)
@@ -145,7 +147,7 @@ module ActiveSupport
"#{left}#{middle.tr('_ ', '--')}#{right}"
end
- # TODO: Add support for other encodings
+ # TODO: Add support for other encodings
def _parse_binary(bin, entity) #:nodoc:
case entity['encoding']
when 'base64'
@@ -162,6 +164,25 @@ module ActiveSupport
f.content_type = entity['content_type']
f
end
+
+ private
+
+ def current_thread_backend
+ Thread.current[:xml_mini_backend]
+ end
+
+ def current_thread_backend=(name)
+ Thread.current[:xml_mini_backend] = name && cast_backend_name_to_module(name)
+ end
+
+ def cast_backend_name_to_module(name)
+ if name.is_a?(Module)
+ name
+ else
+ require "active_support/xml_mini/#{name.downcase}"
+ ActiveSupport.const_get("XmlMini_#{name}")
+ end
+ end
end
XmlMini.backend = 'REXML'
diff --git a/lib/active_support/xml_mini/jdom.rb b/lib/active_support/xml_mini/jdom.rb
index 8d23ce4..27c64c4 100644
--- a/lib/active_support/xml_mini/jdom.rb
+++ b/lib/active_support/xml_mini/jdom.rb
@@ -12,7 +12,6 @@ java_import org.xml.sax.InputSource unless defined? InputSource
java_import org.xml.sax.Attributes unless defined? Attributes
java_import org.w3c.dom.Node unless defined? Node
-# = XmlMini JRuby JDOM implementation
module ActiveSupport
module XmlMini_JDOM #:nodoc:
extend self
@@ -77,7 +76,7 @@ module ActiveSupport
child_nodes = element.child_nodes
if child_nodes.length > 0
- for i in 0...child_nodes.length
+ (0...child_nodes.length).each do |i|
child = child_nodes.item(i)
merge_element!(hash, child) unless child.node_type == Node.TEXT_NODE
end
@@ -139,7 +138,7 @@ module ActiveSupport
def get_attributes(element)
attribute_hash = {}
attributes = element.attributes
- for i in 0...attributes.length
+ (0...attributes.length).each do |i|
attribute_hash[CONTENT_KEY] ||= ''
attribute_hash[attributes.item(i).name] = attributes.item(i).value
end
@@ -153,7 +152,7 @@ module ActiveSupport
def texts(element)
texts = []
child_nodes = element.child_nodes
- for i in 0...child_nodes.length
+ (0...child_nodes.length).each do |i|
item = child_nodes.item(i)
if item.node_type == Node.TEXT_NODE
texts << item.get_data
@@ -169,7 +168,7 @@ module ActiveSupport
def empty_content?(element)
text = ''
child_nodes = element.child_nodes
- for i in 0...child_nodes.length
+ (0...child_nodes.length).each do |i|
item = child_nodes.item(i)
if item.node_type == Node.TEXT_NODE
text << item.get_data.strip
diff --git a/lib/active_support/xml_mini/libxml.rb b/lib/active_support/xml_mini/libxml.rb
index 16570c6..47a2824 100644
--- a/lib/active_support/xml_mini/libxml.rb
+++ b/lib/active_support/xml_mini/libxml.rb
@@ -2,7 +2,6 @@ require 'libxml'
require 'active_support/core_ext/object/blank'
require 'stringio'
-# = XmlMini LibXML implementation
module ActiveSupport
module XmlMini_LibXML #:nodoc:
extend self
@@ -38,7 +37,7 @@ module LibXML #:nodoc:
module Node #:nodoc:
CONTENT_ROOT = '__content__'.freeze
- # Convert XML document to hash
+ # Convert XML document to hash.
#
# hash::
# Hash to merge the converted element into.
diff --git a/lib/active_support/xml_mini/libxmlsax.rb b/lib/active_support/xml_mini/libxmlsax.rb
index 2536b1f..70a9529 100644
--- a/lib/active_support/xml_mini/libxmlsax.rb
+++ b/lib/active_support/xml_mini/libxmlsax.rb
@@ -2,9 +2,8 @@ require 'libxml'
require 'active_support/core_ext/object/blank'
require 'stringio'
-# = XmlMini LibXML implementation using a SAX-based parser
module ActiveSupport
- module XmlMini_LibXMLSAX
+ module XmlMini_LibXMLSAX #:nodoc:
extend self
# Class that will build the hash while the XML document
@@ -33,7 +32,7 @@ module ActiveSupport
end
def on_start_element(name, attrs = {})
- new_hash = { CONTENT_KEY => '' }.merge(attrs)
+ new_hash = { CONTENT_KEY => '' }.merge!(attrs)
new_hash[HASH_SIZE_KEY] = new_hash.size + 1
case current_hash[name]
diff --git a/lib/active_support/xml_mini/nokogiri.rb b/lib/active_support/xml_mini/nokogiri.rb
index 04ec9e8..7398d4f 100644
--- a/lib/active_support/xml_mini/nokogiri.rb
+++ b/lib/active_support/xml_mini/nokogiri.rb
@@ -7,7 +7,6 @@ end
require 'active_support/core_ext/object/blank'
require 'stringio'
-# = XmlMini Nokogiri implementation
module ActiveSupport
module XmlMini_Nokogiri #:nodoc:
extend self
@@ -41,7 +40,7 @@ module ActiveSupport
module Node #:nodoc:
CONTENT_ROOT = '__content__'.freeze
- # Convert XML document to hash
+ # Convert XML document to hash.
#
# hash::
# Hash to merge the converted element into.
diff --git a/lib/active_support/xml_mini/nokogirisax.rb b/lib/active_support/xml_mini/nokogirisax.rb
index 93fd3df..be2d6a4 100644
--- a/lib/active_support/xml_mini/nokogirisax.rb
+++ b/lib/active_support/xml_mini/nokogirisax.rb
@@ -7,9 +7,8 @@ end
require 'active_support/core_ext/object/blank'
require 'stringio'
-# = XmlMini Nokogiri implementation using a SAX-based parser
module ActiveSupport
- module XmlMini_NokogiriSAX
+ module XmlMini_NokogiriSAX #:nodoc:
extend self
# Class that will build the hash while the XML document
@@ -39,7 +38,7 @@ module ActiveSupport
end
def start_element(name, attrs = [])
- new_hash = { CONTENT_KEY => '' }.merge(Hash[attrs])
+ new_hash = { CONTENT_KEY => '' }.merge!(Hash[attrs])
new_hash[HASH_SIZE_KEY] = new_hash.size + 1
case current_hash[name]
diff --git a/lib/active_support/xml_mini/rexml.rb b/lib/active_support/xml_mini/rexml.rb
index a13ad10..5c7c78b 100644
--- a/lib/active_support/xml_mini/rexml.rb
+++ b/lib/active_support/xml_mini/rexml.rb
@@ -2,14 +2,13 @@ require 'active_support/core_ext/kernel/reporting'
require 'active_support/core_ext/object/blank'
require 'stringio'
-# = XmlMini ReXML implementation
module ActiveSupport
module XmlMini_REXML #:nodoc:
extend self
CONTENT_KEY = '__content__'.freeze
- # Parse an XML Document string or IO into a simple hash
+ # Parse an XML Document string or IO into a simple hash.
#
# Same as XmlSimple::xml_in but doesn't shoot itself in the foot,
# and uses the defaults from Active Support.
diff --git a/metadata.yml b/metadata.yml
index 8d9722d..5e96a9c 100644
--- a/metadata.yml
+++ b/metadata.yml
@@ -1,43 +1,91 @@
--- !ruby/object:Gem::Specification
name: activesupport
version: !ruby/object:Gem::Version
- version: 3.2.13
+ version: 4.0.2
platform: ruby
authors:
- David Heinemeier Hansson
autorequire:
bindir: bin
cert_chain: []
-date: 2013-03-18 00:00:00.000000000 Z
+date: 2013-12-03 00:00:00.000000000 Z
dependencies:
- !ruby/object:Gem::Dependency
name: i18n
requirement: !ruby/object:Gem::Requirement
requirements:
- - - '='
+ - - "~>"
- !ruby/object:Gem::Version
- version: 0.6.1
+ version: '0.6'
+ - - ">="
+ - !ruby/object:Gem::Version
+ version: 0.6.4
type: :runtime
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
requirements:
- - - '='
+ - - "~>"
+ - !ruby/object:Gem::Version
+ version: '0.6'
+ - - ">="
- !ruby/object:Gem::Version
- version: 0.6.1
+ version: 0.6.4
- !ruby/object:Gem::Dependency
name: multi_json
requirement: !ruby/object:Gem::Requirement
requirements:
- - - ~>
+ - - "~>"
+ - !ruby/object:Gem::Version
+ version: '1.3'
+ type: :runtime
+ prerelease: false
+ version_requirements: !ruby/object:Gem::Requirement
+ requirements:
+ - - "~>"
+ - !ruby/object:Gem::Version
+ version: '1.3'
+- !ruby/object:Gem::Dependency
+ name: tzinfo
+ requirement: !ruby/object:Gem::Requirement
+ requirements:
+ - - "~>"
+ - !ruby/object:Gem::Version
+ version: 0.3.37
+ type: :runtime
+ prerelease: false
+ version_requirements: !ruby/object:Gem::Requirement
+ requirements:
+ - - "~>"
+ - !ruby/object:Gem::Version
+ version: 0.3.37
+- !ruby/object:Gem::Dependency
+ name: minitest
+ requirement: !ruby/object:Gem::Requirement
+ requirements:
+ - - "~>"
+ - !ruby/object:Gem::Version
+ version: '4.2'
+ type: :runtime
+ prerelease: false
+ version_requirements: !ruby/object:Gem::Requirement
+ requirements:
+ - - "~>"
+ - !ruby/object:Gem::Version
+ version: '4.2'
+- !ruby/object:Gem::Dependency
+ name: thread_safe
+ requirement: !ruby/object:Gem::Requirement
+ requirements:
+ - - "~>"
- !ruby/object:Gem::Version
- version: '1.0'
+ version: '0.1'
type: :runtime
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
requirements:
- - - ~>
+ - - "~>"
- !ruby/object:Gem::Version
- version: '1.0'
+ version: '0.1'
description: A toolkit of support libraries and Ruby core extensions extracted from
the Rails framework. Rich support for multibyte strings, internationalization, time
zones, and testing.
@@ -51,7 +99,6 @@ files:
- README.rdoc
- lib/active_support/all.rb
- lib/active_support/backtrace_cleaner.rb
-- lib/active_support/base64.rb
- lib/active_support/basic_object.rb
- lib/active_support/benchmarkable.rb
- lib/active_support/buffered_logger.rb
@@ -64,13 +111,13 @@ files:
- lib/active_support/cache.rb
- lib/active_support/callbacks.rb
- lib/active_support/concern.rb
+- lib/active_support/concurrency/latch.rb
- lib/active_support/configurable.rb
- lib/active_support/core_ext/array/access.rb
- lib/active_support/core_ext/array/conversions.rb
- lib/active_support/core_ext/array/extract_options.rb
- lib/active_support/core_ext/array/grouping.rb
- lib/active_support/core_ext/array/prepend_and_append.rb
-- lib/active_support/core_ext/array/random_access.rb
- lib/active_support/core_ext/array/uniq_by.rb
- lib/active_support/core_ext/array/wrap.rb
- lib/active_support/core_ext/array.rb
@@ -85,21 +132,18 @@ files:
- lib/active_support/core_ext/date/acts_like.rb
- lib/active_support/core_ext/date/calculations.rb
- lib/active_support/core_ext/date/conversions.rb
-- lib/active_support/core_ext/date/freeze.rb
- lib/active_support/core_ext/date/zones.rb
+- lib/active_support/core_ext/date.rb
+- lib/active_support/core_ext/date_and_time/calculations.rb
- lib/active_support/core_ext/date_time/acts_like.rb
- lib/active_support/core_ext/date_time/calculations.rb
- lib/active_support/core_ext/date_time/conversions.rb
- lib/active_support/core_ext/date_time/zones.rb
+- lib/active_support/core_ext/date_time.rb
- lib/active_support/core_ext/enumerable.rb
-- lib/active_support/core_ext/exception.rb
- lib/active_support/core_ext/file/atomic.rb
-- lib/active_support/core_ext/file/path.rb
- lib/active_support/core_ext/file.rb
-- lib/active_support/core_ext/float/rounding.rb
-- lib/active_support/core_ext/float.rb
- lib/active_support/core_ext/hash/conversions.rb
-- lib/active_support/core_ext/hash/deep_dup.rb
- lib/active_support/core_ext/hash/deep_merge.rb
- lib/active_support/core_ext/hash/diff.rb
- lib/active_support/core_ext/hash/except.rb
@@ -112,7 +156,6 @@ files:
- lib/active_support/core_ext/integer/multiple.rb
- lib/active_support/core_ext/integer/time.rb
- lib/active_support/core_ext/integer.rb
-- lib/active_support/core_ext/io.rb
- lib/active_support/core_ext/kernel/agnostics.rb
- lib/active_support/core_ext/kernel/debugger.rb
- lib/active_support/core_ext/kernel/reporting.rb
@@ -120,6 +163,7 @@ files:
- lib/active_support/core_ext/kernel.rb
- lib/active_support/core_ext/load_error.rb
- lib/active_support/core_ext/logger.rb
+- lib/active_support/core_ext/marshal.rb
- lib/active_support/core_ext/module/aliasing.rb
- lib/active_support/core_ext/module/anonymous.rb
- lib/active_support/core_ext/module/attr_internal.rb
@@ -127,19 +171,19 @@ files:
- lib/active_support/core_ext/module/delegation.rb
- lib/active_support/core_ext/module/deprecation.rb
- lib/active_support/core_ext/module/introspection.rb
-- lib/active_support/core_ext/module/method_names.rb
- lib/active_support/core_ext/module/qualified_const.rb
- lib/active_support/core_ext/module/reachable.rb
- lib/active_support/core_ext/module/remove_method.rb
-- lib/active_support/core_ext/module/synchronization.rb
- lib/active_support/core_ext/module.rb
- lib/active_support/core_ext/name_error.rb
- lib/active_support/core_ext/numeric/bytes.rb
+- lib/active_support/core_ext/numeric/conversions.rb
- lib/active_support/core_ext/numeric/time.rb
- lib/active_support/core_ext/numeric.rb
- lib/active_support/core_ext/object/acts_like.rb
- lib/active_support/core_ext/object/blank.rb
- lib/active_support/core_ext/object/conversions.rb
+- lib/active_support/core_ext/object/deep_dup.rb
- lib/active_support/core_ext/object/duplicable.rb
- lib/active_support/core_ext/object/inclusion.rb
- lib/active_support/core_ext/object/instance_variables.rb
@@ -150,42 +194,41 @@ files:
- lib/active_support/core_ext/object/with_options.rb
- lib/active_support/core_ext/object.rb
- lib/active_support/core_ext/proc.rb
-- lib/active_support/core_ext/process/daemon.rb
-- lib/active_support/core_ext/process.rb
-- lib/active_support/core_ext/range/blockless_step.rb
- lib/active_support/core_ext/range/conversions.rb
-- lib/active_support/core_ext/range/cover.rb
+- lib/active_support/core_ext/range/each.rb
- lib/active_support/core_ext/range/include_range.rb
- lib/active_support/core_ext/range/overlaps.rb
- lib/active_support/core_ext/range.rb
- lib/active_support/core_ext/regexp.rb
-- lib/active_support/core_ext/rexml.rb
- lib/active_support/core_ext/string/access.rb
- lib/active_support/core_ext/string/behavior.rb
- lib/active_support/core_ext/string/conversions.rb
- lib/active_support/core_ext/string/encoding.rb
- lib/active_support/core_ext/string/exclude.rb
- lib/active_support/core_ext/string/filters.rb
+- lib/active_support/core_ext/string/indent.rb
- lib/active_support/core_ext/string/inflections.rb
- lib/active_support/core_ext/string/inquiry.rb
-- lib/active_support/core_ext/string/interpolation.rb
- lib/active_support/core_ext/string/multibyte.rb
- lib/active_support/core_ext/string/output_safety.rb
- lib/active_support/core_ext/string/starts_ends_with.rb
- lib/active_support/core_ext/string/strip.rb
-- lib/active_support/core_ext/string/xchar.rb
+- lib/active_support/core_ext/string/zones.rb
- lib/active_support/core_ext/string.rb
+- lib/active_support/core_ext/struct.rb
+- lib/active_support/core_ext/thread.rb
- lib/active_support/core_ext/time/acts_like.rb
- lib/active_support/core_ext/time/calculations.rb
- lib/active_support/core_ext/time/conversions.rb
- lib/active_support/core_ext/time/marshal.rb
-- lib/active_support/core_ext/time/publicize_conversion_methods.rb
- lib/active_support/core_ext/time/zones.rb
+- lib/active_support/core_ext/time.rb
- lib/active_support/core_ext/uri.rb
- lib/active_support/core_ext.rb
- lib/active_support/dependencies/autoload.rb
- lib/active_support/dependencies.rb
- lib/active_support/deprecation/behaviors.rb
+- lib/active_support/deprecation/instance_delegator.rb
- lib/active_support/deprecation/method_wrappers.rb
- lib/active_support/deprecation/proxy_wrappers.rb
- lib/active_support/deprecation/reporting.rb
@@ -207,50 +250,48 @@ files:
- lib/active_support/json/encoding.rb
- lib/active_support/json/variable.rb
- lib/active_support/json.rb
+- lib/active_support/key_generator.rb
- lib/active_support/lazy_load_hooks.rb
- lib/active_support/locale/en.yml
- lib/active_support/log_subscriber/test_helper.rb
- lib/active_support/log_subscriber.rb
-- lib/active_support/memoizable.rb
+- lib/active_support/logger.rb
+- lib/active_support/logger_silence.rb
- lib/active_support/message_encryptor.rb
- lib/active_support/message_verifier.rb
- lib/active_support/multibyte/chars.rb
-- lib/active_support/multibyte/exceptions.rb
- lib/active_support/multibyte/unicode.rb
-- lib/active_support/multibyte/utils.rb
- lib/active_support/multibyte.rb
- lib/active_support/notifications/fanout.rb
- lib/active_support/notifications/instrumenter.rb
- lib/active_support/notifications.rb
+- lib/active_support/number_helper.rb
- lib/active_support/option_merger.rb
- lib/active_support/ordered_hash.rb
- lib/active_support/ordered_options.rb
+- lib/active_support/per_thread_registry.rb
+- lib/active_support/proxy_object.rb
+- lib/active_support/rails.rb
- lib/active_support/railtie.rb
- lib/active_support/rescuable.rb
-- lib/active_support/ruby/shim.rb
- lib/active_support/string_inquirer.rb
+- lib/active_support/subscriber.rb
- lib/active_support/tagged_logging.rb
- lib/active_support/test_case.rb
- lib/active_support/testing/assertions.rb
+- lib/active_support/testing/autorun.rb
+- lib/active_support/testing/constant_lookup.rb
- lib/active_support/testing/declarative.rb
- lib/active_support/testing/deprecation.rb
- lib/active_support/testing/isolation.rb
-- lib/active_support/testing/mochaing.rb
- lib/active_support/testing/pending.rb
-- lib/active_support/testing/performance/jruby.rb
-- lib/active_support/testing/performance/rubinius.rb
-- lib/active_support/testing/performance/ruby/mri.rb
-- lib/active_support/testing/performance/ruby/yarv.rb
-- lib/active_support/testing/performance/ruby.rb
-- lib/active_support/testing/performance.rb
- lib/active_support/testing/setup_and_teardown.rb
-- lib/active_support/time/autoload.rb
+- lib/active_support/testing/tagged_logging.rb
- lib/active_support/time.rb
- lib/active_support/time_with_zone.rb
- lib/active_support/values/time_zone.rb
- lib/active_support/values/unicode_tables.dat
- lib/active_support/version.rb
-- lib/active_support/whiny_nil.rb
- lib/active_support/xml_mini/jdom.rb
- lib/active_support/xml_mini/libxml.rb
- lib/active_support/xml_mini/libxmlsax.rb
@@ -260,22 +301,23 @@ files:
- lib/active_support/xml_mini.rb
- lib/active_support.rb
homepage: http://www.rubyonrails.org
-licenses: []
+licenses:
+- MIT
metadata: {}
post_install_message:
rdoc_options:
-- --encoding
+- "--encoding"
- UTF-8
require_paths:
- lib
required_ruby_version: !ruby/object:Gem::Requirement
requirements:
- - - '>='
+ - - ">="
- !ruby/object:Gem::Version
- version: 1.8.7
+ version: 1.9.3
required_rubygems_version: !ruby/object:Gem::Requirement
requirements:
- - - '>='
+ - - ">="
- !ruby/object:Gem::Version
version: '0'
requirements: []
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-ruby-extras/ruby-activesupport-3.2.git
More information about the Pkg-ruby-extras-commits
mailing list