diff options
Diffstat (limited to 'activesupport')
184 files changed, 2994 insertions, 1564 deletions
diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG index 8485e7d46b..373236ce9a 100644 --- a/activesupport/CHANGELOG +++ b/activesupport/CHANGELOG @@ -1,4 +1,33 @@ -*Rails 3.0.0 [release candidate] (July 26th, 2010)* +*Rails 3.1.0 (unreleased)* + +* LocalCache strategy is now a real middleware class, not an anonymous class +posing for pictures. + +* ActiveSupport::Dependencies::ClassCache class has been introduced for +holding references to reloadable classes. + +* ActiveSupport::Dependencies::Reference has been refactored to take direct +advantage of the new ClassCache. + +* Backports Range#cover? as an alias for Range#include? in Ruby 1.8 [Diego Carrion, fxn] + +* Added weeks_ago and prev_week to Date/DateTime/Time. [Rob Zolkos, fxn] + +* Added before_remove_const callback to ActiveSupport::Dependencies.remove_unloadable_constants! [Andrew White] + +*Rails 3.0.2 (unreleased)* + +* Added before_remove_const callback to ActiveSupport::Dependencies.remove_unloadable_constants! [Andrew White] + +*Rails 3.0.1 (October 15, 2010)* + +* No Changes, just a version bump. + +*Rails 3.0.0 (August 29, 2010)* + +* Implemented String#strip_heredoc. [fxn] + +* Pluggable cache stores: setting config.cache_store = "custom_store" will require 'active_support/cache/custom_store' and look for the CustomStore constant. #5486 [Mike Perham] * Removed Object#returning, Object#tap should be used instead. [Santiago Pastorino] @@ -18,9 +47,6 @@ * Date#since, #ago, #beginning_of_day, #end_of_day, and #xmlschema honor now the user time zone if set. [Geoff Buesing] - -*Rails 3.0.0 [beta 4] (June 8th, 2010)* - * Extracted String#truncate from TextHelper#truncate [DHH] * Ruby 1.9: support UTF-8 case folding. #4595 [Norman Clarke] @@ -73,32 +99,23 @@ * JSON: encode objects that don't have a native JSON representation using to_hash, if available, instead of instance_values (the old fallback) or to_s (other encoders' default). Encode BigDecimal and Regexp encode as strings to conform with other encoders. Try to transcode non-UTF-8 strings. [Jeremy Kemper] - -*Rails 3.0.0 [beta 3] (April 13th, 2010)* - * HashWithIndifferentAccess: remove inherited symbolize_keys! since its keys are always strings. [Santiago Pastorino] * Improve transliteration quality. #4374 [Norman Clarke] * Speed up and add Ruby 1.9 support for ActiveSupport::Multibyte::Chars#tidy_bytes. #4350 [Norman Clarke] - -*Rails 3.0.0 [beta 2] (April 1st, 2010)* - * Reduced load time by deferring configuration of classes using ActiveSupport::on_load(:component_name) [YK] * Rename #metaclass to #singleton_class now that ruby-core has decided [JK] -* New assertions assert_blank and assert_present. #4299 [Juanjo Bazan] +* New assertions assert_blank and assert_present. #4299 [Juanjo Bazan] * Use Object#singleton_class instead of #metaclass. Prefer Ruby's choice. [Jeremy Kemper] * JSON backend for YAJL. Preferred if available. #2666 [Brian Lopez] - -*Rails 3.0.0 [beta 1] (February 4, 2010)* - * Introduce class_attribute to declare inheritable class attributes. Writing an attribute on a subclass behaves just like overriding the superclass reader method. Unifies and replaces most usage of cattr_accessor, class_inheritable_attribute, superclass_delegating_attribute, and extlib_inheritable_attribute. [Jeremy Kemper, Yehuda Katz] * Time#- with a DateTime argument behaves the same as with a Time argument, i.e. returns the difference between self and arg as a Float #3476 [Geoff Buesing] @@ -243,10 +260,10 @@ ActiveSupport.escape_html_entities_in_json from true to false to match previousl * Fix Ruby's Time marshaling bug in pre-1.9 versions of Ruby: utc instances are now correctly unmarshaled with a utc zone instead of the system local zone [#900 state:resolved] [Luca Guidi, Geoff Buesing] * Add Array#in_groups which splits or iterates over the array in specified number of groups. #579. [Adrian Mugnolo] Example: - + a = (1..10).to_a - a.in_groups(3) #=> [[1, 2, 3, 4], [5, 6, 7, nil], [8, 9, 10, nil]] - a.in_groups(3, false) #=> [[1, 2, 3, 4], [5, 6, 7], [8, 9, 10]] + a.in_groups(3) # => [[1, 2, 3, 4], [5, 6, 7, nil], [8, 9, 10, nil]] + a.in_groups(3, false) # => [[1, 2, 3, 4], [5, 6, 7], [8, 9, 10]] * Fix TimeWithZone unmarshaling: coerce unmarshaled Time instances to utc, because Ruby's marshaling of Time instances doesn't respect the zone [Geoff Buesing] @@ -331,7 +348,7 @@ ActiveSupport.escape_html_entities_in_json from true to false to match previousl * TZInfo: Removing unneeded TimezoneProxy class [Geoff Buesing] -* TZInfo: Removing unneeded TimezoneIndexDefinition, since we're not including Indexes::Timezones [Geoff Buesing] +* TZInfo: Removing unneeded TimezoneIndexDefinition, since we're not including Indexes::Timezones [Geoff Buesing] * Removing unnecessary uses_tzinfo helper from tests, given that TZInfo is now bundled [Geoff Buesing] @@ -339,7 +356,7 @@ ActiveSupport.escape_html_entities_in_json from true to false to match previousl * TimeWithZone#marshal_load does zone lookup via Time.get_zone, so that tzinfo/Olson identifiers are handled [Geoff Buesing] -* Time.zone= accepts TZInfo::Timezone instances and Olson identifiers; wraps result in TimeZone instance [Geoff Buesing] +* Time.zone= accepts TZInfo::Timezone instances and Olson identifiers; wraps result in TimeZone instance [Geoff Buesing] * TimeWithZone time conversions don't need to be wrapped in TimeOrDateTime, because TZInfo does this internally [Geoff Buesing] @@ -433,7 +450,7 @@ ActiveSupport.escape_html_entities_in_json from true to false to match previousl * TimeZone#to_s uses UTC rather than GMT; reapplying change that was undone in [8679]. #1689 [Cheah Chu Yeow] -* Time.days_in_month defaults to current year if no year is supplied as argument #10799 [Radar], uses Date.gregorian_leap? to determine leap year, and uses constant lookup to determine days in month [Geoff Buesing] +* Time.days_in_month defaults to current year if no year is supplied as argument #10799 [Radar], uses Date.gregorian_leap? to determine leap year, and uses constant lookup to determine days in month [Geoff Buesing] * Adding Time and DateTime #compare_with_coercion, which layers behavior on #<=> so that any combination of Time, DateTime and ActiveSupport::TimeWithZone instances can be chronologically compared [Geoff Buesing] @@ -510,7 +527,7 @@ ActiveSupport.escape_html_entities_in_json from true to false to match previousl * Introduce a base class for all test cases used by rails applications. ActiveSupport::TestCase [Michael Koziarski] - The intention is to use this to reduce the amount of monkeypatching / overriding that + The intention is to use this to reduce the amount of monkeypatching / overriding that is done to test/unit's classes. * Document Enumerable and Hash #to_json. #9970 [Cheah Chu Yeow] @@ -679,14 +696,14 @@ ActiveSupport.escape_html_entities_in_json from true to false to match previousl <name>David</name> <avatar type="file" name="me.jpg" content_type="image/jpg">R0lGODlhkACZAPUAAM5lcfjrtMQCG=\n</avatar> </person> - + ...becomes: - + attributes = { :person => { :name => "David", :avatar => #<StringIO> } } attributes[:person][:avatar].content_type # => "image/jpg" attributes[:person][:avatar].original_filename # => "me.jpg" attributes[:person][:avatar].read # => binary data of the file - + Which is duck-type compatible with the files that you get when doing multipart uploads through HTML. * Improved multibyte performance by relying less on exception raising #8159 [Blaine] @@ -889,11 +906,11 @@ public for compatibility. [Jeremy Kemper] class Content < ActiveRecord::Base # has a title attribute end - + class Email < ActiveRecord::Base alias_attribute :subject, :title end - + e = Email.find(1) e.title # => "Superstars" e.subject # => "Superstars" @@ -942,7 +959,7 @@ public for compatibility. [Jeremy Kemper] * Enhance Symbol#to_proc so it works with list objects, such as multi-dimensional arrays. Closes #5295 [nov@yo.rim.or.jp]. Example: {1 => "one", 2 => "two", 3 => "three"}.sort_by(&:first).map(&:last) - #=> ["one", "two", "three"] + # => ["one", "two", "three"] * Added Hash.create_from_xml(string) which will create a hash from a XML string and even typecast if possible [David Heinemeier Hansson]. Example: @@ -952,9 +969,9 @@ public for compatibility. [Jeremy Kemper] <created-at type="date">2004-10-10</created-at> </note> EOT - + ...would return: - + { :note => { :title => "This is a note", :created_at => Date.new(2004, 10, 10) } } * Added Jim Weirich's excellent FlexMock class to vendor (Copyright 2003, 2004 by Jim Weirich (jim@weriichhouse.org)) -- it's not automatically required, though, so require 'flexmock' is still necessary [David Heinemeier Hansson] @@ -966,28 +983,28 @@ public for compatibility. [Jeremy Kemper] * Add OrderedHash#values. [Sam Stephenson] * Added Array#to_s(:db) that'll produce a comma-separated list of ids [David Heinemeier Hansson]. Example: - + Purchase.find(:all, :conditions => "product_id IN (#{shops.products.to_s(:db)})" -* Normalize classify's argument to a String so that it plays nice with Symbols. [Marcel Molina Jr.] +* Normalize classify's argument to a String so that it plays nice with Symbols. [Marcel Molina Jr.] * Strip out leading schema name in classify. References #5139. [Michael Schoen] * Remove Enumerable#first_match since break(value) handles the use case well enough. [Nicholas Seckar] Enumerable#first_match was like detect, but instead of returning the matching element, the yielded value returned. For example: - + user_xml = adapters(:from => User, :to => Xml).first_match do |adapter| adapter.adapt @user end - + But this is just as easily done with: - + user_xml = adapters(:from => User, :to => Xml).each do break adapter.adapt(@user) end - -* Make Array#in_groups_of just return the grouped collection if a block isn't given. [Marcel Molina Jr.] + +* Make Array#in_groups_of just return the grouped collection if a block isn't given. [Marcel Molina Jr.] * Don't destroy a HashWithIndifferentAccess if symbolize_keys! or stringify_keys! is called on it. Closes #5076. [Marcel Molina Jr., guy.naor@famundo.com] @@ -999,7 +1016,7 @@ public for compatibility. [Jeremy Kemper] * Replace Ruby's deprecated append_features in favor of included. [Marcel Molina Jr.] -* Allow default options in with_options to be overridden. Closes #4480. [murphy@cYcnus.de] +* Allow default options in with_options to be overridden. Closes #4480. [murphy@cYcnus.de] * Added Module#alias_method_chain [Jamis Buck] @@ -1050,7 +1067,7 @@ approximations and shouldn't be used for critical calculations. [Michael Koziars * Added Hash#to_xml and Array#to_xml that makes it much easier to produce XML from basic structures [David Heinemeier Hansson]. Examples: { :name => "David", :street_name => "Paulina", :age => 26, :moved_on => Date.new(2005, 11, 15) }.to_xml - + ...returns: <person> @@ -1069,7 +1086,7 @@ approximations and shouldn't be used for critical calculations. [Michael Koziars ex. - latest_transcripts.group_by(&:day).each do |day, transcripts| + latest_transcripts.group_by(&:day).each do |day, transcripts| p "#{day} -> #{transcripts.map(&:class) * ', '}" end "2006-03-01 -> Transcript" @@ -1093,7 +1110,7 @@ approximations and shouldn't be used for critical calculations. [Michael Koziars * Add 'around' methods to Logger, to make it easy to log before and after messages for a given block as requested in #3809. [Michael Koziarski] Example: - logger.around_info("Start rendering component (#{options.inspect}): ", + logger.around_info("Start rendering component (#{options.inspect}): ", "\n\nEnd of component rendering") { yield } * Added Time#beginning_of_quarter #3607 [cohen.jeff@gmail.com] @@ -1111,7 +1128,7 @@ approximations and shouldn't be used for critical calculations. [Michael Koziars delegate :free?, :paying?, :to => :subscription delegate :overdue?, :to => "subscription.last_payment" end - + account.free? # => account.subscription.free? account.overdue? # => account.subscription.last_payment.overdue? @@ -1126,7 +1143,7 @@ approximations and shouldn't be used for critical calculations. [Michael Koziars * Add Reloadable::Subclasses which handles the common case where a base class should not be reloaded, but its subclasses should be. [Nicholas Seckar] * Further improvements to reloading code [Nicholas Seckar, Trevor Squires] - + - All classes/modules which include Reloadable can define reloadable? for fine grained control of reloading - Class.remove_class uses Module#parent to access the parent module - Class.remove_class expanded to handle multiple classes in a single call @@ -1138,7 +1155,7 @@ approximations and shouldn't be used for critical calculations. [Michael Koziars class Setting include Reloadable end - + Reloading a class is done by removing its constant which will cause it to be loaded again on the next reference. [David Heinemeier Hansson] * Added auto-loading support for classes in modules, so Conductor::Migration will look for conductor/migration.rb and Conductor::Database::Settings will look for conductor/database/settings.rb [Nicholas Seckar] @@ -1175,7 +1192,7 @@ approximations and shouldn't be used for critical calculations. [Michael Koziars * Add Symbol#to_proc, which allows for, e.g. [:foo, :bar].map(&:to_s). [Marcel Molina Jr.] * Added the following methods [Marcel Molina Jr., Sam Stephenson]: - * Object#copy_instance_variables_from(object) to copy instance variables from one object to another + * Object#copy_instance_variables_from(object) to copy instance variables from one object to another * Object#extended_by to get an instance's included/extended modules * Object#extend_with_included_modules_from(object) to extend an instance with the modules from another instance @@ -1226,7 +1243,7 @@ approximations and shouldn't be used for critical calculations. [Michael Koziars *1.2.0* (October 16th, 2005) -* Update Exception extension to show the first few framework frames in an application trace. [Nicholas Seckar] +* Update Exception extension to show the first few framework frames in an application trace. [Nicholas Seckar] * Added Exception extension to provide support for clean backtraces. [Nicholas Seckar] @@ -1275,9 +1292,9 @@ approximations and shouldn't be used for critical calculations. [Michael Koziars Inflector.inflections do |inflect| inflect.plural /^(ox)$/i, '\1\2en' inflect.singular /^(ox)en/i, '\1' - + inflect.irregular 'octopus', 'octopi' - + inflect.uncountable "equipment" end @@ -1360,9 +1377,9 @@ approximations and shouldn't be used for critical calculations. [Michael Koziars * Added Object#suppress which allows you to make a saner choice around with exceptions to swallow #980. Example: suppress(ZeroDivisionError) { 1/0 } - + ...instead of: - + 1/0 rescue nil # BAD, EVIL, DIRTY. @@ -1376,7 +1393,7 @@ approximations and shouldn't be used for critical calculations. [Michael Koziars values << 'baz' end end - + foo # => ['bar', 'baz'] diff --git a/activesupport/MIT-LICENSE b/activesupport/MIT-LICENSE index cd928b856d..5e8b7a9450 100644 --- a/activesupport/MIT-LICENSE +++ b/activesupport/MIT-LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2005-2010 David Heinemeier Hansson +Copyright (c) 2005-2011 David Heinemeier Hansson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/activesupport/README.rdoc b/activesupport/README.rdoc index 77b8a64304..13ca4b3bf1 100644 --- a/activesupport/README.rdoc +++ b/activesupport/README.rdoc @@ -14,7 +14,7 @@ The latest version of Active Support can be installed with Rubygems: Source code can be downloaded as part of the Rails project on GitHub -* http://github.com/rails/rails/tree/master/activesupport/ +* https://github.com/rails/rails/tree/master/activesupport/ == License diff --git a/activesupport/Rakefile b/activesupport/Rakefile index 8e2683ef89..d117ca6356 100644..100755 --- a/activesupport/Rakefile +++ b/activesupport/Rakefile @@ -1,7 +1,4 @@ -gem 'rdoc', '>= 2.5.9' -require 'rdoc' require 'rake/testtask' -require 'rdoc/task' require 'rake/gempackagetask' task :default => :test @@ -9,6 +6,7 @@ Rake::TestTask.new do |t| t.libs << 'test' t.pattern = 'test/**/*_test.rb' t.warning = true + t.verbose = true end namespace :test do @@ -20,19 +18,6 @@ end # Create compressed packages dist_dirs = [ "lib", "test"] -# Genereate the RDoc documentation - -RDoc::Task.new { |rdoc| - rdoc.rdoc_dir = 'doc' - rdoc.title = "Active Support -- Utility classes and standard library extensions from Rails" - rdoc.options << '-f' << 'horo' - rdoc.options << '--main' << 'README.rdoc' - rdoc.options << '--charset' << 'utf-8' - rdoc.rdoc_files.include('README.rdoc', 'CHANGELOG') - rdoc.rdoc_files.include('lib/active_support.rb') - rdoc.rdoc_files.include('lib/active_support/**/*.rb') -} - spec = eval(File.read('activesupport.gemspec')) Rake::GemPackageTask.new(spec) do |p| diff --git a/activesupport/bin/generate_tables b/activesupport/bin/generate_tables index 51edb59c77..5fefa429df 100644 --- a/activesupport/bin/generate_tables +++ b/activesupport/bin/generate_tables @@ -45,7 +45,7 @@ module ActiveSupport ([0-9]+); # canonical combining class ([A-Z]+); # bidi class (<([A-Z]*)>)? # decomposition type - ((\ ?[0-9A-F]+)*); # decompomposition mapping + ((\ ?[0-9A-F]+)*); # decomposition mapping ([0-9]*); # decimal digit ([0-9]*); # digit ([^;]*); # numeric @@ -70,7 +70,7 @@ module ActiveSupport end def parse_grapheme_break_property(line) - if line =~ /^([0-9A-F\.]+)\s*;\s*([\w]+)\s*#/ + if line =~ /^([0-9A-F.]+)\s*;\s*([\w]+)\s*#/ type = $2.downcase.intern @ucd.boundary[type] ||= [] if $1.include? '..' diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb index ba91e8bba3..6b662ac660 100644 --- a/activesupport/lib/active_support.rb +++ b/activesupport/lib/active_support.rb @@ -35,12 +35,14 @@ module ActiveSupport end require "active_support/dependencies/autoload" +require "active_support/version" module ActiveSupport extend ActiveSupport::Autoload autoload :DescendantsTracker autoload :FileUpdateChecker + autoload :FileWatcher autoload :LogSubscriber autoload :Notifications diff --git a/activesupport/lib/active_support/backtrace_cleaner.rb b/activesupport/lib/active_support/backtrace_cleaner.rb index 8465bc1e10..0e6bc30fa2 100644 --- a/activesupport/lib/active_support/backtrace_cleaner.rb +++ b/activesupport/lib/active_support/backtrace_cleaner.rb @@ -8,7 +8,7 @@ module ActiveSupport # 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. # - # Example: + # ==== Example: # # bc = BacktraceCleaner.new # bc.add_filter { |line| line.gsub(Rails.root, '') } diff --git a/activesupport/lib/active_support/base64.rb b/activesupport/lib/active_support/base64.rb index b8c01628ce..35014cb3d5 100644 --- a/activesupport/lib/active_support/base64.rb +++ b/activesupport/lib/active_support/base64.rb @@ -7,7 +7,7 @@ module ActiveSupport if defined? ::Base64 Base64 = ::Base64 else - # Base64 provides utility methods for encoding and de-coding binary data + # Base64 provides utility methods for encoding and de-coding binary data # using a base 64 representation. A base 64 representation of binary data # consists entirely of printable US-ASCII characters. The Base64 module # is included in Ruby 1.8, but has been removed in Ruby 1.9. @@ -15,7 +15,7 @@ module ActiveSupport # 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") + # ActiveSupport::Base64.encode64("Original unencoded string") # # => "T3JpZ2luYWwgdW5lbmNvZGVkIHN0cmluZw==\n" def self.encode64(data) [data].pack("m") @@ -23,7 +23,7 @@ module ActiveSupport # Decodes a base 64 encoded string to its original representation. # - # ActiveSupport::Base64.decode64("T3JpZ2luYWwgdW5lbmNvZGVkIHN0cmluZw==") + # ActiveSupport::Base64.decode64("T3JpZ2luYWwgdW5lbmNvZGVkIHN0cmluZw==") # # => "Original unencoded string" def self.decode64(data) data.unpack("m").first diff --git a/activesupport/lib/active_support/benchmarkable.rb b/activesupport/lib/active_support/benchmarkable.rb index ee02ecb043..df62c18f41 100644 --- a/activesupport/lib/active_support/benchmarkable.rb +++ b/activesupport/lib/active_support/benchmarkable.rb @@ -3,10 +3,10 @@ require 'active_support/core_ext/hash/keys' module ActiveSupport module Benchmarkable - # Allows you to measure the execution time of a 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 + # 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 %> @@ -23,7 +23,7 @@ module ActiveSupport # <%= lowlevel_files_operation %> # <% end %> # - # Finally, you can pass true as the third argument to silence all log activity + # Finally, you can pass true as the third argument to silence all log activity # inside the block. This is great for boiling down a noisy block to just a single statement: # # <% benchmark "Process data files", :level => :info, :silence => true do %> diff --git a/activesupport/lib/active_support/buffered_logger.rb b/activesupport/lib/active_support/buffered_logger.rb index b861a6f62a..e41731f3e7 100644 --- a/activesupport/lib/active_support/buffered_logger.rb +++ b/activesupport/lib/active_support/buffered_logger.rb @@ -1,3 +1,4 @@ +require 'thread' require 'active_support/core_ext/class/attribute_accessors' module ActiveSupport diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index 30195bdea5..10c457bb1d 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -58,7 +58,14 @@ module ActiveSupport case store when Symbol store_class_name = store.to_s.camelize - store_class = ActiveSupport::Cache.const_get(store_class_name) + 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) when nil ActiveSupport::Cache::MemoryStore.new @@ -200,26 +207,26 @@ module ActiveSupport # # 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 construction in which call all entries will be + # be specified as an option to the construction in which call all entries will be # affected. Or it can be supplied to the +fetch+ or +write+ method for just one entry. # - # cache = ActiveSupport::Cache::MemoryStore.new(:expire_in => 5.minutes) - # cache.write(key, value, :expire_in => 1.minute) # Set a lower value for 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 unver 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 + # 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 + # 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 + # 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. @@ -410,7 +417,7 @@ module ActiveSupport raise NotImplementedError.new("#{self.class.name} does not support decrement") end - # Cleanup the cache by removing expired entries. + # Cleanup the cache by removing expired entries. # # Options are passed to the underlying cache implementation. # @@ -419,7 +426,7 @@ module ActiveSupport raise NotImplementedError.new("#{self.class.name} does not support cleanup") end - # Clear the entire cache. Be careful with this method since it could + # Clear the entire cache. Be careful with this method since it could # affect other processes if shared cache is being used. # # Options are passed to the underlying cache implementation. @@ -473,23 +480,24 @@ module ActiveSupport end end - # Expand key to be a consistent string value. Invoke +cache_key+ if - # object responds to +cache_key+. Otherwise, to_param method will be + # Expand key to be a consistent string value. Invoke +cache_key+ if + # object responds to +cache_key+. Otherwise, to_param method will be # called. If the key is a Hash, then keys will be sorted alphabetically. def expanded_key(key) # :nodoc: - if key.respond_to?(:cache_key) - key = key.cache_key.to_s - elsif key.is_a?(Array) + return key.cache_key.to_s if key.respond_to?(:cache_key) + + case key + when Array if key.size > 1 - key.collect{|element| expanded_key(element)}.to_param + key = key.collect{|element| expanded_key(element)} else - key.first.to_param + key = key.first end - elsif key.is_a?(Hash) - key = key.to_a.sort{|a,b| a.first.to_s <=> b.first.to_s}.collect{|k,v| "#{k}=#{v}"}.to_param - else - key = key.to_param + when Hash + key = key.sort_by { |k,_| k.to_s }.collect{|k,v| "#{k}=#{v}"} end + + key.to_param end # Prefix a key with the namespace. Namespace and key will be delimited with a colon. @@ -582,11 +590,7 @@ module ActiveSupport # Check if the entry is expired. The +expires_in+ parameter can override the # value set when the entry was created. def expired? - if @expires_in && @created_at + @expires_in <= Time.now.to_f - true - else - false - end + @expires_in && @created_at + @expires_in <= Time.now.to_f end # Set a new time when the entry will expire. diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb index 84f6f29572..18182bbb40 100644 --- a/activesupport/lib/active_support/cache/file_store.rb +++ b/activesupport/lib/active_support/cache/file_store.rb @@ -1,5 +1,6 @@ require 'active_support/core_ext/file/atomic' require 'active_support/core_ext/string/conversions' +require 'rack/utils' module ActiveSupport module Cache @@ -11,8 +12,6 @@ module ActiveSupport attr_reader :cache_path DIR_FORMATTER = "%03X" - ESCAPE_FILENAME_CHARS = /[^a-z0-9_.-]/i - UNESCAPE_FILENAME_CHARS = /%[0-9A-F]{2}/ def initialize(cache_path, options = nil) super(options) @@ -136,7 +135,7 @@ module ActiveSupport # Translate a key into a file path. def key_file_path(key) - fname = key.to_s.gsub(ESCAPE_FILENAME_CHARS){|match| "%#{match.ord.to_s(16).upcase}"} + fname = Rack::Utils.escape(key) hash = Zlib.adler32(fname) hash, dir_1 = hash.divmod(0x1000) dir_2 = hash.modulo(0x1000) @@ -156,7 +155,7 @@ 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 - fname.gsub(UNESCAPE_FILENAME_CHARS){|match| $1.ord.to_s(16)} + Rack::Utils.unescape(fname) end # Delete empty directories in the cache. diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb index f32b562368..45263d482f 100644 --- a/activesupport/lib/active_support/cache/mem_cache_store.rb +++ b/activesupport/lib/active_support/cache/mem_cache_store.rb @@ -1,7 +1,7 @@ begin require 'memcache' rescue LoadError => e - $stderr.puts "You don't have memcache installed in your application. Please add it to your Gemfile and run bundle install" + $stderr.puts "You don't have memcache-client installed in your application. Please add it to your Gemfile and run bundle install" raise e end require 'digest/md5' @@ -73,7 +73,7 @@ module ActiveSupport def read_multi(*names) options = names.extract_options! options = merged_options(options) - keys_to_names = names.inject({}){|map, name| map[escape_key(namespaced_key(name, options))] = name; map} + keys_to_names = Hash[names.map{|name| [escape_key(namespaced_key(name, options)), name]}] raw_values = @data.get_multi(keys_to_names.keys, :raw => true) values = {} raw_values.each do |key, value| diff --git a/activesupport/lib/active_support/cache/strategy/local_cache.rb b/activesupport/lib/active_support/cache/strategy/local_cache.rb index 3edba52fc4..0649a058aa 100644 --- a/activesupport/lib/active_support/cache/strategy/local_cache.rb +++ b/activesupport/lib/active_support/cache/strategy/local_cache.rb @@ -16,7 +16,7 @@ module ActiveSupport @data = {} end - # Don't allow synchronizing since it isn't thread safe, + # Don't allow synchronizing since it isn't thread safe, def synchronize # :nodoc: yield end @@ -50,34 +50,39 @@ module ActiveSupport end end - # Middleware class can be inserted as a Rack handler to be local cache for the - # duration of request. - def middleware - @middleware ||= begin - klass = Class.new - klass.class_eval(<<-EOS, __FILE__, __LINE__ + 1) - class << self - def name - "ActiveSupport::Cache::Strategy::LocalCache" - end - alias :to_s :name - 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 - def initialize(app) - @app = app - end + def initialize(name, thread_local_key) + @name = name + @thread_local_key = thread_local_key + @app = nil + end - def call(env) - Thread.current[:#{thread_local_key}] = LocalStore.new - @app.call(env) - ensure - Thread.current[:#{thread_local_key}] = nil - end - EOS - klass + def new(app) + @app = app + self + end + + def call(env) + Thread.current[thread_local_key] = LocalStore.new + @app.call(env) + ensure + Thread.current[thread_local_key] = nil end end + # Middleware class can be inserted as a Rack handler to be local cache for the + # duration of request. + def middleware + @middleware ||= Middleware.new( + "ActiveSupport::Cache::Strategy::LocalCache", + thread_local_key) + end + def clear(options = nil) # :nodoc: local_cache.clear(options) if local_cache super diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index 24e407c253..418102352f 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -1,31 +1,33 @@ +require 'active_support/concern' require 'active_support/descendants_tracker' require 'active_support/core_ext/array/wrap' -require 'active_support/core_ext/class/inheritable_attributes' +require 'active_support/core_ext/class/attribute' require 'active_support/core_ext/kernel/reporting' require 'active_support/core_ext/kernel/singleton_class' module ActiveSupport - # Callbacks are hooks into the lifecycle of an object that allow you to trigger logic - # before or after an alteration of the object state. + # \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 callbacks in your 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+). # - # Example: - # class Storage - # include ActiveSupport::Callbacks - # - # define_callbacks :save - # end + # 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 ConfigStorage < Storage - # set_callback :save, :before, :saving_message - # def saving_message - # puts "saving..." - # end + # ==== Example # - # set_callback :save, :after do |object| - # puts "saved" - # end + # class Record + # include ActiveSupport::Callbacks + # define_callbacks :save # # def save # run_callbacks :save do @@ -34,29 +36,7 @@ module ActiveSupport # end # end # - # config = ConfigStorage.new - # config.save - # - # Output: - # saving... - # - save - # saved - # - # Callbacks from parent classes are inherited. - # - # Example: - # class Storage - # include ActiveSupport::Callbacks - # - # define_callbacks :save - # - # set_callback :save, :before, :prepare - # def prepare - # puts "preparing save" - # end - # end - # - # class ConfigStorage < Storage + # class PersonRecord < Record # set_callback :save, :before, :saving_message # def saving_message # puts "saving..." @@ -65,19 +45,12 @@ module ActiveSupport # set_callback :save, :after do |object| # puts "saved" # end - # - # def save - # run_callbacks :save do - # puts "- save" - # end - # end # end # - # config = ConfigStorage.new - # config.save + # person = PersonRecord.new + # person.save # # Output: - # preparing save # saving... # - save # saved @@ -89,11 +62,25 @@ module ActiveSupport extend ActiveSupport::DescendantsTracker end + # 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. + # + # 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) end - class Callback + class Callback #:nodoc:# @@_callback_sequence = 0 attr_accessor :chain, :filter, :kind, :options, :per_key, :klass, :raw_filter @@ -178,49 +165,52 @@ module ActiveSupport # options[0] is the compiled form of supplied conditions # options[1] is the "end" for the conditional # - if @kind == :before || @kind == :around - if @kind == :before - # if condition # before_save :filter_name, :if => :condition - # filter_name - # end - filter = <<-RUBY_EVAL - unless halted - result = #{@filter} - halted = (#{chain.config[:terminator]}) - end - RUBY_EVAL - - [@compiled_options[0], filter, @compiled_options[1]].compact.join("\n") - else - # 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) - #{@compiled_options[0] || "if true"} && !halted - #{@filter} do - yield self - end - else + case @kind + when :before + # if condition # before_save :filter_name, :if => :condition + # filter_name + # end + filter = <<-RUBY_EVAL + unless halted + # This double assignment is to prevent warnings in 1.9.3. I would + # remove the `result` variable, but apparently some other + # generated code is depending on this variable being set sometimes + # and sometimes not. + result = result = #{@filter} + halted = (#{chain.config[:terminator]}) + end + RUBY_EVAL + + [@compiled_options[0], filter, @compiled_options[1]].compact.join("\n") + 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) + #{@compiled_options[0] || "if true"} && !halted + #{@filter} do yield self end + else + yield self end - RUBY_EVAL - "#{name}(halted) do" - end + end + RUBY_EVAL + "#{name}(halted) do" end end @@ -229,15 +219,17 @@ module ActiveSupport def end(key=nil, object=nil) return if key && !object.send("_one_time_conditions_valid_#{@callback_id}?") - if @kind == :around || @kind == :after + case @kind + when :after # if condition # after_save :filter_name, :if => :condition # filter_name # end - if @kind == :after - [@compiled_options[0], @filter, @compiled_options[1]].compact.join("\n") - else - "end" + [@compiled_options[0], @filter, @compiled_options[1]].compact.join("\n") + when :around + <<-RUBY_EVAL + value end + RUBY_EVAL end end @@ -330,7 +322,7 @@ module ActiveSupport end # An Array with a compile method - class CallbackChain < Array + class CallbackChain < Array #:nodoc:# attr_reader :name, :config def initialize(name, config) @@ -375,20 +367,9 @@ module ActiveSupport end module ClassMethods - # Make the run_callbacks :save method. The generated method takes - # a block that it'll yield to. It'll call the before and around filters - # in order, yield the block, and then run the after filters. - # - # run_callbacks :save do - # save - # end - # - # The run_callbacks :save method can optionally take a key, which - # will be used to compile an optimized callback method for each - # key. See #define_callbacks for more information. - # + # Generate the internal runner method called by +run_callbacks+. def __define_runner(symbol) #:nodoc: - body = send("_#{symbol}_callbacks").compile(nil) + body = send("_#{symbol}_callbacks").compile silence_warnings do undef_method "_run_#{symbol}_callbacks" if method_defined?("_run_#{symbol}_callbacks") @@ -435,21 +416,52 @@ module ActiveSupport options = filters.last.is_a?(Hash) ? filters.pop : {} filters.unshift(block) if block - ([self] + ActiveSupport::DescendantsTracker.descendants(self)).each do |target| + ([self] + ActiveSupport::DescendantsTracker.descendants(self)).reverse.each do |target| chain = target.send("_#{name}_callbacks") - yield chain, type, filters, options + yield target, chain.dup, type, filters, options target.__define_runner(name) end end - # Set callbacks for a previously defined callback. + # Install a callback for the given event. # - # Syntax: # set_callback :save, :before, :before_meth # set_callback :save, :after, :after_meth, :if => :condition - # set_callback :save, :around, lambda { |r| stuff; yield; stuff } + # set_callback :save, :around, lambda { |r| stuff; result = yield; stuff } + # + # The second arguments indicates whether the callback is to be run +:before+, + # +:after+, or +:around+ the event. If omitted, +:before+ is assumed. This + # means the first example above can also be written as: + # + # 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+. # - # Use skip_callback to skip any defined one. + # 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. + # + # 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, @@ -459,12 +471,12 @@ module ActiveSupport # # becomes # - # dispatch_callback :before, :authenticate, :per_key => {:unless => proc {|c| c.action_name == "index"}} + # 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. + # 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(:dispatch, action_name) { ... dispatch stuff ... } + # 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 @@ -473,27 +485,30 @@ module ActiveSupport def set_callback(name, *filter_list, &block) mapped = nil - __update_callbacks(name, filter_list, block) do |chain, type, filters, options| + __update_callbacks(name, filter_list, block) do |target, chain, type, filters, options| mapped ||= filters.map do |filter| Callback.new(chain, filter, type, options.dup, self) end filters.each do |filter| - chain.delete_if {|c| c.matches?(type, filter) } + chain.delete_if {|c| c.matches?(type, filter) } end - options[:prepend] ? chain.unshift(*mapped) : chain.push(*mapped) + options[:prepend] ? chain.unshift(*(mapped.reverse)) : chain.push(*mapped) + + target.send("_#{name}_callbacks=", chain) end end - # Skip a previously defined callback. + # 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 } # end # def skip_callback(name, *filter_list, &block) - __update_callbacks(name, filter_list, block) do |chain, type, filters, options| + __update_callbacks(name, filter_list, block) do |target, chain, type, filters, options| filters.each do |filter| filter = chain.find {|c| c.matches?(type, filter) } @@ -505,93 +520,98 @@ module ActiveSupport chain.delete(filter) end + target.send("_#{name}_callbacks=", chain) end end - # Reset callbacks for a given type. + # Remove all set callbacks for the given event. # def reset_callbacks(symbol) callbacks = send("_#{symbol}_callbacks") ActiveSupport::DescendantsTracker.descendants(self).each do |target| - chain = target.send("_#{symbol}_callbacks") + chain = target.send("_#{symbol}_callbacks").dup callbacks.each { |c| chain.delete(c) } + target.send("_#{symbol}_callbacks=", chain) target.__define_runner(symbol) end - callbacks.clear + self.send("_#{symbol}_callbacks=", callbacks.dup.clear) + __define_runner(symbol) end - # Defines callbacks types: + # Define sets of events in the object lifecycle that support callbacks. # # define_callbacks :validate + # define_callbacks :initialize, :save, :destroy # - # This macro accepts the following options: + # ===== Options # - # * <tt>:terminator</tt> - Indicates when a before filter is considered - # to halted. This is a string to be eval'ed and has the result of the - # very filter 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 <tt>result</tt> variable. # - # define_callbacks :validate, :terminator => "result == false" + # define_callbacks :validate, :terminator => "result == false" # - # In the example above, if any before validate callbacks returns +false+, - # other callbacks are not executed. Defaults to "false", meaning no value - # halts the chain. + # In this example, if any before validate callbacks returns +false+, + # 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. Set this option to - # true to change this behavior. + # 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>:scope</tt> - Indicates which methods should be executed when a class - # is given as callback. Defaults to <tt>[:kind]</tt>. + # * <tt>:scope</tt> - Indicates which methods should be executed when an object + # is used as a callback. # - # class Audit - # def before(caller) - # puts 'Audit: before is called' - # end + # class Audit + # def before(caller) + # puts 'Audit: before is called' + # end # - # def before_save(caller) - # puts 'Audit: before_save is called' - # end - # end + # def before_save(caller) + # puts 'Audit: before_save is called' + # end + # end # - # class Account - # include ActiveSupport::Callbacks + # class Account + # include ActiveSupport::Callbacks # - # define_callbacks :save - # set_callback :save, :before, Audit.new + # define_callbacks :save + # set_callback :save, :before, Audit.new # - # def save - # run_callbacks :save do - # puts 'save in main' - # end - # end - # end + # def save + # run_callbacks :save do + # puts 'save in main' + # end + # 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 + # A declaration like # - # define_callbacks :save, :scope => [:name] + # define_callbacks :save, :scope => [:name] # - # would call <tt>Audit#save</tt>. + # would call <tt>Audit#save</tt>. # def define_callbacks(*callbacks) config = callbacks.last.is_a?(Hash) ? callbacks.pop : {} callbacks.each do |callback| - extlib_inheritable_reader("_#{callback}_callbacks") do - CallbackChain.new(callback, config) - end + class_attribute "_#{callback}_callbacks" + send("_#{callback}_callbacks=", CallbackChain.new(callback, config)) __define_runner(callback) end end diff --git a/activesupport/lib/active_support/concern.rb b/activesupport/lib/active_support/concern.rb index 2d87e8d0e5..81fb859334 100644 --- a/activesupport/lib/active_support/concern.rb +++ b/activesupport/lib/active_support/concern.rb @@ -1,39 +1,109 @@ -# A typical module looks like this -# -# module M -# def self.included(base) -# base.send(:extend, ClassMethods) -# base.send(:include, InstanceMethods) -# scope :foo, :conditions => { :created_at => nil } -# end -# -# module ClassMethods -# def cm; puts 'I am a class method'; end -# end -# -# module InstanceMethods -# def im; puts 'I am an instance method'; end -# end -# end -# -# By using <tt>ActiveSupport::Concern</tt> the above module could instead be written as: -# -# module M -# extend ActiveSupport::Concern -# -# included do -# scope :foo, :conditions => { :created_at => nil } -# end -# -# module ClassMethods -# def cm; puts 'I am a class method'; end -# end -# -# module InstanceMethods -# def im; puts 'I am an instance method'; end -# end -# end module ActiveSupport + # A typical module looks like this: + # + # module M + # def self.included(base) + # base.extend ClassMethods + # base.send(:include, InstanceMethods) + # scope :disabled, where(:disabled => true) + # end + # + # module ClassMethods + # ... + # end + # + # module InstanceMethods + # ... + # end + # end + # + # By using <tt>ActiveSupport::Concern</tt> the above module could instead be written as: + # + # require 'active_support/concern' + # + # module M + # extend ActiveSupport::Concern + # + # included do + # scope :disabled, where(:disabled => true) + # end + # + # module ClassMethods + # ... + # end + # + # module InstanceMethods + # ... + # 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: + # + # module Foo + # def self.included(base) + # base.class_eval do + # def self.method_injected_by_foo + # ... + # end + # end + # end + # end + # + # module Bar + # def self.included(base) + # base.method_injected_by_foo + # end + # end + # + # class Host + # include Foo # We need to include this dependency for Bar + # 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+: + # + # module Bar + # include Foo + # def self.included(base) + # base.method_injected_by_foo + # end + # end + # + # class Host + # 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: + # + # require 'active_support/concern' + # + # module Foo + # extend ActiveSupport::Concern + # included do + # class_eval do + # def self.method_injected_by_foo + # ... + # end + # end + # end + # end + # + # module Bar + # extend ActiveSupport::Concern + # include Foo + # + # included do + # self.method_injected_by_foo + # end + # end + # + # class Host + # include Bar # works, Bar takes care now of its dependencies + # end + # module Concern def self.extended(base) base.instance_variable_set("@_dependencies", []) diff --git a/activesupport/lib/active_support/configurable.rb b/activesupport/lib/active_support/configurable.rb index f562e17c75..8c56a21ef7 100644 --- a/activesupport/lib/active_support/configurable.rb +++ b/activesupport/lib/active_support/configurable.rb @@ -4,18 +4,52 @@ require 'active_support/core_ext/kernel/singleton_class' require 'active_support/core_ext/module/delegation' module ActiveSupport + # Configurable provides a <tt>config</tt> method to store and retrieve + # configuration options as an <tt>OrderedHash</tt>. module Configurable extend ActiveSupport::Concern + class Configuration < ActiveSupport::InheritableOptions + def compile_methods! + self.class.compile_methods!(keys.reject {|key| respond_to?(key)}) + end + + # compiles reader methods so we don't have to go through method_missing + def self.compile_methods!(keys) + keys.each do |key| + class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{key}; _get(#{key.inspect}); end + RUBY + end + end + end + module ClassMethods def config - @config ||= ActiveSupport::InheritableOptions.new(superclass.respond_to?(:config) ? superclass.config : {}) + @_config ||= if respond_to?(:superclass) && superclass.respond_to?(:config) + superclass.config.inheritable_copy + else + # create a new "anonymous" class that will host the compiled reader methods + Class.new(Configuration).new + end end def configure 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. + # + # class User + # include ActiveSupport::Configurable + # config_accessor :allowed_access + # end + # + # user = User.new + # user.allowed_access = true + # user.allowed_access # => true + # def config_accessor(*names) names.each do |name| code, line = <<-RUBY, __LINE__ + 1 @@ -29,8 +63,25 @@ module ActiveSupport end end + # Reads and writes attributes from a configuration <tt>OrderedHash</tt>. + # + # require 'active_support/configurable' + # + # class User + # include ActiveSupport::Configurable + # end + # + # user = User.new + # + # user.config.allowed_access = true + # user.config.level = 1 + # + # user.config.allowed_access # => true + # user.config.level # => 1 + # def config - @config ||= ActiveSupport::InheritableOptions.new(self.class.config) + @_config ||= self.class.config.inheritable_copy end end -end
\ No newline at end of file +end + diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb index 7585137aca..baefa9cae4 100644 --- a/activesupport/lib/active_support/core_ext/array/conversions.rb +++ b/activesupport/lib/active_support/core_ext/array/conversions.rb @@ -26,7 +26,7 @@ class Array when 0 "" when 1 - self[0].to_s + self[0].to_s.dup when 2 "#{self[0]}#{options[:two_words_connector]}#{self[1]}" else diff --git a/activesupport/lib/active_support/core_ext/array/random_access.rb b/activesupport/lib/active_support/core_ext/array/random_access.rb index edac7278bc..ab1fa7cd5b 100644 --- a/activesupport/lib/active_support/core_ext/array/random_access.rb +++ b/activesupport/lib/active_support/core_ext/array/random_access.rb @@ -1,10 +1,10 @@ class Array - # Backport of Array#sample based on Marc-Andre Lafortune's http://github.com/marcandre/backports/ - # Returns a random element or +n+ random elements from the 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, 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 # => 4 + # [1,2,3,4,5,6].sample(3) # => [2, 4, 5] # [].sample # => nil # [].sample(3) # => [] def sample(n=nil) @@ -24,4 +24,4 @@ class Array result[n..size] = [] result end unless method_defined? :sample -end
\ No newline at end of file +end diff --git a/activesupport/lib/active_support/core_ext/array/wrap.rb b/activesupport/lib/active_support/core_ext/array/wrap.rb index 06b2acd662..7fabae3138 100644 --- a/activesupport/lib/active_support/core_ext/array/wrap.rb +++ b/activesupport/lib/active_support/core_ext/array/wrap.rb @@ -10,7 +10,7 @@ class Array # 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> diff --git a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb index f7f03f4d95..080604147d 100644 --- a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb +++ b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb @@ -1,23 +1,38 @@ require 'bigdecimal' + +begin + require 'psych' +rescue LoadError +end + require 'yaml' class BigDecimal YAML_TAG = 'tag:yaml.org,2002:float' YAML_MAPPING = { 'Infinity' => '.Inf', '-Infinity' => '-.Inf', 'NaN' => '.NaN' } - yaml_as YAML_TAG - # 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) + end + + def to_d + self + end + DEFAULT_STRING_FORMAT = 'F' def to_formatted_s(format = DEFAULT_STRING_FORMAT) _original_to_s(format) diff --git a/activesupport/lib/active_support/core_ext/cgi.rb b/activesupport/lib/active_support/core_ext/cgi.rb deleted file mode 100644 index 7279a3d4da..0000000000 --- a/activesupport/lib/active_support/core_ext/cgi.rb +++ /dev/null @@ -1 +0,0 @@ -require 'active_support/core_ext/cgi/escape_skipping_slashes' diff --git a/activesupport/lib/active_support/core_ext/cgi/escape_skipping_slashes.rb b/activesupport/lib/active_support/core_ext/cgi/escape_skipping_slashes.rb deleted file mode 100644 index d3c3575748..0000000000 --- a/activesupport/lib/active_support/core_ext/cgi/escape_skipping_slashes.rb +++ /dev/null @@ -1,19 +0,0 @@ -require 'cgi' - -class CGI #:nodoc: - if RUBY_VERSION >= '1.9' - def self.escape_skipping_slashes(str) - str = str.join('/') if str.respond_to? :join - str.gsub(/([^ \/a-zA-Z0-9_.-])/n) do - "%#{$1.unpack('H2' * $1.bytesize).join('%').upcase}" - end.tr(' ', '+') - end - else - def self.escape_skipping_slashes(str) - str = str.join('/') if str.respond_to? :join - str.gsub(/([^ \/a-zA-Z0-9_.-])/n) do - "%#{$1.unpack('H2').first.upcase}" - end.tr(' ', '+') - end - end -end diff --git a/activesupport/lib/active_support/core_ext/class/attribute.rb b/activesupport/lib/active_support/core_ext/class/attribute.rb index bfa57fe1f7..5414b3a18f 100644 --- a/activesupport/lib/active_support/core_ext/class/attribute.rb +++ b/activesupport/lib/active_support/core_ext/class/attribute.rb @@ -29,19 +29,19 @@ class Class # In such cases, you don't want to do changes in places but use setters: # # Base.setting = [] - # Base.setting #=> [] - # Subclass.setting #=> [] + # Base.setting # => [] + # Subclass.setting # => [] # # # Appending in child changes both parent and child because it is the same object: # Subclass.setting << :foo - # Base.setting #=> [:foo] - # Subclass.setting #=> [:foo] + # Base.setting # => [:foo] + # Subclass.setting # => [:foo] # # # Use setters to not propagate changes: # Base.setting = [] # Subclass.setting += [:foo] - # Base.setting #=> [] - # Subclass.setting #=> [:foo] + # Base.setting # => [] + # Subclass.setting # => [:foo] # # For convenience, a query method is defined as well: # @@ -72,10 +72,21 @@ class Class 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} + end + end + end + val end + remove_method :#{name} if method_defined?(:#{name}) def #{name} - defined?(@#{name}) ? @#{name} : singleton_class.#{name} + defined?(@#{name}) ? @#{name} : self.class.#{name} end def #{name}? @@ -86,4 +97,15 @@ class Class attr_writer name if instance_writer end end + + private + def singleton_class? + # in case somebody is crazy enough to overwrite allocate + allocate = Class.instance_method(:allocate) + # object.class always points to a real (non-singleton) class + allocate.bind(self).call.class != self + rescue TypeError + # MRI/YARV/JRuby all disallow creating new instances of a singleton class + true + end end diff --git a/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb index 4e35b1b488..a903735acf 100644 --- a/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb +++ b/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb @@ -12,8 +12,8 @@ require 'active_support/core_ext/array/extract_options' # end # # Person.hair_colors = [:brown, :black, :blonde, :red] -# Person.hair_colors #=> [:brown, :black, :blonde, :red] -# Person.new.hair_colors #=> [:brown, :black, :blonde, :red] +# Person.hair_colors # => [:brown, :black, :blonde, :red] +# Person.new.hair_colors # => [:brown, :black, :blonde, :red] # # To opt out of the instance writer method, pass :instance_writer => false. # To opt out of the instance reader method, pass :instance_reader => false. diff --git a/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb b/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb index e844cf50d1..ca3db2349e 100644 --- a/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb +++ b/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb @@ -1,13 +1,15 @@ require 'active_support/core_ext/object/duplicable' require 'active_support/core_ext/array/extract_options' +require 'active_support/deprecation' # Retained for backward compatibility. Methods are now included in Class. module ClassInheritableAttributes # :nodoc: + DEPRECATION_WARNING_MESSAGE = "class_inheritable_attribute is deprecated, please use class_attribute method instead. Notice their behavior are slightly different, so refer to class_attribute documentation first" end -# It is recommend to use <tt>class_attribute</tt> over methods defined in this file. Please +# It is recommended to use <tt>class_attribute</tt> over methods defined in this file. Please # refer to documentation for <tt>class_attribute</tt> for more information. Officially it is not -# deprected but <tt>class_attribute</tt> is faster. +# deprecated but <tt>class_attribute</tt> is faster. # # Allows attributes to be shared within an inheritance hierarchy. Each descendant gets a copy of # their parents' attributes, instead of just a pointer to the same. This means that the child can add elements @@ -22,8 +24,8 @@ end # end # # Person.hair_colors = [:brown, :black, :blonde, :red] -# Person.hair_colors #=> [:brown, :black, :blonde, :red] -# Person.new.hair_colors #=> [:brown, :black, :blonde, :red] +# Person.hair_colors # => [:brown, :black, :blonde, :red] +# Person.new.hair_colors # => [:brown, :black, :blonde, :red] # # To opt out of the instance writer method, pass :instance_writer => false. # To opt out of the instance reader method, pass :instance_reader => false. @@ -36,6 +38,7 @@ end # Person.new.hair_colors # => NoMethodError class Class # :nodoc: def class_inheritable_reader(*syms) + ActiveSupport::Deprecation.warn ClassInheritableAttributes::DEPRECATION_WARNING_MESSAGE options = syms.extract_options! syms.each do |sym| next if sym.is_a?(Hash) @@ -54,6 +57,7 @@ class Class # :nodoc: end def class_inheritable_writer(*syms) + ActiveSupport::Deprecation.warn ClassInheritableAttributes::DEPRECATION_WARNING_MESSAGE options = syms.extract_options! syms.each do |sym| class_eval(<<-EOS, __FILE__, __LINE__ + 1) @@ -71,6 +75,7 @@ class Class # :nodoc: end def class_inheritable_array_writer(*syms) + ActiveSupport::Deprecation.warn ClassInheritableAttributes::DEPRECATION_WARNING_MESSAGE options = syms.extract_options! syms.each do |sym| class_eval(<<-EOS, __FILE__, __LINE__ + 1) @@ -88,6 +93,7 @@ class Class # :nodoc: end def class_inheritable_hash_writer(*syms) + ActiveSupport::Deprecation.warn ClassInheritableAttributes::DEPRECATION_WARNING_MESSAGE options = syms.extract_options! syms.each do |sym| class_eval(<<-EOS, __FILE__, __LINE__ + 1) @@ -124,6 +130,7 @@ class Class # :nodoc: end def write_inheritable_attribute(key, value) + ActiveSupport::Deprecation.warn ClassInheritableAttributes::DEPRECATION_WARNING_MESSAGE if inheritable_attributes.equal?(EMPTY_INHERITABLE_ATTRIBUTES) @inheritable_attributes = {} end @@ -141,10 +148,12 @@ class Class # :nodoc: end def read_inheritable_attribute(key) + ActiveSupport::Deprecation.warn ClassInheritableAttributes::DEPRECATION_WARNING_MESSAGE inheritable_attributes[key] end def reset_inheritable_attributes + ActiveSupport::Deprecation.warn ClassInheritableAttributes::DEPRECATION_WARNING_MESSAGE @inheritable_attributes = EMPTY_INHERITABLE_ATTRIBUTES end @@ -158,9 +167,9 @@ class Class # :nodoc: if inheritable_attributes.equal?(EMPTY_INHERITABLE_ATTRIBUTES) new_inheritable_attributes = EMPTY_INHERITABLE_ATTRIBUTES else - new_inheritable_attributes = inheritable_attributes.inject({}) do |memo, (key, value)| - memo.update(key => value.duplicable? ? value.dup : value) - end + new_inheritable_attributes = Hash[inheritable_attributes.map do |(key, value)| + [key, value.duplicable? ? value.dup : value] + end] end child.instance_variable_set('@inheritable_attributes', new_inheritable_attributes) @@ -169,86 +178,3 @@ class Class # :nodoc: alias inherited_without_inheritable_attributes inherited alias inherited inherited_with_inheritable_attributes end - -class Class - # Defines class-level inheritable attribute reader. Attributes are available to subclasses, - # each subclass has a copy of parent's attribute. - # - # @param *syms<Array[#to_s]> Array of attributes to define inheritable reader for. - # @return <Array[#to_s]> Array of attributes converted into inheritable_readers. - # - # @api public - # - # @todo Do we want to block instance_reader via :instance_reader => false - # @todo It would be preferable that we do something with a Hash passed in - # (error out or do the same as other methods above) instead of silently - # moving on). In particular, this makes the return value of this function - # less useful. - def extlib_inheritable_reader(*ivars, &block) - options = ivars.extract_options! - - ivars.each do |ivar| - self.class_eval <<-RUBY, __FILE__, __LINE__ + 1 - def self.#{ivar} - return @#{ivar} if self.object_id == #{self.object_id} || defined?(@#{ivar}) - ivar = superclass.#{ivar} - return nil if ivar.nil? && !#{self}.instance_variable_defined?("@#{ivar}") - @#{ivar} = ivar.duplicable? ? ivar.dup : ivar - end - RUBY - unless options[:instance_reader] == false - self.class_eval <<-RUBY, __FILE__, __LINE__ + 1 - def #{ivar} - self.class.#{ivar} - end - RUBY - end - instance_variable_set(:"@#{ivar}", yield) if block_given? - end - end - - # Defines class-level inheritable attribute writer. Attributes are available to subclasses, - # each subclass has a copy of parent's attribute. - # - # @param *syms<Array[*#to_s, Hash{:instance_writer => Boolean}]> Array of attributes to - # define inheritable writer for. - # @option syms :instance_writer<Boolean> if true, instance-level inheritable attribute writer is defined. - # @return <Array[#to_s]> An Array of the attributes that were made into inheritable writers. - # - # @api public - # - # @todo We need a style for class_eval <<-HEREDOC. I'd like to make it - # class_eval(<<-RUBY, __FILE__, __LINE__), but we should codify it somewhere. - def extlib_inheritable_writer(*ivars) - options = ivars.extract_options! - - ivars.each do |ivar| - self.class_eval <<-RUBY, __FILE__, __LINE__ + 1 - def self.#{ivar}=(obj) - @#{ivar} = obj - end - RUBY - unless options[:instance_writer] == false - self.class_eval <<-RUBY, __FILE__, __LINE__ + 1 - def #{ivar}=(obj) self.class.#{ivar} = obj end - RUBY - end - - self.send("#{ivar}=", yield) if block_given? - end - end - - # Defines class-level inheritable attribute accessor. Attributes are available to subclasses, - # each subclass has a copy of parent's attribute. - # - # @param *syms<Array[*#to_s, Hash{:instance_writer => Boolean}]> Array of attributes to - # define inheritable accessor for. - # @option syms :instance_writer<Boolean> if true, instance-level inheritable attribute writer is defined. - # @return <Array[#to_s]> An Array of attributes turned into inheritable accessors. - # - # @api public - def extlib_inheritable_accessor(*syms, &block) - extlib_inheritable_reader(*syms) - extlib_inheritable_writer(*syms, &block) - end -end diff --git a/activesupport/lib/active_support/core_ext/class/subclasses.rb b/activesupport/lib/active_support/core_ext/class/subclasses.rb index 3e5d1a2a42..46e9daaa8f 100644 --- a/activesupport/lib/active_support/core_ext/class/subclasses.rb +++ b/activesupport/lib/active_support/core_ext/class/subclasses.rb @@ -2,49 +2,35 @@ require 'active_support/core_ext/module/anonymous' require 'active_support/core_ext/module/reachable' class Class #:nodoc: - # Rubinius - if defined?(Class.__subclasses__) - alias :subclasses :__subclasses__ + begin + ObjectSpace.each_object(Class.new) {} def descendants descendants = [] - __subclasses__.each do |k| - descendants << k - descendants.concat k.descendants + ObjectSpace.each_object(class << self; self; end) do |k| + descendants.unshift k unless k == self end descendants end - else # MRI - begin - ObjectSpace.each_object(Class.new) {} - - def descendants - descendants = [] - ObjectSpace.each_object(class << self; self; end) do |k| - descendants.unshift k unless k == self - end - descendants - end - rescue StandardError # JRuby - def descendants - descendants = [] - ObjectSpace.each_object(Class) do |k| - descendants.unshift k if k < self - end - descendants.uniq! - descendants + rescue StandardError # JRuby + def descendants + descendants = [] + ObjectSpace.each_object(Class) do |k| + descendants.unshift k if k < self end + descendants.uniq! + descendants end + end - # Returns an array with the direct children of +self+. - # - # Integer.subclasses # => [Bignum, Fixnum] - def subclasses - subclasses, chain = [], descendants - chain.each do |k| - subclasses << k unless chain.any? { |c| c > k } - end - subclasses + # Returns an array with the direct children of +self+. + # + # Integer.subclasses # => [Bignum, Fixnum] + def subclasses + subclasses, chain = [], descendants + chain.each do |k| + subclasses << k unless chain.any? { |c| c > k } end + subclasses end end diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb index c5b54318ce..724e076407 100644 --- a/activesupport/lib/active_support/core_ext/date/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date/calculations.rb @@ -5,9 +5,11 @@ require 'active_support/core_ext/date/zones' require 'active_support/core_ext/time/zones' class Date + DAYS_INTO_WEEK = { :monday => 0, :tuesday => 1, :wednesday => 2, :thursday => 3, :friday => 4, :saturday => 5, :sunday => 6 } + if RUBY_VERSION < '1.9' undef :>> - + # 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. @@ -34,9 +36,9 @@ class Date ::Date.current.tomorrow end - # Returns Time.zone.today when config.time_zone is set, otherwise just returns Date.today. + # Returns Time.zone.today when <tt>Time.zone</tt> or <tt>config.time_zone</tt> are set, otherwise just returns Date.today. def current - ::Time.zone_default ? ::Time.zone.today : ::Date.today + ::Time.zone ? ::Time.zone.today : ::Date.today end end @@ -127,6 +129,11 @@ class Date ) 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) @@ -156,7 +163,7 @@ class Date def next_year years_since(1) end unless method_defined?(:next_year) - + # Shorthand for months_ago(1) def prev_month months_ago(1) @@ -185,10 +192,15 @@ class Date alias :sunday :end_of_week alias :at_end_of_week :end_of_week + # Returns a new Date/DateTime representing the start of the given day in the previous week (default is Monday). + 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) - days_into_week = { :monday => 0, :tuesday => 1, :wednesday => 2, :thursday => 3, :friday => 4, :saturday => 5, :sunday => 6} - result = (self + 7).beginning_of_week + days_into_week[day] + result = (self + 7).beginning_of_week + DAYS_INTO_WEEK[day] self.acts_like?(:time) ? result.change(:hour => 0) : result end diff --git a/activesupport/lib/active_support/core_ext/date/conversions.rb b/activesupport/lib/active_support/core_ext/date/conversions.rb index 092f936961..769ead9544 100644 --- a/activesupport/lib/active_support/core_ext/date/conversions.rb +++ b/activesupport/lib/active_support/core_ext/date/conversions.rb @@ -1,5 +1,5 @@ require 'date' -require 'active_support/inflector' +require 'active_support/inflector/methods' require 'active_support/core_ext/date/zones' class Date @@ -93,6 +93,12 @@ class Date ::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 end diff --git a/activesupport/lib/active_support/core_ext/date/zones.rb b/activesupport/lib/active_support/core_ext/date/zones.rb index 3a83af6be2..a70b47b7bc 100644 --- a/activesupport/lib/active_support/core_ext/date/zones.rb +++ b/activesupport/lib/active_support/core_ext/date/zones.rb @@ -2,10 +2,10 @@ require 'date' require 'active_support/core_ext/time/zones' class Date - # Converts Date to a TimeWithZone in the current zone if Time.zone_default is set, - # otherwise converts Date to a Time via Date#to_time + # 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 def to_time_in_current_zone - if ::Time.zone_default + if ::Time.zone ::Time.zone.local(year, month, day) else to_time diff --git a/activesupport/lib/active_support/core_ext/date_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_time/calculations.rb index 1dc3933e12..48cf1a435d 100644 --- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb @@ -1,6 +1,4 @@ require 'rational' unless RUBY_VERSION >= '1.9.2' -require 'active_support/core_ext/object/acts_like' -require 'active_support/core_ext/time/zones' class DateTime class << self @@ -9,8 +7,9 @@ class DateTime ::Time.local(2007).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>. def current - ::Time.zone_default ? ::Time.zone.now.to_datetime : ::Time.now.to_datetime + ::Time.zone ? ::Time.zone.now.to_datetime : ::Time.now.to_datetime end end @@ -82,6 +81,29 @@ class DateTime change(:hour => 23, :min => 59, :sec => 59) end + # 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 + end + # Adjusts DateTime to UTC by adding its offset value; offset is set to 0 # # Example: @@ -104,11 +126,7 @@ class DateTime end # Layers additional behavior on DateTime#<=> so that Time and ActiveSupport::TimeWithZone instances can be compared with a DateTime - def compare_with_coercion(other) - other = other.comparable_time if other.respond_to?(:comparable_time) - other = other.to_datetime unless other.acts_like?(:date) - compare_without_coercion(other) + def <=>(other) + super other.to_datetime end - alias_method :compare_without_coercion, :<=> - alias_method :<=>, :compare_with_coercion end diff --git a/activesupport/lib/active_support/core_ext/date_time/conversions.rb b/activesupport/lib/active_support/core_ext/date_time/conversions.rb index 47b8aa59fb..21b84b994b 100644 --- a/activesupport/lib/active_support/core_ext/date_time/conversions.rb +++ b/activesupport/lib/active_support/core_ext/date_time/conversions.rb @@ -1,4 +1,4 @@ -require 'active_support/inflector' +require 'active_support/inflector/methods' require 'active_support/core_ext/time/conversions' require 'active_support/core_ext/date_time/calculations' require 'active_support/values/time_zone' @@ -9,12 +9,12 @@ class DateTime 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>. - # + # # === Examples # datetime = DateTime.civil(2007, 12, 4, 0, 0, 0, 0) # => Tue, 04 Dec 2007 00:00:00 +0000 - # + # # datetime.to_formatted_s(:db) # => "2007-12-04 00:00:00" # datetime.to_s(:db) # => "2007-12-04 00:00:00" # datetime.to_s(:number) # => "20071204000000" @@ -50,7 +50,7 @@ class DateTime def formatted_offset(colon = true, alternate_utc_string = nil) utc? && alternate_utc_string || ActiveSupport::TimeZone.seconds_to_utc_offset(utc_offset, colon) end - + # Overrides the default inspect method with a human readable one, e.g., "Mon, 21 Feb 2005 14:30:00 +0000" def readable_inspect to_s(:rfc822) @@ -66,7 +66,7 @@ class DateTime # 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) : self + 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 @@ -83,8 +83,8 @@ class DateTime 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 diff --git a/activesupport/lib/active_support/core_ext/date_time/zones.rb b/activesupport/lib/active_support/core_ext/date_time/zones.rb index a7411d54ae..82a4f7ac5a 100644 --- a/activesupport/lib/active_support/core_ext/date_time/zones.rb +++ b/activesupport/lib/active_support/core_ext/date_time/zones.rb @@ -9,7 +9,7 @@ class DateTime # 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, + # 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 diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb index f76ed401cd..6d7f771b5d 100644 --- a/activesupport/lib/active_support/core_ext/enumerable.rb +++ b/activesupport/lib/active_support/core_ext/enumerable.rb @@ -9,7 +9,7 @@ module Enumerable # # Example: # - # latest_transcripts.group_by(&:day).each do |day, transcripts| + # latest_transcripts.group_by(&:day).each do |day, transcripts| # p "#{day} -> #{transcripts.map(&:class).join(', ')}" # end # "2006-03-01 -> Transcript" @@ -88,21 +88,18 @@ module Enumerable # => { "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 - inject({}) do |accum, elem| - accum[yield(elem)] = elem - accum - end + Hash[map { |elem| [yield(elem), elem] }] end - + # Returns true if the collection has more than 1 element. Functionally equivalent to collection.size > 1. - # Works with a block too ala any?, so people.many? { |p| p.age > 26 } # => returns true if more than 1 person is over 26. + # Can be called with a block too, much like any?, so people.many? { |p| p.age > 26 } returns true if more than 1 person is over 26. def many?(&block) size = block_given? ? select(&block).size : self.size size > 1 end - + # The negative of the Enumerable#include?. Returns true if the collection does not include the object. def exclude?(object) !include?(object) diff --git a/activesupport/lib/active_support/core_ext/float/rounding.rb b/activesupport/lib/active_support/core_ext/float/rounding.rb index 9bdf5bba7b..0d4fb87665 100644 --- a/activesupport/lib/active_support/core_ext/float/rounding.rb +++ b/activesupport/lib/active_support/core_ext/float/rounding.rb @@ -16,4 +16,4 @@ class Float precisionless_round end end -end +end if RUBY_VERSION < '1.9' diff --git a/activesupport/lib/active_support/core_ext/hash.rb b/activesupport/lib/active_support/core_ext/hash.rb index 501483498d..fd1cda991e 100644 --- a/activesupport/lib/active_support/core_ext/hash.rb +++ b/activesupport/lib/active_support/core_ext/hash.rb @@ -1,5 +1,6 @@ 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/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb index 2763af6121..3005fef44c 100644 --- a/activesupport/lib/active_support/core_ext/hash/conversions.rb +++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb @@ -7,7 +7,7 @@ require 'active_support/core_ext/string/inflections' class Hash # Returns a string containing an XML representation of its receiver: - # + # # {"foo" => 1, "bar" => 2}.to_xml # # => # # <?xml version="1.0" encoding="UTF-8"?> @@ -15,22 +15,34 @@ class Hash # # <foo type="integer">1</foo> # # <bar type="integer">2</bar> # # </hash> - # + # # To do so, the method loops over the pairs and builds nodes that depend on # the _values_. Given a pair +key+, +value+: - # + # # * If +value+ is a hash there's a recursive call with +key+ as <tt>:root</tt>. - # + # # * If +value+ is an array there's a recursive call with +key+ as <tt>:root</tt>, # and +key+ singularized as <tt>:children</tt>. - # + # # * 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. Its - # return value becomes a new node. - # + # 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) }) + # # => "<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!" + # end + # end + # + # {: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 # +value+ as text node. If +value+ is +nil+ an attribute "nil" set to "true" is added. # Unless the option <tt>:skip_types</tt> exists and is true, an attribute "type" is @@ -48,9 +60,9 @@ class Hash # "DateTime" => "datetime", # "Time" => "datetime" # } - # + # # By default the root node is "hash", but that's configurable via the <tt>:root</tt> option. - # + # # The default XML builder is a fresh instance of <tt>Builder::XmlMarkup</tt>. You can # configure your own builder with the <tt>:builder</tt> option. The method also accepts # options like <tt>:dasherize</tt> and friends, they are forwarded to the builder. @@ -108,17 +120,14 @@ class Hash # 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 + # 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 = value.inject({}) do |h,(k,v)| - h[k] = typecast_xml_value(v) - h - end - + xml_value = Hash[value.map { |k,v| [k, typecast_xml_value(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 @@ -136,10 +145,7 @@ class Hash def unrename_keys(params) case params.class.to_s when "Hash" - params.inject({}) do |h,(k,v)| - h[k.to_s.tr("-", "_")] = unrename_keys(v) - h - end + Hash[params.map { |k,v| [k.to_s.tr("-", "_"), unrename_keys(v)] } ] when "Array" params.map { |v| unrename_keys(v) } else diff --git a/activesupport/lib/active_support/core_ext/hash/deep_dup.rb b/activesupport/lib/active_support/core_ext/hash/deep_dup.rb new file mode 100644 index 0000000000..447142605c --- /dev/null +++ b/activesupport/lib/active_support/core_ext/hash/deep_dup.rb @@ -0,0 +1,11 @@ +class Hash + # Returns a deep copy of hash. + 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/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb index 0420e206af..aad4b61e16 100644 --- a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb +++ b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb @@ -7,8 +7,6 @@ class Hash # {:a => 1}.with_indifferent_access["a"] # => 1 # def with_indifferent_access - hash = ActiveSupport::HashWithIndifferentAccess.new(self) - hash.default = self.default - hash + ActiveSupport::HashWithIndifferentAccess.new_from_hash_copying_default(self) end end diff --git a/activesupport/lib/active_support/core_ext/hash/keys.rb b/activesupport/lib/active_support/core_ext/hash/keys.rb index 045a6944fa..d8748b1138 100644 --- a/activesupport/lib/active_support/core_ext/hash/keys.rb +++ b/activesupport/lib/active_support/core_ext/hash/keys.rb @@ -35,11 +35,13 @@ class Hash # as keys, this will fail. # # ==== Examples - # { :name => "Rob", :years => "28" }.assert_valid_keys(:name, :age) # => raises "ArgumentError: Unknown key(s): years" - # { :name => "Rob", :age => "28" }.assert_valid_keys("name", "age") # => raises "ArgumentError: Unknown key(s): name, age" + # { :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) - unknown_keys = keys - [valid_keys].flatten - raise(ArgumentError, "Unknown key(s): #{unknown_keys.join(", ")}") unless unknown_keys.empty? + valid_keys.flatten! + each_key do |k| + raise(ArgumentError, "Unknown key: #{k}") unless valid_keys.include?(k) + end end end diff --git a/activesupport/lib/active_support/core_ext/hash/reverse_merge.rb b/activesupport/lib/active_support/core_ext/hash/reverse_merge.rb index d7ebd5feef..01863a162b 100644 --- a/activesupport/lib/active_support/core_ext/hash/reverse_merge.rb +++ b/activesupport/lib/active_support/core_ext/hash/reverse_merge.rb @@ -1,27 +1,22 @@ class Hash - # Allows for reverse merging two hashes where the keys in the calling hash take precedence over those - # in the <tt>other_hash</tt>. This is particularly useful for initializing an option hash with default values: + # Merges the caller into +other_hash+. For example, # - # def setup(options = {}) - # options.reverse_merge! :size => 25, :velocity => 10 - # end + # options = options.reverse_merge(:size => 25, :velocity => 10) # - # Using <tt>merge</tt>, the above example would look as follows: + # is equivalent to # - # def setup(options = {}) - # { :size => 25, :velocity => 10 }.merge(options) - # end + # options = {:size => 25, :velocity => 10}.merge(options) # - # The default <tt>:size</tt> and <tt>:velocity</tt> are only set if the +options+ hash passed in doesn't already - # have the respective key. + # This is particularly useful for initializing an options hash + # with default values. def reverse_merge(other_hash) other_hash.merge(self) end - # Performs the opposite of <tt>merge</tt>, with the keys and values from the first hash taking precedence over the second. - # Modifies the receiver in place. + # Destructive +reverse_merge+. def reverse_merge!(other_hash) - merge!( other_hash ){|k,o,n| o } + # right wins if there is no left + merge!( other_hash ){|key,left,right| left } end alias_method :reverse_update, :reverse_merge! diff --git a/activesupport/lib/active_support/core_ext/integer/time.rb b/activesupport/lib/active_support/core_ext/integer/time.rb index a0ccf0e971..c677400396 100644 --- a/activesupport/lib/active_support/core_ext/integer/time.rb +++ b/activesupport/lib/active_support/core_ext/integer/time.rb @@ -1,7 +1,7 @@ class Integer # Enables the use of time calculations and declarations, like 45.minutes + 2.hours + 4.years. # - # These methods use Time#advance for precise date calculations when using from_now, ago, etc. + # 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) @@ -12,7 +12,7 @@ class Integer # # # 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: diff --git a/activesupport/lib/active_support/core_ext/kernel/reporting.rb b/activesupport/lib/active_support/core_ext/kernel/reporting.rb index 7c455f66d5..37a827123a 100644 --- a/activesupport/lib/active_support/core_ext/kernel/reporting.rb +++ b/activesupport/lib/active_support/core_ext/kernel/reporting.rb @@ -38,7 +38,7 @@ module Kernel # puts 'But this will' def silence_stream(stream) old_stream = stream.dup - stream.reopen(Config::CONFIG['host_os'] =~ /mswin|mingw/ ? 'NUL:' : '/dev/null') + stream.reopen(RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ ? 'NUL:' : '/dev/null') stream.sync = true yield ensure @@ -59,4 +59,23 @@ module Kernel raise unless exception_classes.any? { |cls| e.kind_of?(cls) } end end + + # Captures the given stream and returns it: + # + # stream = capture(:stdout){ puts "Cool" } + # stream # => "Cool\n" + # + def capture(stream) + begin + stream = stream.to_s + eval "$#{stream} = StringIO.new" + yield + result = eval("$#{stream}").string + ensure + eval("$#{stream} = #{stream.upcase}") + end + + result + end + alias :silence :capture end diff --git a/activesupport/lib/active_support/core_ext/logger.rb b/activesupport/lib/active_support/core_ext/logger.rb index a1c351bfd9..e63a0a9ed9 100644 --- a/activesupport/lib/active_support/core_ext/logger.rb +++ b/activesupport/lib/active_support/core_ext/logger.rb @@ -4,18 +4,17 @@ require 'active_support/core_ext/class/attribute_accessors' class Logger #:nodoc: def self.define_around_helper(level) module_eval <<-end_eval, __FILE__, __LINE__ + 1 - def around_#{level}(before_message, after_message, &block) # def around_debug(before_message, after_message, &block) - self.#{level}(before_message) # self.debug(before_message) - return_value = block.call(self) # return_value = block.call(self) - self.#{level}(after_message) # self.debug(after_message) - return return_value # return return_value - end # end + def around_#{level}(before_message, after_message) # def around_debug(before_message, after_message, &block) + self.#{level}(before_message) # self.debug(before_message) + return_value = yield(self) # return_value = yield(self) + self.#{level}(after_message) # self.debug(after_message) + return_value # return_value + end # end end_eval end [:debug, :info, :error, :fatal].each {|level| define_around_helper(level) } end - require 'logger' # Extensions to the built-in Ruby logger. @@ -65,11 +64,11 @@ class Logger formatter.datetime_format if formatter.respond_to?(:datetime_format) end - alias :old_formatter :formatter if method_defined?(:formatter) - # Get the current formatter. The default formatter is a SimpleFormatter which only - # displays the log message - def formatter - @formatter ||= SimpleFormatter.new + alias :old_initialize :initialize + # Overwrite initialize to set a default formatter. + def initialize(*args) + old_initialize(*args) + self.formatter = SimpleFormatter.new end # Simple formatter which only displays the message. @@ -79,30 +78,4 @@ class Logger "#{String === msg ? msg : msg.inspect}\n" end end - - private - alias old_format_message format_message - - # Ruby 1.8.3 transposed the msg and progname arguments to format_message. - # We can't test RUBY_VERSION because some distributions don't keep Ruby - # and its standard library in sync, leading to installations of Ruby 1.8.2 - # with Logger from 1.8.3 and vice versa. - if method_defined?(:formatter=) - def format_message(severity, timestamp, progname, msg) - formatter.call(severity, timestamp, progname, msg) - end - else - def format_message(severity, timestamp, msg, progname) - formatter.call(severity, timestamp, progname, msg) - end - - attr_writer :formatter - public :formatter= - - alias old_format_datetime format_datetime - def format_datetime(datetime) datetime end - - alias old_msg2str msg2str - def msg2str(msg) msg end - end end diff --git a/activesupport/lib/active_support/core_ext/module/anonymous.rb b/activesupport/lib/active_support/core_ext/module/anonymous.rb index df25a09ec9..3982c9c586 100644 --- a/activesupport/lib/active_support/core_ext/module/anonymous.rb +++ b/activesupport/lib/active_support/core_ext/module/anonymous.rb @@ -19,6 +19,6 @@ class Module 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.blank? end end diff --git a/activesupport/lib/active_support/core_ext/module/attr_accessor_with_default.rb b/activesupport/lib/active_support/core_ext/module/attr_accessor_with_default.rb index 28ac89dab9..e3259a0a84 100644 --- a/activesupport/lib/active_support/core_ext/module/attr_accessor_with_default.rb +++ b/activesupport/lib/active_support/core_ext/module/attr_accessor_with_default.rb @@ -2,30 +2,29 @@ class Module # Declare an attribute accessor with an initial default return value. # # To give attribute <tt>:age</tt> the initial value <tt>25</tt>: - # + # # class Person # attr_accessor_with_default :age, 25 # end # - # some_person.age - # => 25 - # some_person.age = 26 - # some_person.age - # => 26 + # person = Person.new + # person.age # => 25 + # + # person.age = 26 + # person.age # => 26 # # To give attribute <tt>:element_name</tt> a dynamic default value, evaluated # in scope of self: # - # attr_accessor_with_default(:element_name) { name.underscore } + # attr_accessor_with_default(:element_name) { name.underscore } # - def attr_accessor_with_default(sym, default = nil, &block) - raise 'Default value or block required' unless !default.nil? || block - define_method(sym, block_given? ? block : Proc.new { default }) + def attr_accessor_with_default(sym, default = Proc.new) + define_method(sym, block_given? ? default : Proc.new { default }) module_eval(<<-EVAL, __FILE__, __LINE__ + 1) - def #{sym}=(value) # def age=(value) - class << self; attr_reader :#{sym} end # class << self; attr_reader :age end - @#{sym} = value # @age = value - end # end + def #{sym}=(value) # def age=(value) + class << self; attr_accessor :#{sym} end # class << self; attr_accessor :age end + @#{sym} = value # @age = value + end # end EVAL end end diff --git a/activesupport/lib/active_support/core_ext/module/attr_internal.rb b/activesupport/lib/active_support/core_ext/module/attr_internal.rb index 28bc30ae26..00db75bfec 100644 --- a/activesupport/lib/active_support/core_ext/module/attr_internal.rb +++ b/activesupport/lib/active_support/core_ext/module/attr_internal.rb @@ -1,16 +1,12 @@ class Module # Declares an attribute reader backed by an internally-named instance variable. def attr_internal_reader(*attrs) - attrs.each do |attr| - module_eval "def #{attr}() #{attr_internal_ivar_name(attr)} end", __FILE__, __LINE__ - end + attrs.each {|attr_name| attr_internal_define(attr_name, :reader)} end # Declares an attribute writer backed by an internally-named instance variable. def attr_internal_writer(*attrs) - attrs.each do |attr| - module_eval "def #{attr}=(v) #{attr_internal_ivar_name(attr)} = v end", __FILE__, __LINE__ - end + attrs.each {|attr_name| attr_internal_define(attr_name, :writer)} end # Declares an attribute reader and writer backed by an internally-named instance @@ -29,4 +25,15 @@ class Module def attr_internal_ivar_name(attr) Module.attr_internal_naming_format % attr end + + def attr_internal_define(attr_name, type) + internal_name = attr_internal_ivar_name(attr_name).sub(/\A@/, '') + class_eval do # class_eval is necessary on 1.9 or else the methods a made private + # use native attr_* methods as they are faster on some Ruby implementations + send("attr_#{type}", internal_name) + end + attr_name, internal_name = "#{attr_name}=", "#{internal_name}=" if type == :writer + alias_method attr_name, internal_name + remove_method internal_name + end end diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb index 2d88cb57e5..871f5cef3b 100644 --- a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb +++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb @@ -11,7 +11,7 @@ class Module @@#{sym} end EOS - + unless options[:instance_reader] == false class_eval(<<-EOS, __FILE__, __LINE__ + 1) def #{sym} diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb index 40a1866428..3a7652f5bf 100644 --- a/activesupport/lib/active_support/core_ext/module/delegation.rb +++ b/activesupport/lib/active_support/core_ext/module/delegation.rb @@ -113,7 +113,7 @@ class Module raise ArgumentError, "Can only automatically set the delegation prefix when delegating to a method." end - prefix = options[:prefix] && "#{options[:prefix] == true ? to : options[:prefix]}_" + prefix = options[:prefix] && "#{options[:prefix] == true ? to : options[:prefix]}_" || '' file, line = caller.first.split(':', 2) line = line.to_i diff --git a/activesupport/lib/active_support/core_ext/module/deprecation.rb b/activesupport/lib/active_support/core_ext/module/deprecation.rb index 5a5b4e3f80..9c169a2598 100644 --- a/activesupport/lib/active_support/core_ext/module/deprecation.rb +++ b/activesupport/lib/active_support/core_ext/module/deprecation.rb @@ -1,3 +1,5 @@ +require 'active_support/deprecation' + class Module # Declare that a method has been deprecated. # deprecate :foo diff --git a/activesupport/lib/active_support/core_ext/module/remove_method.rb b/activesupport/lib/active_support/core_ext/module/remove_method.rb index b8c01aca0e..07d7c9b018 100644 --- a/activesupport/lib/active_support/core_ext/module/remove_method.rb +++ b/activesupport/lib/active_support/core_ext/module/remove_method.rb @@ -3,7 +3,7 @@ class Module remove_method(method) rescue NameError end - + def redefine_method(method, &block) remove_possible_method(method) define_method(method, &block) diff --git a/activesupport/lib/active_support/core_ext/module/synchronization.rb b/activesupport/lib/active_support/core_ext/module/synchronization.rb index de76a069d6..ed16c2f71b 100644 --- a/activesupport/lib/active_support/core_ext/module/synchronization.rb +++ b/activesupport/lib/active_support/core_ext/module/synchronization.rb @@ -1,11 +1,12 @@ +require 'thread' require 'active_support/core_ext/module/aliasing' require 'active_support/core_ext/array/extract_options' class Module # Synchronize access around a method, delegating synchronization to a - # particular mutex. A mutex (either a Mutex, or any object that responds to + # 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, + # The :with option should be a symbol or string, and can represent a method, # constant, or instance or class variable. # Example: # class SharedCache diff --git a/activesupport/lib/active_support/core_ext/numeric/time.rb b/activesupport/lib/active_support/core_ext/numeric/time.rb index d1062805c5..e73915ffcf 100644 --- a/activesupport/lib/active_support/core_ext/numeric/time.rb +++ b/activesupport/lib/active_support/core_ext/numeric/time.rb @@ -3,7 +3,7 @@ require 'active_support/duration' class Numeric # Enables the use of time calculations and declarations, like 45.minutes + 2.hours + 4.years. # - # These methods use Time#advance for precise date calculations when using from_now, ago, etc. + # 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) @@ -14,7 +14,7 @@ class Numeric # # # 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: @@ -25,8 +25,8 @@ class Numeric # # equivalent to 365.25.days.to_f.from_now # 1.year.to_f.from_now # - # In such cases, Ruby's core - # Date[http://stdlib.rubyonrails.org/libdoc/date/rdoc/index.html] and + # 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 def seconds @@ -37,13 +37,13 @@ class Numeric def minutes ActiveSupport::Duration.new(self * 60, [[:seconds, self * 60]]) end - alias :minute :minutes - + alias :minute :minutes + def hours ActiveSupport::Duration.new(self * 3600, [[:seconds, self * 3600]]) end alias :hour :hours - + def days ActiveSupport::Duration.new(self * 24.hours, [[:days, self]]) end @@ -53,14 +53,14 @@ class Numeric ActiveSupport::Duration.new(self * 7.days, [[:days, self * 7]]) end alias :week :weeks - + def fortnights ActiveSupport::Duration.new(self * 2.weeks, [[:days, self * 14]]) end alias :fortnight :fortnights - + # Reads best without arguments: 10.minutes.ago - def ago(time = ::Time.now) + def ago(time = ::Time.current) time - self end @@ -68,7 +68,7 @@ class Numeric alias :until :ago # Reads best with argument: 10.minutes.since(time) - def since(time = ::Time.now) + def since(time = ::Time.current) time + self end diff --git a/activesupport/lib/active_support/core_ext/object.rb b/activesupport/lib/active_support/core_ext/object.rb index d671da6711..790a26f5c1 100644 --- a/activesupport/lib/active_support/core_ext/object.rb +++ b/activesupport/lib/active_support/core_ext/object.rb @@ -2,7 +2,6 @@ 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/try' -require 'active_support/core_ext/object/returning' require 'active_support/core_ext/object/conversions' require 'active_support/core_ext/object/instance_variables' diff --git a/activesupport/lib/active_support/core_ext/object/blank.rb b/activesupport/lib/active_support/core_ext/object/blank.rb index eb99bb1a36..d0c1ea8326 100644 --- a/activesupport/lib/active_support/core_ext/object/blank.rb +++ b/activesupport/lib/active_support/core_ext/object/blank.rb @@ -13,11 +13,11 @@ class Object respond_to?(:empty?) ? empty? : !self end - # An object is present if it's not blank. + # An object is present if it's not <tt>blank?</tt>. def present? !blank? end - + # Returns object if it's #present? otherwise returns nil. # object.presence is equivalent to object.present? ? object : nil. # diff --git a/activesupport/lib/active_support/core_ext/object/instance_variables.rb b/activesupport/lib/active_support/core_ext/object/instance_variables.rb index 866317b17a..eda9694614 100644 --- a/activesupport/lib/active_support/core_ext/object/instance_variables.rb +++ b/activesupport/lib/active_support/core_ext/object/instance_variables.rb @@ -7,13 +7,10 @@ class Object # @x, @y = x, y # end # end - # + # # C.new(0, 1).instance_values # => {"x" => 0, "y" => 1} def instance_values #:nodoc: - instance_variables.inject({}) do |values, name| - values[name.to_s[1..-1]] = instance_variable_get(name) - values - end + Hash[instance_variables.map { |name| [name.to_s[1..-1], instance_variable_get(name)] }] end # Returns an array of instance variable names including "@". They are strings @@ -24,7 +21,7 @@ class Object # @x, @y = x, y # end # end - # + # # C.new(0, 1).instance_variable_names # => ["@y", "@x"] if RUBY_VERSION >= '1.9' def instance_variable_names @@ -33,35 +30,4 @@ class Object else alias_method :instance_variable_names, :instance_variables end - - # Copies the instance variables of +object+ into +self+. - # - # Instance variable names in the +exclude+ array are ignored. If +object+ - # responds to <tt>protected_instance_variables</tt> the ones returned are - # also ignored. For example, Rails controllers implement that method. - # - # In both cases strings and symbols are understood, and they have to include - # the at sign. - # - # class C - # def initialize(x, y, z) - # @x, @y, @z = x, y, z - # end - # - # def protected_instance_variables - # %w(@z) - # end - # end - # - # a = C.new(0, 1, 2) - # b = C.new(3, 4, 5) - # - # a.copy_instance_variables_from(b, [:@y]) - # # a is now: @x = 3, @y = 1, @z = 2 - def copy_instance_variables_from(object, exclude = []) #:nodoc: - exclude += object.protected_instance_variables if object.respond_to? :protected_instance_variables - - vars = object.instance_variables.map(&:to_s) - exclude.map(&:to_s) - vars.each { |name| instance_variable_set(name, object.instance_variable_get(name)) } - end end diff --git a/activesupport/lib/active_support/core_ext/object/returning.rb b/activesupport/lib/active_support/core_ext/object/returning.rb deleted file mode 100644 index 07250b2a27..0000000000 --- a/activesupport/lib/active_support/core_ext/object/returning.rb +++ /dev/null @@ -1,43 +0,0 @@ -class Object - # Returns +value+ after yielding +value+ to the block. This simplifies the - # process of constructing an object, performing work on the object, and then - # returning the object from a method. It is a Ruby-ized realization of the K - # combinator, courtesy of Mikael Brockman. - # - # ==== Examples - # - # # Without returning - # def foo - # values = [] - # values << "bar" - # values << "baz" - # return values - # end - # - # foo # => ['bar', 'baz'] - # - # # returning with a local variable - # def foo - # returning values = [] do - # values << 'bar' - # values << 'baz' - # end - # end - # - # foo # => ['bar', 'baz'] - # - # # returning with a block argument - # def foo - # returning [] do |values| - # values << 'bar' - # values << 'baz' - # end - # end - # - # foo # => ['bar', 'baz'] - def returning(value) - ActiveSupport::Deprecation.warn('Object#returning has been deprecated in favor of Object#tap.', caller) - yield(value) - value - end -end
\ No newline at end of file diff --git a/activesupport/lib/active_support/core_ext/object/to_param.rb b/activesupport/lib/active_support/core_ext/object/to_param.rb index f2e7c2351e..e5f81078ee 100644 --- a/activesupport/lib/active_support/core_ext/object/to_param.rb +++ b/activesupport/lib/active_support/core_ext/object/to_param.rb @@ -1,5 +1,3 @@ - - class Object # Alias of <tt>to_s</tt>. def to_param @@ -34,16 +32,24 @@ class Array end class Hash - # Converts a hash into a string suitable for use as a URL query string. An optional <tt>namespace</tt> can be - # passed to enclose the param names (see example below). + # Returns a string representation of the receiver suitable for use as a URL + # query string: + # + # {:name => 'David', :nationality => 'Danish'}.to_param + # # => "name=David&nationality=Danish" + # + # An optional namespace can be passed to enclose the param names: + # + # {:name => 'David', :nationality => 'Danish'}.to_param('user') + # # => "user[name]=David&user[nationality]=Danish" # - # ==== Examples - # { :name => 'David', :nationality => 'Danish' }.to_param # => "name=David&nationality=Danish" + # The string pairs "key=value" that conform the query string + # are sorted lexicographically in ascending order. # - # { :name => 'David', :nationality => 'Danish' }.to_param('user') # => "user[name]=David&user[nationality]=Danish" + # This method is also aliased as +to_query+. def to_param(namespace = nil) collect do |key, value| value.to_query(namespace ? "#{namespace}[#{key}]" : key) - end * '&' + end.sort * '&' end end diff --git a/activesupport/lib/active_support/core_ext/object/to_query.rb b/activesupport/lib/active_support/core_ext/object/to_query.rb index c9981895b4..3f1540f685 100644 --- a/activesupport/lib/active_support/core_ext/object/to_query.rb +++ b/activesupport/lib/active_support/core_ext/object/to_query.rb @@ -7,7 +7,7 @@ class Object # Note: This method is defined as a default implementation for all Objects for Hash#to_query to work. def to_query(key) require 'cgi' unless defined?(CGI) && defined?(CGI::escape) - "#{CGI.escape(key.to_s).gsub(/%(5B|5D)/n) { [$1].pack('H*') }}=#{CGI.escape(to_param.to_s)}" + "#{CGI.escape(key.to_s)}=#{CGI.escape(to_param.to_s)}" end end @@ -15,7 +15,7 @@ class Array # Converts an array into a string suitable for use as a URL query string, # using the given +key+ as the param name. # - # ['Rails', 'coding'].to_query('hobbies') # => "hobbies[]=Rails&hobbies[]=coding" + # ['Rails', 'coding'].to_query('hobbies') # => "hobbies%5B%5D=Rails&hobbies%5B%5D=coding" def to_query(key) prefix = "#{key}[]" collect { |value| value.to_query(prefix) }.join '&' diff --git a/activesupport/lib/active_support/core_ext/object/try.rb b/activesupport/lib/active_support/core_ext/object/try.rb index a1c63a0e54..04619124a1 100644 --- a/activesupport/lib/active_support/core_ext/object/try.rb +++ b/activesupport/lib/active_support/core_ext/object/try.rb @@ -5,6 +5,8 @@ class Object # *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. # + # If try is called without a method to call, it will yield any given block with the object. + # # ==== Examples # # Without try @@ -18,15 +20,18 @@ class Object # +try+ also accepts arguments and/or a block, for the method it is trying # Person.try(:find, 1) # @people.try(:collect) {|p| p.name} + # + # Without a method argument try will yield to the block unless the receiver is nil. + # @person.try { |p| "#{p.first_name} #{p.last_name}" } #-- - # This method definition below is for rdoc purposes only. The alias_method call - # below overrides it as an optimization since +try+ behaves like +Object#send+, - # unless called on +NilClass+. - def try(method, *args, &block) - send(method, *args, &block) + # +try+ behaves like +Object#send+, unless called on +NilClass+. + def try(*a, &b) + if a.empty? && block_given? + yield self + else + __send__(*a, &b) + end end - remove_method :try - alias_method :try, :__send__ end class NilClass #:nodoc: diff --git a/activesupport/lib/active_support/core_ext/object/with_options.rb b/activesupport/lib/active_support/core_ext/object/with_options.rb index 3209cf7f11..c23afabfdb 100644 --- a/activesupport/lib/active_support/core_ext/object/with_options.rb +++ b/activesupport/lib/active_support/core_ext/object/with_options.rb @@ -7,13 +7,27 @@ class Object # provided. Each method called on the block variable must take an options # hash as its final argument. # - # with_options :order => 'created_at', :class_name => 'Comment' do |post| - # post.has_many :comments, :conditions => ['approved = ?', true], :dependent => :delete_all - # post.has_many :unapproved_comments, :conditions => ['approved = ?', false] - # post.has_many :all_comments + # Without with_options, 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 + # end + # + # Using with_options, we can remove the duplication: + # + # class Account < ActiveRecord::Base + # with_options :dependent => :destroy do |assoc| + # assoc.has_many :customers + # assoc.has_many :products + # assoc.has_many :invoices + # assoc.has_many :expenses + # end # end # - # Can also be used with an explicit receiver: + # It can also be used with an explicit receiver: # # map.with_options :controller => "people" do |people| # people.connect "/people", :action => "index" diff --git a/activesupport/lib/active_support/core_ext/range.rb b/activesupport/lib/active_support/core_ext/range.rb index c0736f3a44..2428a02242 100644 --- a/activesupport/lib/active_support/core_ext/range.rb +++ b/activesupport/lib/active_support/core_ext/range.rb @@ -2,3 +2,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' diff --git a/activesupport/lib/active_support/core_ext/range/conversions.rb b/activesupport/lib/active_support/core_ext/range/conversions.rb index 11a7ff66de..544e63132d 100644 --- a/activesupport/lib/active_support/core_ext/range/conversions.rb +++ b/activesupport/lib/active_support/core_ext/range/conversions.rb @@ -6,7 +6,7 @@ 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/activesupport/lib/active_support/core_ext/range/cover.rb b/activesupport/lib/active_support/core_ext/range/cover.rb new file mode 100644 index 0000000000..3a182cddd2 --- /dev/null +++ b/activesupport/lib/active_support/core_ext/range/cover.rb @@ -0,0 +1,3 @@ +class Range + alias_method(:cover?, :include?) unless instance_methods.include?(:cover?) +end diff --git a/activesupport/lib/active_support/core_ext/string.rb b/activesupport/lib/active_support/core_ext/string.rb index d8d1f9436e..8fb8c31ade 100644 --- a/activesupport/lib/active_support/core_ext/string.rb +++ b/activesupport/lib/active_support/core_ext/string.rb @@ -9,4 +9,5 @@ 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'
\ No newline at end of file +require 'active_support/core_ext/string/encoding' +require 'active_support/core_ext/string/strip' diff --git a/activesupport/lib/active_support/core_ext/string/access.rb b/activesupport/lib/active_support/core_ext/string/access.rb index 64bc8f6cea..c0d5cdf2d5 100644 --- a/activesupport/lib/active_support/core_ext/string/access.rb +++ b/activesupport/lib/active_support/core_ext/string/access.rb @@ -4,27 +4,27 @@ 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: + # 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: + # 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: + # Examples: # "hello".to(0) # => "h" # "hello".to(2) # => "hel" # "hello".to(10) # => "hello" @@ -34,7 +34,7 @@ class String # Returns the first character of the string or the first +limit+ characters. # - # Examples: + # Examples: # "hello".first # => "h" # "hello".first(2) # => "he" # "hello".first(10) # => "hello" @@ -50,7 +50,7 @@ class String # Returns the last character of the string or the last +limit+ characters. # - # Examples: + # Examples: # "hello".last # => "o" # "hello".last(2) # => "lo" # "hello".last(10) # => "hello" diff --git a/activesupport/lib/active_support/core_ext/string/filters.rb b/activesupport/lib/active_support/core_ext/string/filters.rb index e15a1df9c9..d478ee0ef6 100644 --- a/activesupport/lib/active_support/core_ext/string/filters.rb +++ b/activesupport/lib/active_support/core_ext/string/filters.rb @@ -25,13 +25,13 @@ class String # "Once upon a time in a world far far away".truncate(27) # # => "Once upon a time in a wo..." # - # The last characters will be replaced with the <tt>:omission</tt> string (defaults to "...") - # for a total length not exceeding <tt>:length</tt>: + # Pass a <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..." # - # Pass a <tt>:separator</tt> to truncate +text+ at a natural break: + # The last characters will be replaced with the <tt>:omission</tt> string (defaults to "...") + # for a total length not exceeding <tt>:length</tt>: # # "And they found that many people were sleeping better.".truncate(25, :omission => "... (continued)") # # => "And they f... (continued)" diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb index 32913a06ad..2f0676f567 100644 --- a/activesupport/lib/active_support/core_ext/string/inflections.rb +++ b/activesupport/lib/active_support/core_ext/string/inflections.rb @@ -1,7 +1,9 @@ require 'active_support/inflector/methods' require 'active_support/inflector/inflections' +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 database from the name of a class. +# For instance, you can figure out the name of a table from the name of a class. # # "ScaleScore".tableize # => "scale_scores" # @@ -72,7 +74,7 @@ class String alias_method :titlecase, :titleize # The reverse of +camelize+. Makes an underscored, lowercase form from the expression in the string. - # + # # +underscore+ will also change '::' to '/' to convert namespaces to paths. # # "ActiveRecord".underscore # => "active_record" @@ -97,7 +99,7 @@ class String end # Replaces special characters in a string so that it may be used as part of a 'pretty' URL. - # + # # ==== Examples # # class Person @@ -105,10 +107,10 @@ class String # "#{id}-#{name.parameterize}" # end # end - # + # # @person = Person.find(1) # # => #<Person id: 1, name: "Donald E. Knuth"> - # + # # <%= link_to(@person.name, person_path %> # # => <a href="/person/1-donald-e-knuth">Donald E. Knuth</a> def parameterize(sep = '-') @@ -138,11 +140,11 @@ class String def classify ActiveSupport::Inflector.classify(self) end - + # Capitalizes the first word, turns underscores into spaces, and strips '_id'. # Like +titleize+, this is meant for creating pretty output. # - # "employee_salary" # => "Employee salary" + # "employee_salary" # => "Employee salary" # "author_id" # => "Author" def humanize ActiveSupport::Inflector.humanize(self) diff --git a/activesupport/lib/active_support/core_ext/string/multibyte.rb b/activesupport/lib/active_support/core_ext/string/multibyte.rb index 0b974f5e0a..41de4d6435 100644 --- a/activesupport/lib/active_support/core_ext/string/multibyte.rb +++ b/activesupport/lib/active_support/core_ext/string/multibyte.rb @@ -9,7 +9,7 @@ class String # # 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 encapsuled string. + # 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" diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb index b53929c2a3..addd4dab95 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -24,6 +24,7 @@ class ERB end end + # Aliasing twice issues a warning "discarding old...". Remove first to avoid it. remove_method(:h) alias h html_escape @@ -32,15 +33,23 @@ class ERB singleton_class.send(:remove_method, :html_escape) module_function :html_escape - # A utility method for escaping HTML entities in JSON strings. - # This method is also aliased as <tt>j</tt>. + # 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?") + # # => is a \u003E 0 \u0026 a \u003C 10? + # + # Note that after this operation is performed the output is not + # valid JSON. In particular double quotes are removed: + # + # 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: # - # In your ERb templates, use this method to escape any HTML entities: # <%=j @person.to_json %> # - # ==== Example: - # puts json_escape("is a > 0 & a < 10?") - # # => is a \u003E 0 \u0026 a \u003C 10? def json_escape(s) s.to_s.gsub(/[&"><]/) { |special| JSON_ESCAPE[special] } end @@ -92,7 +101,13 @@ module ActiveSupport #:nodoc: self end + def encode_with(coder) + 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 end diff --git a/activesupport/lib/active_support/core_ext/string/strip.rb b/activesupport/lib/active_support/core_ext/string/strip.rb new file mode 100644 index 0000000000..086c610976 --- /dev/null +++ b/activesupport/lib/active_support/core_ext/string/strip.rb @@ -0,0 +1,26 @@ +require 'active_support/core_ext/object/try' + +class String + # Strips indentation in heredocs. + # + # For example in + # + # if options[:usage] + # puts <<-USAGE.strip_heredoc + # This command does such and such. + # + # Supported options are: + # -h This message + # ... + # USAGE + # end + # + # the user would see the usage message aligned against the left margin. + # + # Technically, it looks for the least indented line in the whole string, and removes + # that amount of leading whitespace. + def strip_heredoc + indent = scan(/^[ \t]*(?=\S)/).min.try(:size) || 0 + gsub(/^[ \t]{#{indent}}/, '') + end +end diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb index 90b6cd3685..7e134db118 100644 --- a/activesupport/lib/active_support/core_ext/time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/time/calculations.rb @@ -1,7 +1,4 @@ require 'active_support/duration' -require 'active_support/core_ext/date/acts_like' -require 'active_support/core_ext/date/calculations' -require 'active_support/core_ext/date_time/conversions' class Time COMMON_YEAR_DAYS_IN_MONTH = [nil, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] @@ -22,7 +19,7 @@ class Time # 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 + # 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) 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. @@ -41,9 +38,9 @@ class Time time_with_datetime_fallback(:local, *args) end - # Returns <tt>Time.zone.now</tt> when <tt>config.time_zone</tt> is set, otherwise just returns <tt>Time.now</tt>. + # Returns <tt>Time.zone.now</tt> when <tt>Time.zone</tt> or <tt>config.time_zone</tt> are set, otherwise just returns <tt>Time.now</tt>. def current - ::Time.zone_default ? ::Time.zone.now : ::Time.now + ::Time.zone ? ::Time.zone.now : ::Time.now end end @@ -92,12 +89,12 @@ class Time options[:weeks], partial_weeks = options[:weeks].divmod(1) options[:days] = (options[: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 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 @@ -117,6 +114,11 @@ 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) @@ -172,6 +174,11 @@ class Time end alias :at_end_of_week :end_of_week + # 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) @@ -273,14 +280,8 @@ class Time # Layers additional behavior on Time#<=> so that DateTime and ActiveSupport::TimeWithZone instances # can be chronologically compared with a Time def compare_with_coercion(other) - # if other is an ActiveSupport::TimeWithZone, coerce a Time instance from it so we can do <=> comparison - other = other.comparable_time if other.respond_to?(:comparable_time) - if other.acts_like?(:date) - # other is a Date/DateTime, so coerce self #to_datetime and hand off to DateTime#<=> - to_datetime.compare_without_coercion(other) - else - compare_without_coercion(other) - end + # we're avoiding Time#to_datetime cause it's expensive + other.is_a?(Time) ? compare_without_coercion(other.to_time) : to_datetime <=> other end alias_method :compare_without_coercion, :<=> alias_method :<=>, :compare_with_coercion diff --git a/activesupport/lib/active_support/core_ext/time/conversions.rb b/activesupport/lib/active_support/core_ext/time/conversions.rb index 86103ebce2..d9d5e02778 100644 --- a/activesupport/lib/active_support/core_ext/time/conversions.rb +++ b/activesupport/lib/active_support/core_ext/time/conversions.rb @@ -1,4 +1,4 @@ -require 'active_support/inflector' +require 'active_support/inflector/methods' require 'active_support/core_ext/time/publicize_conversion_methods' require 'active_support/values/time_zone' @@ -19,8 +19,8 @@ class Time # # time = Time.now # => Thu Jan 18 06:10:17 CST 2007 # - # time.to_formatted_s(:time) # => "06:10:17" - # time.to_s(:time) # => "06:10:17" + # 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" @@ -46,7 +46,7 @@ class Time end alias_method :to_default_s, :to_s alias_method :to_s, :to_formatted_s - + # Returns the UTC offset as an +HH:MM formatted string. # # Time.local(2000).formatted_offset # => "-06:00" @@ -55,31 +55,9 @@ class Time 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/activesupport/lib/active_support/core_ext/time/zones.rb b/activesupport/lib/active_support/core_ext/time/zones.rb index 6b9ee84d5c..ff90d7ca58 100644 --- a/activesupport/lib/active_support/core_ext/time/zones.rb +++ b/activesupport/lib/active_support/core_ext/time/zones.rb @@ -4,13 +4,13 @@ class Time class << self attr_accessor :zone_default - # Returns the TimeZone for the current request, if this has been set (via Time.zone=). + # Returns the TimeZone for the current request, if this has been set (via Time.zone=). # If <tt>Time.zone</tt> has not been set for the current request, returns the TimeZone specified in <tt>config.time_zone</tt>. def zone Thread.current[:time_zone] || zone_default end - # Sets <tt>Time.zone</tt> to a TimeZone object for the current request/thread. + # Sets <tt>Time.zone</tt> to a TimeZone object for the current request/thread. # # This method accepts any of the following: # @@ -19,14 +19,18 @@ class Time # * A TZInfo::Timezone object. # * An identifier for a TZInfo::Timezone object (e.g., "America/New_York"). # - # Here's an example of how you might set <tt>Time.zone</tt> on a per request basis -- <tt>current_user.time_zone</tt> - # just needs to return a string identifying the user's preferred TimeZone: + # Here's an example of how you might set <tt>Time.zone</tt> on a per request basis and reset it when the request is done. + # <tt>current_user.time_zone</tt> just needs to return a string identifying the user's preferred time zone: # # class ApplicationController < ActionController::Base - # before_filter :set_time_zone + # around_filter :set_time_zone # # def set_time_zone - # Time.zone = current_user.time_zone + # old_time_zone = Time.zone + # Time.zone = current_user.time_zone if logged_in? + # yield + # ensure + # Time.zone = old_time_zone # end # end def zone=(time_zone) @@ -63,7 +67,7 @@ class Time # 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, + # 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 diff --git a/activesupport/lib/active_support/core_ext/uri.rb b/activesupport/lib/active_support/core_ext/uri.rb index b7fe0a6209..ee991e3439 100644 --- a/activesupport/lib/active_support/core_ext/uri.rb +++ b/activesupport/lib/active_support/core_ext/uri.rb @@ -20,3 +20,11 @@ if RUBY_VERSION >= '1.9' end end end + +module URI + class << self + def parser + @parser ||= URI.const_defined?(:Parser) ? URI::Parser.new : URI + end + end +end diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index 2b80bd214f..dc10f78104 100644 --- a/activesupport/lib/active_support/dependencies.rb +++ b/activesupport/lib/active_support/dependencies.rb @@ -5,6 +5,7 @@ require 'active_support/core_ext/module/aliasing' require 'active_support/core_ext/module/attribute_accessors' require 'active_support/core_ext/module/introspection' require 'active_support/core_ext/module/anonymous' +require 'active_support/core_ext/module/deprecation' require 'active_support/core_ext/object/blank' require 'active_support/core_ext/load_error' require 'active_support/core_ext/name_error' @@ -47,9 +48,6 @@ module ActiveSupport #:nodoc: mattr_accessor :autoloaded_constants self.autoloaded_constants = [] - mattr_accessor :references - self.references = {} - # An array of constant names that need to be unloaded on every request. Used # to allow arbitrary constants to be marked for unloading. mattr_accessor :explicitly_unloadable_constants @@ -63,58 +61,78 @@ module ActiveSupport #:nodoc: mattr_accessor :log_activity self.log_activity = false - class WatchStack < Array + # 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 + class WatchStack < Hash + # @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]]. + def initialize - @mutex = Mutex.new + @watching = [] + super { |h,k| h[k] = [] } end - def self.locked(*methods) - methods.each { |m| class_eval "def #{m}(*) lock { super } end", __FILE__, __LINE__ } - end + # return a list of new constants found since the last call to watch_namespaces + def new_constants + constants = [] - locked :concat, :each, :delete_if, :<< + # Grab the list of namespaces that we're looking for new constants under + @watching.last.each do |namespace| + # Retrieve the constants that were present under the namespace when watch_namespaces + # was originally called + original_constants = self[namespace].last - def new_constants_for(frames) - constants = [] - frames.each do |mod_name, prior_constants| - mod = Inflector.constantize(mod_name) if Dependencies.qualified_const_defined?(mod_name) + mod = Inflector.constantize(namespace) if Dependencies.qualified_const_defined?(namespace) next unless mod.is_a?(Module) - new_constants = mod.local_constant_names - prior_constants - - # If we are checking for constants under, say, :Object, nested under something - # else that is checking for constants also under :Object, make sure the - # parent knows that we have found, and taken care of, the constant. - # - # In particular, this means that since Kernel.require discards the constants - # it finds, parents will be notified that about those constants, and not - # consider them "new". As a result, they will not be added to the - # autoloaded_constants list. - each do |key, value| - value.concat(new_constants) if key == mod_name + # Get a list of the constants that were added + new_constants = mod.local_constant_names - 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 + # element of self[Object] will be an Array of the constants that were present + # before parent.rb was required. The second element will be an Array of the + # constants that were present before child.rb was required. + self[namespace].each do |namespace_constants| + namespace_constants.concat(new_constants) end + # Normalize the list of new constants, and add them to the list we will return new_constants.each do |suffix| - constants << ([mod_name, suffix] - ["Object"]).join("::") + constants << ([namespace, suffix] - ["Object"]).join("::") end end constants + ensure + # A call to new_constants is always called after a call to watch_namespaces + pop_modules(@watching.pop) end # Add a set of modules to the watch stack, remembering the initial constants - def add_modules(modules) - list = modules.map do |desc| - name = Dependencies.to_constant_name(desc) - consts = Dependencies.qualified_const_defined?(name) ? - Inflector.constantize(name).local_constant_names : [] - [name, consts] + def watch_namespaces(namespaces) + 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 : [] + + watching << module_name + self[module_name] << original_constants end - concat(list) - list + @watching << watching end - def lock - @mutex.synchronize { yield self } + def pop_modules(modules) + modules.each { |mod| self[mod].pop } end end @@ -146,7 +164,7 @@ module ActiveSupport #:nodoc: def const_missing(const_name, nesting = nil) klass_name = name.presence || "Object" - if !nesting + 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 @@ -311,7 +329,7 @@ module ActiveSupport #:nodoc: if load? log "loading #{file_name}" - # Enable warnings iff this file has not been loaded before and + # Enable warnings if this file has not been loaded before and # warnings_on_first_load is set. load_args = ["#{file_name}.rb"] load_args << const_path unless const_path.nil? @@ -491,7 +509,12 @@ module ActiveSupport #:nodoc: end # Remove the constants that have been autoloaded, and those that have been - # marked for unloading. + # marked for unloading. Before each constant is removed a callback is sent + # to its class/module if it implements +before_remove_const+. + # + # The callback implementation should be restricted to cleaning up caches, etc. + # as the environment will be in an inconsistent state, e.g. other constants + # may have already been unloaded and not accessible. def remove_unloadable_constants! autoloaded_constants.each { |const| remove_constant const } autoloaded_constants.clear @@ -499,31 +522,76 @@ module ActiveSupport #:nodoc: explicitly_unloadable_constants.each { |const| remove_constant const } end - class Reference - @@constants = Hash.new { |h, k| h[k] = Inflector.constantize(k) } + class ClassCache + def initialize + @store = Hash.new { |h, k| h[k] = Inflector.constantize(k) } + end - attr_reader :name + def empty? + @store.empty? + end - def initialize(name) - @name = name.to_s - @@constants[@name] = name if name.respond_to?(:name) + def key?(key) + @store.key?(key) end - def get - @@constants[@name] + def []=(key, value) + return unless key.respond_to?(:name) + + raise(ArgumentError, 'anonymous classes cannot be cached') if key.name.blank? + + @store[key.name] = value end - def self.clear! - @@constants.clear + def [](key) + key = key.name if key.respond_to?(:name) + + @store[key] + end + alias :get :[] + + class Getter # :nodoc: + def initialize(name) + @name = name + end + + def get + Reference.get @name + end + deprecate :get + end + + def new(name) + self[name] = name + Getter.new(name) + end + deprecate :new + + def store(name) + self[name] = name + self + end + + def clear! + @store.clear end end + Reference = ClassCache.new + def ref(name) - references[name] ||= Reference.new(name) + Reference.new(name) + end + deprecate :ref + + # Store a reference to a class +klass+. + def reference(klass) + Reference.store klass end + # Get the reference for class named +name+. def constantize(name) - ref(name).get + Reference.get(name) end # Determine if the given constant has been automatically loaded. @@ -563,14 +631,15 @@ module ActiveSupport #:nodoc: # and will be removed immediately. def new_constants_in(*descs) log_call(*descs) - watch_frames = constant_watch_stack.add_modules(descs) + constant_watch_stack.watch_namespaces(descs) aborting = true + begin yield # Now yield to the code that is to define new constants. aborting = false ensure - new_constants = constant_watch_stack.new_constants_for(watch_frames) + new_constants = constant_watch_stack.new_constants log "New constants: #{new_constants * ', '}" return new_constants unless aborting @@ -580,9 +649,6 @@ module ActiveSupport #:nodoc: end return [] - ensure - # Remove the stack frames that we added. - watch_frames.each {|f| constant_watch_stack.delete(f) } if watch_frames.present? end class LoadingModule #:nodoc: @@ -618,6 +684,8 @@ module ActiveSupport #:nodoc: parent = Inflector.constantize(names * '::') 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 @@ -625,7 +693,7 @@ module ActiveSupport #:nodoc: protected def log_call(*args) - if logger && log_activity + if log_activity? arg_str = args.collect { |arg| arg.inspect } * ', ' /in `([a-z_\?\!]+)'/ =~ caller(1).first selector = $1 || '<unknown>' @@ -634,9 +702,11 @@ module ActiveSupport #:nodoc: end def log(msg) - if logger && log_activity - logger.debug "Dependencies: #{msg}" - end + logger.debug "Dependencies: #{msg}" if log_activity? + end + + def log_activity? + logger && log_activity end end end diff --git a/activesupport/lib/active_support/deprecation.rb b/activesupport/lib/active_support/deprecation.rb index e1b8211d68..ce0775a690 100644 --- a/activesupport/lib/active_support/deprecation.rb +++ b/activesupport/lib/active_support/deprecation.rb @@ -4,12 +4,12 @@ require 'active_support/deprecation/method_wrappers' require 'active_support/deprecation/proxy_wrappers' module ActiveSupport - module Deprecation #:nodoc: + module Deprecation class << self # The version the deprecated behavior will be removed, by default. attr_accessor :deprecation_horizon end - self.deprecation_horizon = '3.0' + self.deprecation_horizon = '3.1' # By default, warnings are not silenced and debugging is off. self.silenced = false diff --git a/activesupport/lib/active_support/deprecation/behaviors.rb b/activesupport/lib/active_support/deprecation/behaviors.rb index f54f65dcf0..da4af339fc 100644 --- a/activesupport/lib/active_support/deprecation/behaviors.rb +++ b/activesupport/lib/active_support/deprecation/behaviors.rb @@ -7,10 +7,17 @@ module ActiveSupport # Whether to print a backtrace along with the warning. attr_accessor :debug + # Returns the set behaviour or if one isn't set, defaults to +:stderr+ def behavior @behavior ||= [DEFAULT_BEHAVIORS[:stderr]] end + # Sets the behaviour to the specified value. Can be a single value or an array. + # + # Examples + # + # ActiveSupport::Deprecation.behavior = :stderr + # ActiveSupport::Deprecation.behavior = [:stderr, :log] def behavior=(behavior) @behavior = Array.wrap(behavior).map { |b| DEFAULT_BEHAVIORS[b] || b } end diff --git a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb index deb33ab702..a65fcafb44 100644 --- a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb +++ b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb @@ -1,4 +1,4 @@ -require 'active_support/inflector' +require 'active_support/inflector/methods' module ActiveSupport module Deprecation @@ -58,7 +58,7 @@ module ActiveSupport end end - class DeprecatedConstantProxy < DeprecationProxy #:nodoc: + class DeprecatedConstantProxy < DeprecationProxy #:nodoc:all def initialize(old_const, new_const) @old_const = old_const @new_const = new_const diff --git a/activesupport/lib/active_support/deprecation/reporting.rb b/activesupport/lib/active_support/deprecation/reporting.rb index 49d58cd3a1..ced08b8783 100644 --- a/activesupport/lib/active_support/deprecation/reporting.rb +++ b/activesupport/lib/active_support/deprecation/reporting.rb @@ -3,6 +3,12 @@ module ActiveSupport class << self attr_accessor :silenced + # Outputs a deprecation warning to the output configured by <tt>ActiveSupport::Deprecation.behavior</tt> + # + # Example: + # + # ActiveSupport::Deprecation.warn("something broke!") + # #=> "DEPRECATION WARNING: something broke! (called from your_code.rb:1)" def warn(message = nil, callstack = caller) return if silenced deprecation_message(callstack, message).tap do |m| @@ -46,10 +52,14 @@ module ActiveSupport end def extract_callstack(callstack) - if md = callstack.first.match(/^(.+?):(\d+)(?::in `(.*?)')?/) - md.captures - else - callstack.first + rails_gem_root = File.expand_path("../../../../..", __FILE__) + "/" + offending_line = callstack.find { |line| !line.start_with?(rails_gem_root) } || callstack.first + if offending_line + if md = offending_line.match(/^(.+?):(\d+)(?::in `(.*?)')?/) + md.captures + else + offending_line + end end end end diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb index 7da357730b..00c67a470d 100644 --- a/activesupport/lib/active_support/duration.rb +++ b/activesupport/lib/active_support/duration.rb @@ -3,7 +3,7 @@ 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 + # Provides accurate date and time measurements using Date#advance and # Time#advance, respectively. It mainly supports the methods on Numeric. # Example: # @@ -80,6 +80,10 @@ module ActiveSupport parts.to_sentence(:locale => :en) end + def as_json(options = nil) #:nodoc: + to_i + end + protected def sum(sign, time = ::Time.current) #:nodoc: @@ -99,7 +103,7 @@ module ActiveSupport private def method_missing(method, *args, &block) #:nodoc: - value.send(method, *args) + value.send(method, *args, &block) end end end diff --git a/activesupport/lib/active_support/file_update_checker.rb b/activesupport/lib/active_support/file_update_checker.rb index cd658fe173..a97e9d7daf 100644 --- a/activesupport/lib/active_support/file_update_checker.rb +++ b/activesupport/lib/active_support/file_update_checker.rb @@ -8,7 +8,7 @@ module ActiveSupport # I18n.reload! # end # - # ActionDispatch::Callbacks.to_prepare do + # ActionDispatch::Reloader.to_prepare do # i18n_reloader.execute_if_updated # end # diff --git a/activesupport/lib/active_support/file_watcher.rb b/activesupport/lib/active_support/file_watcher.rb new file mode 100644 index 0000000000..81e63e76a7 --- /dev/null +++ b/activesupport/lib/active_support/file_watcher.rb @@ -0,0 +1,36 @@ +module ActiveSupport + class FileWatcher + class Backend + def initialize(path, watcher) + @watcher = watcher + @path = path + end + + def trigger(files) + @watcher.trigger(files) + end + end + + def initialize + @regex_matchers = {} + end + + def watch(pattern, &block) + @regex_matchers[pattern] = block + end + + def trigger(files) + trigger_files = Hash.new { |h,k| h[k] = Hash.new { |h2,k2| h2[k2] = [] } } + + files.each do |file, state| + @regex_matchers.each do |pattern, block| + trigger_files[block][state] << file if pattern === file + end + end + + trigger_files.each do |block, payload| + block.call payload + end + end + end +end diff --git a/activesupport/lib/active_support/gzip.rb b/activesupport/lib/active_support/gzip.rb index 35a50e9a77..62f9c9aa2e 100644 --- a/activesupport/lib/active_support/gzip.rb +++ b/activesupport/lib/active_support/gzip.rb @@ -5,6 +5,10 @@ module ActiveSupport # A convenient wrapper for the zlib standard library that allows compression/decompression of strings with gzip. module Gzip class Stream < StringIO + def initialize(*) + super + set_encoding "BINARY" if "".encoding_aware? + end def close; rewind; end end @@ -22,4 +26,4 @@ module ActiveSupport output.string end end -end
\ No newline at end of file +end diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb index eec5d4cf47..79a0de7940 100644 --- a/activesupport/lib/active_support/hash_with_indifferent_access.rb +++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb @@ -1,8 +1,7 @@ -require 'active_support/core_ext/hash/indifferent_access' require 'active_support/core_ext/hash/keys' # This class has dubious semantics and we only have it so that -# people can write params[:key] instead of params['key'] +# people can write <tt>params[:key]</tt> instead of <tt>params['key']</tt> # and they get the same value for both keys. module ActiveSupport @@ -28,6 +27,12 @@ module ActiveSupport end end + def self.new_from_hash_copying_default(hash) + new(hash).tap do |new_hash| + new_hash.default = hash.default + end + end + alias_method :regular_writer, :[]= unless method_defined?(:regular_writer) alias_method :regular_update, :update unless method_defined?(:regular_update) @@ -40,6 +45,8 @@ module ActiveSupport regular_writer(convert_key(key), convert_value(value)) end + alias_method :store, :[]= + # Updates the instantized hash with values from the second: # # hash_1 = HashWithIndifferentAccess.new @@ -90,7 +97,9 @@ module ActiveSupport # Returns an exact copy of the hash. def dup - HashWithIndifferentAccess.new(self) + self.class.new(self).tap do |new_hash| + new_hash.default = default + end end # Merges the instantized and the specified hashes together, giving precedence to the values from the second hash @@ -100,9 +109,9 @@ module ActiveSupport 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 HashWithDifferentAccess. + # This overloaded definition prevents returning a regular hash, if reverse_merge is called on a <tt>HashWithDifferentAccess</tt>. def reverse_merge(other_hash) - super other_hash.with_indifferent_access + super self.class.new_from_hash_copying_default(other_hash) end def reverse_merge!(other_hash) @@ -131,11 +140,10 @@ module ActiveSupport end def convert_value(value) - case value - when Hash - value.with_indifferent_access - when Array - value.collect { |e| e.is_a?(Hash) ? e.with_indifferent_access : e } + if value.class == Hash + self.class.new_from_hash_copying_default(value) + elsif value.is_a?(Array) + value.dup.replace(value.map { |e| convert_value(e) }) else value end diff --git a/activesupport/lib/active_support/i18n.rb b/activesupport/lib/active_support/i18n.rb index 45b9d20c01..00ea8813dd 100644 --- a/activesupport/lib/active_support/i18n.rb +++ b/activesupport/lib/active_support/i18n.rb @@ -1,9 +1,9 @@ begin require 'i18n' + require 'active_support/lazy_load_hooks' rescue LoadError => e $stderr.puts "You don't have i18n installed in your application. Please add it to your Gemfile and run bundle install" raise e end I18n.load_path << "#{File.dirname(__FILE__)}/locale/en.yml" -ActiveSupport.run_load_hooks(:i18n) diff --git a/activesupport/lib/active_support/i18n_railtie.rb b/activesupport/lib/active_support/i18n_railtie.rb index db09919fd3..a25e951080 100644 --- a/activesupport/lib/active_support/i18n_railtie.rb +++ b/activesupport/lib/active_support/i18n_railtie.rb @@ -14,19 +14,34 @@ module I18n @reloader ||= ActiveSupport::FileUpdateChecker.new([]){ I18n.reload! } end - # Add I18n::Railtie.reloader to ActionDispatch callbacks. Since, at this + # 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 - ActionDispatch::Callbacks.to_prepare do + ActionDispatch::Reloader.to_prepare do I18n::Railtie.reloader.execute_if_updated end end - # Set the i18n configuration only after initialization since a lot of + # Set the i18n configuration after initialization since a lot of # configuration is still usually done in application initializers. config.after_initialize do |app| + I18n::Railtie.initialize_i18n(app) + end + + # Trigger i18n config before any eager loading has happened + # so it's ready if any classes require it when eager loaded + config.before_eager_load do |app| + I18n::Railtie.initialize_i18n(app) + end + + protected + + # Setup i18n configuration + def self.initialize_i18n(app) + return if @i18n_inited + fallbacks = app.config.i18n.delete(:fallbacks) app.config.i18n.each do |setting, value| @@ -44,9 +59,9 @@ module I18n reloader.paths.concat I18n.load_path reloader.execute_if_updated - end - protected + @i18n_inited = true + end def self.include_fallbacks_module I18n.backend.class.send(:include, I18n::Backend::Fallbacks) @@ -66,7 +81,7 @@ module I18n I18n.fallbacks = I18n::Locale::Fallbacks.new(*args) end - + def self.validate_fallbacks(fallbacks) case fallbacks when ActiveSupport::OrderedOptions @@ -78,4 +93,4 @@ module I18n end end end -end
\ No newline at end of file +end diff --git a/activesupport/lib/active_support/inflections.rb b/activesupport/lib/active_support/inflections.rb index e7b5387ed7..06ceccdb22 100644 --- a/activesupport/lib/active_support/inflections.rb +++ b/activesupport/lib/active_support/inflections.rb @@ -4,10 +4,12 @@ module ActiveSupport inflect.plural(/s$/i, 's') 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') inflect.plural(/(bu)s$/i, '\1ses') inflect.plural(/(buffal|tomat)o$/i, '\1oes') inflect.plural(/([ti])um$/i, '\1a') + inflect.plural(/([ti])a$/i, '\1a') inflect.plural(/sis$/i, 'ses') inflect.plural(/(?:([^f])fe|([lr])f)$/i, '\1\2ves') inflect.plural(/(hive)$/i, '\1s') @@ -15,7 +17,9 @@ module ActiveSupport 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(/^(ox)$/i, '\1en') + inflect.plural(/^(oxen)$/i, '\1') inflect.plural(/(quiz)$/i, '\1zes') inflect.singular(/s$/i, '') diff --git a/activesupport/lib/active_support/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb index 3caf78bc7d..e136e4c5b3 100644 --- a/activesupport/lib/active_support/inflector/inflections.rb +++ b/activesupport/lib/active_support/inflector/inflections.rb @@ -148,7 +148,7 @@ module ActiveSupport def singularize(word) result = word.to_s.dup - if inflections.uncountables.any? { |inflection| result =~ /#{inflection}\Z/i } + if inflections.uncountables.any? { |inflection| result =~ /\b(#{inflection})\Z/i } result else inflections.singulars.each { |(rule, replacement)| break if result.gsub!(rule, replacement) } diff --git a/activesupport/lib/active_support/inflector/transliterate.rb b/activesupport/lib/active_support/inflector/transliterate.rb index bccc5425a6..40e7a0e389 100644 --- a/activesupport/lib/active_support/inflector/transliterate.rb +++ b/activesupport/lib/active_support/inflector/transliterate.rb @@ -1,5 +1,6 @@ # encoding: utf-8 require 'active_support/core_ext/string/multibyte' +require 'active_support/i18n' module ActiveSupport module Inflector diff --git a/activesupport/lib/active_support/json/backends/jsongem.rb b/activesupport/lib/active_support/json/backends/jsongem.rb index cfe28d7bb9..533ba25da3 100644 --- a/activesupport/lib/active_support/json/backends/jsongem.rb +++ b/activesupport/lib/active_support/json/backends/jsongem.rb @@ -26,7 +26,11 @@ module ActiveSupport when nil nil when DATE_REGEX - DateTime.parse(data) + begin + DateTime.parse(data) + rescue ArgumentError + data + end when Array data.map! { |d| convert_dates_from(d) } when Hash diff --git a/activesupport/lib/active_support/json/backends/yajl.rb b/activesupport/lib/active_support/json/backends/yajl.rb index 64e50e0d87..58818658c7 100644 --- a/activesupport/lib/active_support/json/backends/yajl.rb +++ b/activesupport/lib/active_support/json/backends/yajl.rb @@ -23,7 +23,11 @@ module ActiveSupport when nil nil when DATE_REGEX - DateTime.parse(data) + begin + DateTime.parse(data) + rescue ArgumentError + data + end when Array data.map! { |d| convert_dates_from(d) } when Hash diff --git a/activesupport/lib/active_support/json/backends/yaml.rb b/activesupport/lib/active_support/json/backends/yaml.rb index 4cb9d01077..e25e29d36b 100644 --- a/activesupport/lib/active_support/json/backends/yaml.rb +++ b/activesupport/lib/active_support/json/backends/yaml.rb @@ -7,14 +7,21 @@ module ActiveSupport ParseError = ::StandardError extend self + EXCEPTIONS = [::ArgumentError] # :nodoc: + begin + require 'psych' + EXCEPTIONS << Psych::SyntaxError + rescue LoadError + end + # Parses a JSON string or IO and converts it into an object def decode(json) if json.respond_to?(:read) json = json.read end YAML.load(convert_json_to_yaml(json)) - rescue ArgumentError - raise ParseError, "Invalid JSON string" + rescue *EXCEPTIONS => e + raise ParseError, "Invalid JSON string: '%s'" % json end protected @@ -22,21 +29,21 @@ module ActiveSupport def convert_json_to_yaml(json) #:nodoc: require 'strscan' unless defined? ::StringScanner scanner, quoting, marks, pos, times = ::StringScanner.new(json), false, [], nil, [] - while scanner.scan_until(/(\\['"]|['":,\\]|\\.)/) + while scanner.scan_until(/(\\['"]|['":,\\]|\\.|[\]])/) case char = scanner[1] when '"', "'" if !quoting quoting = char pos = scanner.pos elsif quoting == char - if json[pos..scanner.pos-2] =~ DATE_REGEX + if valid_date?(json[pos..scanner.pos-2]) # found a date, track the exact positions of the quotes so we can # overwrite them with spaces later. - times << pos << scanner.pos + times << pos end quoting = false end - when ":","," + when ":",",", "]" marks << scanner.pos - 1 unless quoting when "\\" scanner.skip(/\\/) @@ -47,7 +54,9 @@ module ActiveSupport json.gsub(/\\([\\\/]|u[[:xdigit:]]{4})/) do ustr = $1 if ustr.start_with?('u') - [ustr[1..-1].to_i(16)].pack("U") + char = [ustr[1..-1].to_i(16)].pack("U") + # "\n" needs extra escaping due to yaml formatting + char == "\n" ? "\\n" : char elsif ustr == '\\' '\\\\' else @@ -61,14 +70,18 @@ module ActiveSupport left_pos.each_with_index do |left, i| scanner.pos = left.succ chunk = scanner.peek(right_pos[i] - scanner.pos + 1) - # overwrite the quotes found around the dates with spaces - while times.size > 0 && times[0] <= right_pos[i] - chunk[times.shift - scanner.pos - 1] = ' ' + if ActiveSupport.parse_json_times + # overwrite the quotes found around the dates with spaces + while times.size > 0 && times[0] <= right_pos[i] + chunk.insert(times.shift - scanner.pos - 1, '! ') + end end chunk.gsub!(/\\([\\\/]|u[[:xdigit:]]{4})/) do ustr = $1 if ustr.start_with?('u') - [ustr[1..-1].to_i(16)].pack("U") + char = [ustr[1..-1].to_i(16)].pack("U") + # "\n" needs extra escaping due to yaml formatting + char == "\n" ? "\\n" : char elsif ustr == '\\' '\\\\' else @@ -83,6 +96,16 @@ module ActiveSupport output end end + + private + def valid_date?(date_string) + begin + date_string =~ DATE_REGEX && DateTime.parse(date_string) + rescue ArgumentError + false + end + end + end end end diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb index 2f9588e0f4..82b8a7e148 100644 --- a/activesupport/lib/active_support/json/encoding.rb +++ b/activesupport/lib/active_support/json/encoding.rb @@ -23,7 +23,7 @@ module ActiveSupport module JSON # matches YAML-formatted dates - DATE_REGEX = /^(?:\d{4}-\d{2}-\d{2}|\d{4}-\d{1,2}-\d{1,2}[ \t]+\d{1,2}:\d{2}:\d{2}(\.[0-9]*)?(([ \t]*)Z|[-+]\d{2}?(:\d{2})?))$/ + 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. def self.encode(value, options = nil) @@ -41,9 +41,26 @@ module ActiveSupport @seen = [] end - def encode(value) + def encode(value, use_options = true) check_for_circular_references(value) do - value.as_json(options).encode_json(self) + jsonified = use_options ? value.as_json(options_for(value)) : value.as_json + jsonified.encode_json(self) + end + end + + # like encode, but only calls as_json, without encoding to string + def as_json(value) + check_for_circular_references(value) do + value.as_json(options_for(value)) + end + end + + def options_for(value) + if value.is_a?(Array) || value.is_a?(Hash) + # hashes and arrays need to get encoder in the options, so that they can detect circular references + (options || {}).merge(:encoder => self) + else + options end end @@ -136,6 +153,12 @@ class Object end end +class Struct + def as_json(options = nil) #:nodoc: + Hash[members.zip(values)] + end +end + class TrueClass AS_JSON = ActiveSupport::JSON::Variable.new('true').freeze def as_json(options = nil) AS_JSON end #:nodoc: @@ -186,13 +209,22 @@ module Enumerable end class Array - def as_json(options = nil) self end #:nodoc: - def encode_json(encoder) "[#{map { |v| encoder.encode(v) } * ','}]" end #:nodoc: + def as_json(options = nil) #:nodoc: + # use encoder as a proxy to call as_json on all elements, to protect from circular references + encoder = options && options[:encoder] || ActiveSupport::JSON::Encoding::Encoder.new(options) + map { |v| encoder.as_json(v) } + end + + def encode_json(encoder) #:nodoc: + # we assume here that the encoder has already run as_json on self and the elements, so we run encode_json directly + "[#{map { |v| v.encode_json(encoder) } * ','}]" + end end class Hash def as_json(options = nil) #:nodoc: - if options + # create a subset of the hash by applying :only or :except + subset = if options if attrs = options[:only] slice(*Array.wrap(attrs)) elsif attrs = options[:except] @@ -203,10 +235,21 @@ class Hash else self end + + # use encoder as a proxy to call as_json on all values in the subset, to protect from circular references + encoder = options && options[:encoder] || ActiveSupport::JSON::Encoding::Encoder.new(options) + result = self.is_a?(ActiveSupport::OrderedHash) ? ActiveSupport::OrderedHash : Hash + result[subset.map { |k, v| [k.to_s, encoder.as_json(v)] }] end def encode_json(encoder) - "{#{map { |k,v| "#{encoder.encode(k.to_s)}:#{encoder.encode(v)}" } * ','}}" + # values are encoded with use_options = false, because we don't want hash representations from ActiveModel to be + # processed once again with as_json with options, as this could cause unexpected results (i.e. missing fields); + + # on the other hand, we need to run as_json on the elements, because the model representation may contain fields + # like Time/Date in their original (not jsonified) form, etc. + + "{#{map { |k,v| "#{encoder.encode(k.to_s)}:#{encoder.encode(v, false)}" } * ','}}" end end diff --git a/activesupport/lib/active_support/lazy_load_hooks.rb b/activesupport/lib/active_support/lazy_load_hooks.rb index ef43fc0431..82507c1e03 100644 --- a/activesupport/lib/active_support/lazy_load_hooks.rb +++ b/activesupport/lib/active_support/lazy_load_hooks.rb @@ -1,21 +1,21 @@ -# lazy_load_hooks allows rails to lazily load a lot of components and thus making the app boot faster. Because of +# 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 +# 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. +# 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) +# ActiveSupport.run_load_hooks(:active_record, ActiveRecord::Base) # module ActiveSupport @load_hooks = Hash.new {|h,k| h[k] = [] } diff --git a/activesupport/lib/active_support/locale/en.yml b/activesupport/lib/active_support/locale/en.yml index 8f08f37a70..a1499bcc90 100644 --- a/activesupport/lib/active_support/locale/en.yml +++ b/activesupport/lib/active_support/locale/en.yml @@ -7,10 +7,10 @@ en: default: "%Y-%m-%d" short: "%b %d" long: "%B %d, %Y" - + day_names: [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday] abbr_day_names: [Sun, Mon, Tue, Wed, Thu, Fri, Sat] - + # Don't forget the nil at the beginning; there's no such thing as a 0th month month_names: [~, January, February, March, April, May, June, July, August, September, October, November, December] abbr_month_names: [~, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec] @@ -27,7 +27,7 @@ en: long: "%B %d, %Y %H:%M" am: "am" pm: "pm" - + # Used in array.to_sentence. support: array: diff --git a/activesupport/lib/active_support/log_subscriber.rb b/activesupport/lib/active_support/log_subscriber.rb index 83930b3f0d..10675edac5 100644 --- a/activesupport/lib/active_support/log_subscriber.rb +++ b/activesupport/lib/active_support/log_subscriber.rb @@ -4,7 +4,7 @@ require 'active_support/core_ext/class/attribute' module ActiveSupport # ActiveSupport::LogSubscriber is an object set to consume ActiveSupport::Notifications # with solely purpose of logging. The log subscriber dispatches notifications to a - # regirested object based on its given namespace. + # registered object based on its given namespace. # # An example would be Active Record log subscriber responsible for logging queries: # @@ -16,12 +16,12 @@ module ActiveSupport # end # end # - # And it's finally registed as: + # And it's finally registered as: # # 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 ActiveRecord::LogSubscriber definition. + # 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 @@ -31,23 +31,10 @@ module ActiveSupport # all logs when the request finishes (via action_dispatch.callback notification) in # a Rails environment. class LogSubscriber - mattr_accessor :colorize_logging - self.colorize_logging = true - - class_attribute :logger - - class << self - remove_method :logger - end - - def self.logger - @logger ||= Rails.logger if defined?(Rails) - end - # Embed in a String to clear all previous ANSI sequences. CLEAR = "\e[0m" BOLD = "\e[1m" - + # Colors BLACK = "\e[30m" RED = "\e[31m" @@ -58,32 +45,44 @@ module ActiveSupport CYAN = "\e[36m" WHITE = "\e[37m" - def self.attach_to(namespace, log_subscriber=new, notifier=ActiveSupport::Notifications) - log_subscribers << log_subscriber - @@flushable_loggers = nil + 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 + log_subscriber.public_methods(false).each do |event| + next if 'call' == event.to_s - notifier.subscribe("#{event}.#{namespace}", log_subscriber) + notifier.subscribe("#{event}.#{namespace}", log_subscriber) + end end - end - def self.log_subscribers - @@log_subscribers ||= [] - end + def log_subscribers + @@log_subscribers ||= [] + end - def self.flushable_loggers - @@flushable_loggers ||= begin - loggers = log_subscribers.map(&:logger) - loggers.uniq! - loggers.select { |l| l.respond_to?(:flush) } + def flushable_loggers + @@flushable_loggers ||= begin + loggers = log_subscribers.map(&:logger) + loggers.uniq! + loggers.select { |l| l.respond_to?(:flush) } + end end - end - # Flush all log_subscribers' logger. - def self.flush_all! - flushable_loggers.each(&:flush) + # Flush all log_subscribers' logger. + def flush_all! + flushable_loggers.each(&:flush) + end end def call(message, *args) diff --git a/activesupport/lib/active_support/log_subscriber/test_helper.rb b/activesupport/lib/active_support/log_subscriber/test_helper.rb index 9e52cb97a9..392e33edbc 100644 --- a/activesupport/lib/active_support/log_subscriber/test_helper.rb +++ b/activesupport/lib/active_support/log_subscriber/test_helper.rb @@ -12,7 +12,7 @@ module ActiveSupport # def setup # ActiveRecord::LogSubscriber.attach_to(:active_record) # end - # + # # def test_basic_query_logging # Developer.all # wait @@ -23,11 +23,11 @@ module ActiveSupport # 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 is reponsible for setting + # 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 do not send anything to your output), and you can collect them + # 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. # @@ -38,13 +38,14 @@ module ActiveSupport ActiveSupport::LogSubscriber.colorize_logging = false + @old_notifier = ActiveSupport::Notifications.notifier set_logger(@logger) ActiveSupport::Notifications.notifier = @notifier end def teardown set_logger(nil) - ActiveSupport::Notifications.notifier = nil + ActiveSupport::Notifications.notifier = @old_notifier end class MockLogger diff --git a/activesupport/lib/active_support/memoizable.rb b/activesupport/lib/active_support/memoizable.rb index 9fb506fea1..0a7bcd5bb8 100644 --- a/activesupport/lib/active_support/memoizable.rb +++ b/activesupport/lib/active_support/memoizable.rb @@ -95,6 +95,8 @@ module ActiveSupport # 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 diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb index 51fa626b45..4f7cd12d48 100644 --- a/activesupport/lib/active_support/message_encryptor.rb +++ b/activesupport/lib/active_support/message_encryptor.rb @@ -2,12 +2,12 @@ require 'openssl' require 'active_support/base64' module ActiveSupport - # MessageEncryptor is a simple way to encrypt values which get stored somewhere + # 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. # - # This can be used in situations similar to the MessageVerifier, but where you don't + # 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. class MessageEncryptor class InvalidMessage < StandardError; end @@ -17,53 +17,53 @@ module ActiveSupport @secret = secret @cipher = cipher end - + def encrypt(value) cipher = new_cipher # Rely on OpenSSL for the initialization vector iv = cipher.random_iv - - cipher.encrypt + + cipher.encrypt cipher.key = @secret cipher.iv = iv - - encrypted_data = cipher.update(Marshal.dump(value)) + + encrypted_data = cipher.update(Marshal.dump(value)) encrypted_data << cipher.final [encrypted_data, iv].map {|v| ActiveSupport::Base64.encode64s(v)}.join("--") end - + def decrypt(encrypted_message) cipher = new_cipher encrypted_data, iv = encrypted_message.split("--").map {|v| ActiveSupport::Base64.decode64(v)} - + cipher.decrypt cipher.key = @secret cipher.iv = iv decrypted_data = cipher.update(encrypted_data) decrypted_data << cipher.final - + Marshal.load(decrypted_data) rescue OpenSSLCipherError, TypeError raise InvalidMessage end - + def encrypt_and_sign(value) verifier.generate(encrypt(value)) end - + def decrypt_and_verify(value) decrypt(verifier.verify(value)) end - - - - private + + + + private def new_cipher OpenSSL::Cipher::Cipher.new(@cipher) end - + def verifier MessageVerifier.new(@secret) end diff --git a/activesupport/lib/active_support/message_verifier.rb b/activesupport/lib/active_support/message_verifier.rb index 6c46b68eaf..8f3946325a 100644 --- a/activesupport/lib/active_support/message_verifier.rb +++ b/activesupport/lib/active_support/message_verifier.rb @@ -2,30 +2,30 @@ require 'active_support/base64' require 'active_support/core_ext/object/blank' module ActiveSupport - # MessageVerifier makes it easy to generate and verify messages which are signed + # +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. # # Remember Me: # cookies[:remember_me] = @verifier.generate([@user.id, 2.weeks.from_now]) - # + # # In the authentication filter: # # id, time = @verifier.verify(cookies[:remember_me]) # if time < Time.now # self.current_user = User.find(id) # end - # + # class MessageVerifier class InvalidSignature < StandardError; end - + def initialize(secret, digest = 'SHA1') @secret = secret @digest = digest end - + def verify(signed_message) raise InvalidSignature if signed_message.blank? @@ -36,12 +36,12 @@ module ActiveSupport raise InvalidSignature end end - + def generate(value) data = ActiveSupport::Base64.encode64s(Marshal.dump(value)) "#{data}--#{generate_digest(data)}" end - + private # constant-time comparison algorithm to prevent timing attacks def secure_compare(a, b) diff --git a/activesupport/lib/active_support/multibyte.rb b/activesupport/lib/active_support/multibyte.rb index 8ffdf5a1bf..57e8e24bf4 100644 --- a/activesupport/lib/active_support/multibyte.rb +++ b/activesupport/lib/active_support/multibyte.rb @@ -6,7 +6,7 @@ module ActiveSupport #:nodoc: 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. diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb index 019fb2df06..f9607f1aaf 100644 --- a/activesupport/lib/active_support/multibyte/chars.rb +++ b/activesupport/lib/active_support/multibyte/chars.rb @@ -64,7 +64,7 @@ module ActiveSupport #:nodoc: # 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) || false + super || @wrapped_string.respond_to?(method, include_private) end # Enable more predictable duck-typing on String-like classes. See Object#acts_like?. @@ -270,13 +270,14 @@ module ActiveSupport #:nodoc: @wrapped_string[*args] = replace_by else result = Unicode.u_unpack(@wrapped_string) - if args[0].is_a?(Fixnum) + 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) - elsif args.first.is_a?(Range) + when Range raise RangeError, "#{args[0]} out of range" if args[0].min >= result.length range = args[0] else @@ -437,7 +438,7 @@ module ActiveSupport #:nodoc: begin @wrapped_string[0...byte_offset].unpack('U*').length - rescue ArgumentError => e + rescue ArgumentError byte_offset -= 1 retry end diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb index 1139783b65..513f83e445 100644 --- a/activesupport/lib/active_support/multibyte/unicode.rb +++ b/activesupport/lib/active_support/multibyte/unicode.rb @@ -247,7 +247,7 @@ module ActiveSupport if is_unused || is_restricted bytes[i] = tidy_byte(byte) elsif is_cont - # Not expecting contination byte? Clean up. Otherwise, now expect one less. + # Not expecting continuation byte? Clean up. Otherwise, now expect one less. conts_expected == 0 ? bytes[i] = tidy_byte(byte) : conts_expected -= 1 else if conts_expected > 0 diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb index fd79188ba4..77696eb1db 100644 --- a/activesupport/lib/active_support/notifications.rb +++ b/activesupport/lib/active_support/notifications.rb @@ -44,8 +44,11 @@ module ActiveSupport @instrumenters = Hash.new { |h,k| h[k] = notifier.listening?(k) } class << self - attr_writer :notifier - delegate :publish, :to => :notifier + attr_accessor :notifier + + def publish(name, *args) + notifier.publish(name, *args) + end def instrument(name, payload = {}) if @instrumenters[name] @@ -61,18 +64,16 @@ module ActiveSupport end end - def unsubscribe(*args) - notifier.unsubscribe(*args) + def unsubscribe(args) + notifier.unsubscribe(args) @instrumenters.clear end - def notifier - @notifier ||= Fanout.new - end - def instrumenter Thread.current[:"instrumentation_#{notifier.object_id}"] ||= Instrumenter.new(notifier) end end + + self.notifier = Fanout.new end end diff --git a/activesupport/lib/active_support/ordered_hash.rb b/activesupport/lib/active_support/ordered_hash.rb index 2e8d538d0b..fbc40d1b69 100644 --- a/activesupport/lib/active_support/ordered_hash.rb +++ b/activesupport/lib/active_support/ordered_hash.rb @@ -1,3 +1,8 @@ +begin + require 'psych' +rescue LoadError +end + require 'yaml' YAML.add_builtin_type("omap") do |type, val| @@ -8,21 +13,29 @@ 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: - # + # # 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. class OrderedHash < ::Hash #:nodoc: def to_yaml_type "!tag:yaml.org,2002:omap" end + def encode_with(coder) + 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, to_yaml_style) do |seq| + out.seq(taguri) do |seq| each do |k, v| seq.add(k => v) end @@ -79,7 +92,7 @@ module ActiveSupport end def []=(key, value) - @keys << key if !has_key?(key) + @keys << key unless has_key?(key) super end @@ -124,19 +137,27 @@ module ActiveSupport 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 alias_method :each_pair, :each + alias_method :select, :find_all + def clear super @keys.clear diff --git a/activesupport/lib/active_support/ordered_options.rb b/activesupport/lib/active_support/ordered_options.rb index 7fc2b45b51..8d8e6ebc58 100644 --- a/activesupport/lib/active_support/ordered_options.rb +++ b/activesupport/lib/active_support/ordered_options.rb @@ -1,23 +1,26 @@ require 'active_support/ordered_hash' # Usually key value pairs are handled something like this: -# -# h = ActiveSupport::OrderedOptions.new +# +# h = {} # h[:boy] = 'John' # h[:girl] = 'Mary' # h[:boy] # => 'John' # h[:girl] # => 'Mary' -# -# Using <tt>OrderedOptions</tt> above code could be reduced to: +# +# 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 + alias_method :_get, :[] # preserve the original #[] method + protected :_get # make it protected + def []=(key, value) super(key.to_sym, value) end @@ -28,7 +31,7 @@ module ActiveSupport #:nodoc: def method_missing(name, *args) if name.to_s =~ /(.*)=$/ - self[$1.to_sym] = args.first + self[$1] = args.first else self[name] end @@ -36,8 +39,19 @@ module ActiveSupport #:nodoc: end class InheritableOptions < OrderedOptions - def initialize(parent) - super() { |h,k| parent[k] } + def initialize(parent = nil) + if parent.kind_of?(OrderedOptions) + # use the faster _get when dealing with OrderedOptions + super() { |h,k| parent._get(k) } + elsif parent + super() { |h,k| parent[k] } + else + super() + end + end + + def inheritable_copy + self.class.new(self) end end end diff --git a/activesupport/lib/active_support/rescuable.rb b/activesupport/lib/active_support/rescuable.rb index cd6f92cdfe..0f4a06468a 100644 --- a/activesupport/lib/active_support/rescuable.rb +++ b/activesupport/lib/active_support/rescuable.rb @@ -47,6 +47,7 @@ module ActiveSupport # exception.record.new_record? ? ... # end # end + # def rescue_from(*klasses, &block) options = klasses.extract_options! diff --git a/activesupport/lib/active_support/secure_random.rb b/activesupport/lib/active_support/secure_random.rb index 73344498cb..368954907f 100644 --- a/activesupport/lib/active_support/secure_random.rb +++ b/activesupport/lib/active_support/secure_random.rb @@ -46,8 +46,10 @@ module ActiveSupport # p SecureRandom.random_bytes(10) # => "\016\t{\370g\310pbr\301" # p SecureRandom.random_bytes(10) # => "\323U\030TO\234\357\020\a\337" # ... + # module SecureRandom - # SecureRandom.random_bytes generates a random binary string. + + # Generates a random binary string. # # The argument n specifies the length of the result string. # @@ -56,6 +58,7 @@ module ActiveSupport # # If secure random number generator is not available, # NotImplementedError is raised. + # def self.random_bytes(n=nil) n ||= 16 @@ -92,7 +95,7 @@ module ActiveSupport end end - if !defined?(@has_win32) + unless defined?(@has_win32) begin require 'Win32API' @@ -124,7 +127,7 @@ module ActiveSupport raise NotImplementedError, "No random device" end - # SecureRandom.hex generates a random hex string. + # Generates a random hex string. # # The argument n specifies the length of the random length. # The length of the result string is twice of n. @@ -134,11 +137,12 @@ module ActiveSupport # # If secure random number generator is not available, # NotImplementedError is raised. + # def self.hex(n=nil) random_bytes(n).unpack("H*")[0] end - # SecureRandom.base64 generates a random base64 string. + # Generates a random base64 string. # # The argument n specifies the length of the random length. # The length of the result string is about 4/3 of n. @@ -148,11 +152,12 @@ module ActiveSupport # # If secure random number generator is not available, # NotImplementedError is raised. + # def self.base64(n=nil) [random_bytes(n)].pack("m*").delete("\n") end - # SecureRandom.random_number generates a random number. + # Generates a random number. # # If an positive integer is given as n, # SecureRandom.random_number returns an integer: @@ -161,6 +166,7 @@ module ActiveSupport # If 0 is given or an argument is not given, # SecureRandom.random_number returns an float: # 0.0 <= SecureRandom.random_number() < 1.0. + # def self.random_number(n=0) if 0 < n hex = n.to_s(16) diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb index ed8c02ba3e..fb52fc7083 100644 --- a/activesupport/lib/active_support/test_case.rb +++ b/activesupport/lib/active_support/test_case.rb @@ -24,8 +24,7 @@ module ActiveSupport else Assertion = Test::Unit::AssertionFailedError - require 'active_support/testing/default' - include ActiveSupport::Testing::Default + undef :default_test end $tags = {} diff --git a/activesupport/lib/active_support/testing/assertions.rb b/activesupport/lib/active_support/testing/assertions.rb index 33793f0688..a4e0361a32 100644 --- a/activesupport/lib/active_support/testing/assertions.rb +++ b/activesupport/lib/active_support/testing/assertions.rb @@ -63,19 +63,21 @@ module ActiveSupport 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. # # assert_blank [] # => true - def assert_blank(object) - assert object.blank?, "#{object.inspect} is not blank" + def assert_blank(object, message=nil) + message ||= "#{object.inspect} is not blank" + assert object.blank?, message end - + # Test if an expression is not blank. Passes if object.present? is true. # # assert_present {:data => 'x' } # => true - def assert_present(object) - assert object.present?, "#{object.inspect} is blank" + def assert_present(object, message=nil) + message ||= "#{object.inspect} is blank" + assert object.present?, message end end end diff --git a/activesupport/lib/active_support/testing/declarative.rb b/activesupport/lib/active_support/testing/declarative.rb index 70a6c2ca60..1c05d45888 100644 --- a/activesupport/lib/active_support/testing/declarative.rb +++ b/activesupport/lib/active_support/testing/declarative.rb @@ -1,10 +1,10 @@ module ActiveSupport module Testing module Declarative - + def self.extended(klass) klass.class_eval do - + unless method_defined?(:describe) def self.describe(text) class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 @@ -14,9 +14,9 @@ module ActiveSupport RUBY_EVAL end end - + end - end + end unless defined?(Spec) # test "verify something" do diff --git a/activesupport/lib/active_support/testing/default.rb b/activesupport/lib/active_support/testing/default.rb deleted file mode 100644 index a0bd6303c7..0000000000 --- a/activesupport/lib/active_support/testing/default.rb +++ /dev/null @@ -1,9 +0,0 @@ -module ActiveSupport - module Testing - module Default #:nodoc: - # Placeholder so test/unit ignores test cases without any tests. - def default_test - end - end - end -end diff --git a/activesupport/lib/active_support/testing/isolation.rb b/activesupport/lib/active_support/testing/isolation.rb index d629f6f2b7..6b29ba4c10 100644 --- a/activesupport/lib/active_support/testing/isolation.rb +++ b/activesupport/lib/active_support/testing/isolation.rb @@ -34,7 +34,7 @@ module ActiveSupport module Isolation def self.forking_env? - !ENV["NO_FORK"] && ((Config::CONFIG['host_os'] !~ /mswin|mingw/) && (RUBY_PLATFORM !~ /java/)) + !ENV["NO_FORK"] && ((RbConfig::CONFIG['host_os'] !~ /mswin|mingw/) && (RUBY_PLATFORM !~ /java/)) end def self.included(base) diff --git a/activesupport/lib/active_support/testing/pending.rb b/activesupport/lib/active_support/testing/pending.rb index 21134ff9e2..3d119e2fba 100644 --- a/activesupport/lib/active_support/testing/pending.rb +++ b/activesupport/lib/active_support/testing/pending.rb @@ -1,5 +1,5 @@ # Some code from jeremymcanally's "pending" -# http://github.com/jeremymcanally/pending/tree/master +# https://github.com/jeremymcanally/pending/tree/master module ActiveSupport module Testing @@ -25,13 +25,13 @@ module ActiveSupport failed = true end - flunk("<#{description}> did not fail.") unless failed + 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:" @@ -42,7 +42,7 @@ module ActiveSupport end end end - + end end -end
\ No newline at end of file +end diff --git a/activesupport/lib/active_support/testing/performance.rb b/activesupport/lib/active_support/testing/performance.rb index f7ddf6421d..8c91a061fb 100644 --- a/activesupport/lib/active_support/testing/performance.rb +++ b/activesupport/lib/active_support/testing/performance.rb @@ -58,16 +58,16 @@ begin metric.send(mode) { __send__ @method_name } rescue ::Test::Unit::AssertionFailedError => e add_failure(e.message, e.backtrace) - rescue StandardError, ScriptError - add_error($!) + 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 - add_error($!) + rescue StandardError, ScriptError => e + add_error(e) end end @@ -401,7 +401,7 @@ begin Mode = RubyProf::GC_TIME if RubyProf.const_defined?(:GC_TIME) # Ruby 1.9 with GC::Profiler - if GC.respond_to?(:total_time) + if defined?(GC::Profiler) && GC::Profiler.respond_to?(:total_time) def measure GC::Profiler.total_time end diff --git a/activesupport/lib/active_support/testing/setup_and_teardown.rb b/activesupport/lib/active_support/testing/setup_and_teardown.rb index b2d9ddfeb7..22e41fa905 100644 --- a/activesupport/lib/active_support/testing/setup_and_teardown.rb +++ b/activesupport/lib/active_support/testing/setup_and_teardown.rb @@ -31,14 +31,14 @@ module ActiveSupport def run(runner) result = '.' begin - _run_setup_callbacks do + run_callbacks :setup do result = super end rescue Exception => e result = runner.puke(self.class, method_name, e) ensure begin - _run_teardown_callbacks + run_callbacks :teardown rescue Exception => e result = runner.puke(self.class, method_name, e) end @@ -62,7 +62,7 @@ module ActiveSupport begin begin - _run_setup_callbacks do + run_callbacks :setup do setup __send__(@method_name) mocha_verify(mocha_counter) if mocha_counter @@ -77,7 +77,7 @@ module ActiveSupport ensure begin teardown - _run_teardown_callbacks + run_callbacks :teardown rescue Test::Unit::AssertionFailedError => e add_failure(e.message, e.backtrace) rescue Exception => e diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index ad6c3de1f5..c66aa78ce8 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -18,7 +18,7 @@ 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. + # TimeWithZone instances implement the same API as Ruby Time instances, so that Time and TimeWithZone instances are interchangeable. # Examples: # # t = Time.zone.now # => Sun, 18 May 2008 13:27:25 EDT -04:00 @@ -32,11 +32,12 @@ module ActiveSupport # t > Time.utc(1999) # => true # t.is_a?(Time) # => true # t.is_a?(ActiveSupport::TimeWithZone) # => true + # class TimeWithZone def self.name 'Time' # Report class name as 'Time' to thwart type checking end - + include Comparable attr_reader :time_zone @@ -72,7 +73,7 @@ module ActiveSupport # Returns a <tt>Time.local()</tt> instance of the simultaneous time in your system's <tt>ENV['TZ']</tt> zone def localtime - utc.getlocal + utc.respond_to?(:getlocal) ? utc.getlocal : utc.to_time.getlocal end alias_method :getlocal, :localtime @@ -127,6 +128,7 @@ 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 @@ -135,14 +137,20 @@ module ActiveSupport end end - def to_yaml(options = {}) - if options.kind_of?(YAML::Emitter) - utc.to_yaml(options) + def encode_with(coder) + if coder.respond_to?(:represent_object) + coder.represent_object(nil, utc) else - time.to_yaml(options).gsub('Z', formatted_offset(true, 'Z')) + coder.represent_scalar(nil, utc.strftime("%Y-%m-%d %H:%M:%S.%9NZ")) end end + def to_yaml(options = {}) + return super if defined?(YAML::ENGINE) && !YAML::ENGINE.syck? + + utc.to_yaml(options) + end + def httpdate utc.httpdate end @@ -273,7 +281,7 @@ module ActiveSupport # A TimeWithZone acts like a Time, so just return +self+. def to_time - self + utc end def to_datetime diff --git a/activesupport/lib/active_support/version.rb b/activesupport/lib/active_support/version.rb index 9d2cf13260..690fc7f0fc 100644 --- a/activesupport/lib/active_support/version.rb +++ b/activesupport/lib/active_support/version.rb @@ -1,10 +1,10 @@ module ActiveSupport module VERSION #:nodoc: MAJOR = 3 - MINOR = 0 + MINOR = 1 TINY = 0 - BUILD = "rc" + PRE = "beta" - STRING = [MAJOR, MINOR, TINY, BUILD].join('.') + STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') end end diff --git a/activesupport/lib/active_support/whiny_nil.rb b/activesupport/lib/active_support/whiny_nil.rb index 91ddef2619..bcedbfd57a 100644 --- a/activesupport/lib/active_support/whiny_nil.rb +++ b/activesupport/lib/active_support/whiny_nil.rb @@ -37,7 +37,7 @@ class NilClass # Raises a RuntimeError when you attempt to call +id+ on +nil+. def id - raise RuntimeError, "Called id for nil, which would mistakenly be 4 -- if you really wanted the id of nil, use object_id", caller + 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 private diff --git a/activesupport/lib/active_support/xml_mini.rb b/activesupport/lib/active_support/xml_mini.rb index 352172027b..cddfcddb57 100644 --- a/activesupport/lib/active_support/xml_mini.rb +++ b/activesupport/lib/active_support/xml_mini.rb @@ -25,7 +25,7 @@ module ActiveSupport DEFAULT_ENCODINGS = { "binary" => "base64" - } unless defined?(TYPE_NAMES) + } unless defined?(DEFAULT_ENCODINGS) TYPE_NAMES = { "Symbol" => "symbol", @@ -126,9 +126,11 @@ module ActiveSupport end def rename_key(key, options = {}) - camelize = options.has_key?(:camelize) && options[:camelize] + camelize = options[:camelize] dasherize = !options.has_key?(:dasherize) || options[:dasherize] - key = key.camelize if camelize + if camelize + key = true == camelize ? key.camelize : key.camelize(camelize) + end key = _dasherize(key) if dasherize key end diff --git a/activesupport/lib/active_support/xml_mini/libxml.rb b/activesupport/lib/active_support/xml_mini/libxml.rb index 7fdcb11465..16570c6aea 100644 --- a/activesupport/lib/active_support/xml_mini/libxml.rb +++ b/activesupport/lib/active_support/xml_mini/libxml.rb @@ -1,5 +1,6 @@ require 'libxml' require 'active_support/core_ext/object/blank' +require 'stringio' # = XmlMini LibXML implementation module ActiveSupport diff --git a/activesupport/lib/active_support/xml_mini/libxmlsax.rb b/activesupport/lib/active_support/xml_mini/libxmlsax.rb index fe2c1b9349..2536b1f33e 100644 --- a/activesupport/lib/active_support/xml_mini/libxmlsax.rb +++ b/activesupport/lib/active_support/xml_mini/libxmlsax.rb @@ -1,5 +1,6 @@ require 'libxml' require 'active_support/core_ext/object/blank' +require 'stringio' # = XmlMini LibXML implementation using a SAX-based parser module ActiveSupport @@ -82,4 +83,4 @@ module ActiveSupport end end end -end
\ No newline at end of file +end diff --git a/activesupport/lib/active_support/xml_mini/nokogiri.rb b/activesupport/lib/active_support/xml_mini/nokogiri.rb index e03a744257..04ec9e8ab8 100644 --- a/activesupport/lib/active_support/xml_mini/nokogiri.rb +++ b/activesupport/lib/active_support/xml_mini/nokogiri.rb @@ -5,6 +5,7 @@ rescue LoadError => e raise e end require 'active_support/core_ext/object/blank' +require 'stringio' # = XmlMini Nokogiri implementation module ActiveSupport diff --git a/activesupport/lib/active_support/xml_mini/nokogirisax.rb b/activesupport/lib/active_support/xml_mini/nokogirisax.rb index 38c8685390..93fd3dfe57 100644 --- a/activesupport/lib/active_support/xml_mini/nokogirisax.rb +++ b/activesupport/lib/active_support/xml_mini/nokogirisax.rb @@ -5,6 +5,7 @@ rescue LoadError => e raise e end require 'active_support/core_ext/object/blank' +require 'stringio' # = XmlMini Nokogiri implementation using a SAX-based parser module ActiveSupport @@ -38,8 +39,7 @@ module ActiveSupport end def start_element(name, attrs = []) - new_hash = { CONTENT_KEY => '' } - new_hash[attrs.shift] = attrs.shift while attrs.length > 0 + new_hash = { CONTENT_KEY => '' }.merge(Hash[attrs]) new_hash[HASH_SIZE_KEY] = new_hash.size + 1 case current_hash[name] diff --git a/activesupport/lib/active_support/xml_mini/rexml.rb b/activesupport/lib/active_support/xml_mini/rexml.rb index a58f22ee5d..a13ad10118 100644 --- a/activesupport/lib/active_support/xml_mini/rexml.rb +++ b/activesupport/lib/active_support/xml_mini/rexml.rb @@ -1,5 +1,6 @@ require 'active_support/core_ext/kernel/reporting' require 'active_support/core_ext/object/blank' +require 'stringio' # = XmlMini ReXML implementation module ActiveSupport @@ -19,7 +20,7 @@ module ActiveSupport if !data.respond_to?(:read) data = StringIO.new(data || '') end - + char = data.getc if char.nil? {} diff --git a/activesupport/test/abstract_unit.rb b/activesupport/test/abstract_unit.rb index 6db21f9e12..0382739871 100644 --- a/activesupport/test/abstract_unit.rb +++ b/activesupport/test/abstract_unit.rb @@ -10,8 +10,19 @@ end lib = File.expand_path("#{File.dirname(__FILE__)}/../lib") $:.unshift(lib) unless $:.include?('lib') || $:.include?(lib) -require 'test/unit' require 'active_support/core_ext/kernel/reporting' + +require 'active_support/core_ext/string/encoding' +if "ruby".encoding_aware? + # These are the normal settings that will be set up by Railties + # TODO: Have these tests support other combinations of these values + silence_warnings do + Encoding.default_internal = "UTF-8" + Encoding.default_external = "UTF-8" + end +end + +require 'test/unit' require 'empty_bool' silence_warnings { require 'mocha' } diff --git a/activesupport/test/autoloading_fixtures/class_folder/nested_class.rb b/activesupport/test/autoloading_fixtures/class_folder/nested_class.rb index 77ea230d2c..57a13d89ea 100644 --- a/activesupport/test/autoloading_fixtures/class_folder/nested_class.rb +++ b/activesupport/test/autoloading_fixtures/class_folder/nested_class.rb @@ -1,7 +1,7 @@ class ClassFolder class NestedClass end - + class SiblingClass end end diff --git a/activesupport/test/buffered_logger_test.rb b/activesupport/test/buffered_logger_test.rb index 97c0ef14db..8d1b1c02c6 100644 --- a/activesupport/test/buffered_logger_test.rb +++ b/activesupport/test/buffered_logger_test.rb @@ -115,11 +115,9 @@ class BufferedLoggerTest < Test::Unit::TestCase def test_should_create_the_log_directory_if_it_doesnt_exist tmp_directory = File.join(File.dirname(__FILE__), "tmp") log_file = File.join(tmp_directory, "development.log") - assert !File.exist?(tmp_directory) + FileUtils.rm_rf(tmp_directory) @logger = Logger.new(log_file) assert File.exist?(tmp_directory) - ensure - FileUtils.rm_rf(tmp_directory) end def test_logger_should_maintain_separate_buffers_for_each_thread diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb index b79a7bbaec..e5668e29d7 100644 --- a/activesupport/test/caching_test.rb +++ b/activesupport/test/caching_test.rb @@ -356,9 +356,13 @@ module CacheDeleteMatchedBehavior def test_delete_matched @cache.write("foo", "bar") @cache.write("fu", "baz") + @cache.write("foo/bar", "baz") + @cache.write("fu/baz", "bar") @cache.delete_matched(/oo/) assert_equal false, @cache.exist?("foo") assert_equal true, @cache.exist?("fu") + assert_equal false, @cache.exist?("foo/bar") + assert_equal true, @cache.exist?("fu/baz") end end @@ -509,6 +513,11 @@ class FileStoreTest < ActiveSupport::TestCase assert_nil old_cache.read('foo') end end + + def test_key_transformation + key = @cache.send(:key_file_path, "views/index?id=1") + assert_equal "views/index?id=1", @cache.send(:file_path_key, key) + end end class MemoryStoreTest < ActiveSupport::TestCase @@ -649,12 +658,12 @@ class CacheStoreLoggerTest < ActiveSupport::TestCase def test_logging @cache.fetch('foo') { 'bar' } - assert @buffer.string.present? + assert_present @buffer.string end def test_mute_logging @cache.mute { @cache.fetch('foo') { 'bar' } } - assert @buffer.string.blank? + assert_blank @buffer.string end end @@ -670,12 +679,12 @@ class CacheEntryTest < ActiveSupport::TestCase def test_expired entry = ActiveSupport::Cache::Entry.new("value") - assert_equal false, entry.expired? + assert !entry.expired?, 'entry not expired' entry = ActiveSupport::Cache::Entry.new("value", :expires_in => 60) - assert_equal false, entry.expired? + assert !entry.expired?, 'entry not expired' time = Time.now + 61 Time.stubs(:now).returns(time) - assert_equal true, entry.expired? + assert entry.expired?, 'entry is expired' end def test_compress_values diff --git a/activesupport/test/callback_inheritance_test.rb b/activesupport/test/callback_inheritance_test.rb index 8caf000c5d..d569cbb4fb 100644 --- a/activesupport/test/callback_inheritance_test.rb +++ b/activesupport/test/callback_inheritance_test.rb @@ -70,7 +70,7 @@ class EmptyParent end def dispatch - _run_dispatch_callbacks + run_callbacks :dispatch self end end @@ -82,6 +82,30 @@ class EmptyChild < EmptyParent end end +class CountingParent + include ActiveSupport::Callbacks + + attr_reader :count + + define_callbacks :dispatch + + def initialize + @count = 0 + end + + def count! + @count += 1 + end + + def dispatch + run_callbacks(:dispatch) + self + end +end + +class CountingChild < CountingParent +end + class BasicCallbacksTest < Test::Unit::TestCase def setup @index = GrandParent.new("index").dispatch @@ -147,4 +171,10 @@ class DynamicInheritedCallbacks < Test::Unit::TestCase child = EmptyChild.new.dispatch assert child.performed? end + + def test_callbacks_should_be_performed_once_in_child_class + CountingParent.set_callback(:dispatch, :before) { count! } + child = CountingChild.new.dispatch + assert_equal 1, child.count + end end diff --git a/activesupport/test/callbacks_test.rb b/activesupport/test/callbacks_test.rb index 70a2950f9b..816dcad968 100644 --- a/activesupport/test/callbacks_test.rb +++ b/activesupport/test/callbacks_test.rb @@ -3,6 +3,27 @@ require 'test/unit' require 'active_support' module CallbacksTest + class Phone + include ActiveSupport::Callbacks + define_callbacks :save, :rescuable => true + + set_callback :save, :before, :before_save1 + set_callback :save, :after, :after_save1 + + def before_save1; self.history << :before; end + def after_save1; self.history << :after; end + + def save + run_callbacks :save do + raise 'boom' + end + end + + def history + @history ||= [] + end + end + class Record include ActiveSupport::Callbacks @@ -149,6 +170,27 @@ module CallbacksTest end end + class AfterSaveConditionalPerson < Record + after_save Proc.new { |r| r.history << [:after_save, :string1] } + after_save Proc.new { |r| r.history << [:after_save, :string2] } + def save + run_callbacks :save + end + end + + class AfterSaveConditionalPersonCallbackTest < Test::Unit::TestCase + def test_after_save_runs_in_the_reverse_order + person = AfterSaveConditionalPerson.new + person.save + assert_equal [ + [:after_save, :string2], + [:after_save, :string1] + ], person.history + end + end + + + class ConditionalPerson < Record # proc before_save Proc.new { |r| r.history << [:before_save, :proc] }, :if => Proc.new { |r| true } @@ -257,6 +299,32 @@ module CallbacksTest end end end + + class AroundPersonResult < MySuper + attr_reader :result + + set_callback :save, :after, :tweedle_1 + set_callback :save, :around, :tweedle_dum + set_callback :save, :after, :tweedle_2 + + def tweedle_dum + @result = yield + end + + def tweedle_1 + :tweedle_1 + end + + def tweedle_2 + :tweedle_2 + end + + def save + run_callbacks :save do + :running + end + end + end class HyphenatedCallbacks include ActiveSupport::Callbacks @@ -296,6 +364,14 @@ module CallbacksTest ], around.history end end + + class AroundCallbackResultTest < Test::Unit::TestCase + def test_save_around + around = AroundPersonResult.new + around.save + assert_equal :running, around.result + end + end class SkipCallbacksTest < Test::Unit::TestCase def test_skip_person @@ -317,6 +393,14 @@ module CallbacksTest end class CallbacksTest < Test::Unit::TestCase + def test_save_phone + phone = Phone.new + assert_raise RuntimeError do + phone.save + end + assert_equal [:before, :after], phone.history + end + def test_save_person person = Person.new assert_equal [], person.history @@ -352,6 +436,8 @@ module CallbacksTest end end + + class ResetCallbackTest < Test::Unit::TestCase def test_save_conditional_person person = CleanPerson.new @@ -524,4 +610,31 @@ module CallbacksTest assert_equal "ACTION", obj.stuff end end + + class WriterSkipper < Person + attr_accessor :age + skip_callback :save, :before, :before_save_method, :if => lambda {self.age > 21} + end + + class WriterCallbacksTest < Test::Unit::TestCase + def test_skip_writer + writer = WriterSkipper.new + writer.age = 18 + assert_equal [], writer.history + writer.save + assert_equal [ + [:before_save, :symbol], + [:before_save, :string], + [:before_save, :proc], + [:before_save, :object], + [:before_save, :block], + [:after_save, :block], + [:after_save, :object], + [:after_save, :proc], + [:after_save, :string], + [:after_save, :symbol] + ], writer.history + end + end + end diff --git a/activesupport/test/class_cache_test.rb b/activesupport/test/class_cache_test.rb new file mode 100644 index 0000000000..8445af8d25 --- /dev/null +++ b/activesupport/test/class_cache_test.rb @@ -0,0 +1,108 @@ +require 'abstract_unit' +require 'active_support/dependencies' + +module ActiveSupport + module Dependencies + class ClassCacheTest < ActiveSupport::TestCase + def setup + @cache = ClassCache.new + end + + def test_empty? + assert @cache.empty? + @cache[ClassCacheTest] = ClassCacheTest + assert !@cache.empty? + end + + def test_clear! + assert @cache.empty? + @cache[ClassCacheTest] = ClassCacheTest + assert !@cache.empty? + @cache.clear! + assert @cache.empty? + end + + def test_set_key + @cache[ClassCacheTest] = ClassCacheTest + assert @cache.key?(ClassCacheTest.name) + end + + def test_set_rejects_strings + @cache[ClassCacheTest.name] = ClassCacheTest + assert @cache.empty? + end + + def test_get_with_class + @cache[ClassCacheTest] = ClassCacheTest + assert_equal ClassCacheTest, @cache[ClassCacheTest] + end + + def test_get_with_name + @cache[ClassCacheTest] = ClassCacheTest + assert_equal ClassCacheTest, @cache[ClassCacheTest.name] + end + + def test_get_constantizes + assert @cache.empty? + assert_equal ClassCacheTest, @cache[ClassCacheTest.name] + end + + def test_get_is_an_alias + assert_equal @cache[ClassCacheTest], @cache.get(ClassCacheTest.name) + end + + def test_new + assert_deprecated do + @cache.new ClassCacheTest + end + assert @cache.key?(ClassCacheTest.name) + end + + def test_new_rejects_strings + assert_deprecated do + @cache.new ClassCacheTest.name + end + assert !@cache.key?(ClassCacheTest.name) + end + + def test_new_rejects_strings + @cache.store ClassCacheTest.name + assert !@cache.key?(ClassCacheTest.name) + end + + def test_store_returns_self + x = @cache.store ClassCacheTest + assert_equal @cache, x + end + + def test_new_returns_proxy + v = nil + assert_deprecated do + v = @cache.new ClassCacheTest.name + end + + assert_deprecated do + assert_equal ClassCacheTest, v.get + end + end + + def test_anonymous_class_fail + assert_raises(ArgumentError) do + assert_deprecated do + @cache.new Class.new + end + end + + assert_raises(ArgumentError) do + x = Class.new + @cache[x] = x + end + + assert_raises(ArgumentError) do + x = Class.new + @cache.store x + end + end + end + end +end diff --git a/activesupport/test/clean_backtrace_test.rb b/activesupport/test/clean_backtrace_test.rb index 86838a7f9a..32e346bb48 100644 --- a/activesupport/test/clean_backtrace_test.rb +++ b/activesupport/test/clean_backtrace_test.rb @@ -5,7 +5,7 @@ class BacktraceCleanerFilterTest < ActiveSupport::TestCase @bc = ActiveSupport::BacktraceCleaner.new @bc.add_filter { |line| line.gsub("/my/prefix", '') } end - + test "backtrace should not contain prefix when it has been filtered out" do assert_equal "/my/class.rb", @bc.clean([ "/my/prefix/my/class.rb" ]).first end @@ -14,14 +14,14 @@ class BacktraceCleanerFilterTest < ActiveSupport::TestCase @bc.remove_filters! assert_equal "/my/prefix/my/class.rb", @bc.clean(["/my/prefix/my/class.rb"]).first end - + test "backtrace should contain unaltered lines if they dont match a filter" do assert_equal "/my/other_prefix/my/class.rb", @bc.clean([ "/my/other_prefix/my/class.rb" ]).first end - + test "backtrace should filter all lines in a backtrace" do assert_equal \ - ["/my/class.rb", "/my/module.rb"], + ["/my/class.rb", "/my/module.rb"], @bc.clean([ "/my/prefix/my/class.rb", "/my/prefix/my/module.rb" ]) end end @@ -31,10 +31,10 @@ class BacktraceCleanerSilencerTest < ActiveSupport::TestCase @bc = ActiveSupport::BacktraceCleaner.new @bc.add_silencer { |line| line =~ /mongrel/ } end - + test "backtrace should not contain lines that match the silencer" do assert_equal \ - [ "/other/class.rb" ], + [ "/other/class.rb" ], @bc.clean([ "/mongrel/class.rb", "/other/class.rb", "/mongrel/stuff.rb" ]) end end @@ -45,7 +45,7 @@ class BacktraceCleanerFilterAndSilencerTest < ActiveSupport::TestCase @bc.add_filter { |line| line.gsub("/mongrel", "") } @bc.add_silencer { |line| line =~ /mongrel/ } end - + test "backtrace should not silence lines that has first had their silence hook filtered out" do assert_equal [ "/class.rb" ], @bc.clean([ "/mongrel/class.rb" ]) end diff --git a/activesupport/test/clean_logger_test.rb b/activesupport/test/clean_logger_test.rb index 6c4ec5ac46..2cc46904b4 100644 --- a/activesupport/test/clean_logger_test.rb +++ b/activesupport/test/clean_logger_test.rb @@ -41,7 +41,7 @@ class CleanLoggerTest < Test::Unit::TestCase assert_equal "error\nfatal\nerror\nfatal\nunsilenced\n", @out.string end - + def test_datetime_format @logger.formatter = Logger::Formatter.new @logger.datetime_format = "%Y-%m-%d" @@ -49,7 +49,7 @@ class CleanLoggerTest < Test::Unit::TestCase assert_equal "%Y-%m-%d", @logger.datetime_format assert_match(/D, \[\d\d\d\d-\d\d-\d\d#\d+\] DEBUG -- : debug/, @out.string) end - + def test_nonstring_formatting an_object = [1, 2, 3, 4, 5] @logger.debug an_object diff --git a/activesupport/test/configurable_test.rb b/activesupport/test/configurable_test.rb index cef67e3cf9..2b28e61815 100644 --- a/activesupport/test/configurable_test.rb +++ b/activesupport/test/configurable_test.rb @@ -21,6 +21,12 @@ class ConfigurableActiveSupport < ActiveSupport::TestCase assert_equal({ :foo => :bar }, Parent.config) end + test "adds a configuration hash to a module as well" do + mixin = Module.new { include ActiveSupport::Configurable } + mixin.config.foo = :bar + assert_equal({ :foo => :bar }, mixin.config) + end + test "configuration hash is inheritable" do assert_equal :bar, Child.config.foo assert_equal :bar, Parent.config.foo @@ -39,4 +45,22 @@ class ConfigurableActiveSupport < ActiveSupport::TestCase assert_equal :baz, instance.config.foo assert_equal :bar, Parent.config.foo end -end
\ No newline at end of file + + test "configuration is crystalizeable" do + parent = Class.new { include ActiveSupport::Configurable } + child = Class.new(parent) + + parent.config.bar = :foo + assert !parent.config.respond_to?(:bar) + assert !child.config.respond_to?(:bar) + assert !child.new.config.respond_to?(:bar) + + parent.config.compile_methods! + assert_equal :foo, parent.config.bar + assert_equal :foo, child.new.config.bar + + assert_respond_to parent.config, :bar + assert_respond_to child.config, :bar + assert_respond_to child.new.config, :bar + end +end diff --git a/activesupport/test/core_ext/array_ext_test.rb b/activesupport/test/core_ext/array_ext_test.rb index 009a254c64..d7ab3ce605 100644 --- a/activesupport/test/core_ext/array_ext_test.rb +++ b/activesupport/test/core_ext/array_ext_test.rb @@ -18,10 +18,10 @@ class ArrayExtAccessTests < Test::Unit::TestCase assert_equal %w( a b c ), %w( a b c d ).to(2) assert_equal %w( a b c d ), %w( a b c d ).to(10) end - + def test_second_through_tenth array = (1..42).to_a - + assert_equal array[1], array.second assert_equal array[2], array.third assert_equal array[3], array.fourth @@ -82,6 +82,11 @@ class ArrayExtToSentenceTests < Test::Unit::TestCase assert_equal "one", ['one'].to_sentence end + def test_one_element_not_same_object + elements = ["one"] + assert_not_equal elements[0].object_id, elements.to_sentence.object_id + end + def test_one_non_string_element assert_equal '1', [1].to_sentence end @@ -319,7 +324,7 @@ class ArrayExtractOptionsTests < Test::Unit::TestCase assert_equal({}, options) assert_equal [hash], array end - + def test_extract_options_extracts_extractable_subclass hash = ExtractableHashSubclass.new hash[:foo] = 1 @@ -370,14 +375,14 @@ class ArrayExtRandomTests < ActiveSupport::TestCase assert_equal 2, s.size assert_equal 1, (a-s).size assert_equal [], a-(0..20).sum{a.sample(2)} - + o = Object.new def o.to_int; 1; end assert_equal [0], [0].sample(o) - + o = Object.new assert_raises(TypeError) { [0].sample(o) } - + o = Object.new def o.to_int; ''; end assert_raises(TypeError) { [0].sample(o) } diff --git a/activesupport/test/core_ext/bigdecimal.rb b/activesupport/test/core_ext/bigdecimal.rb deleted file mode 100644 index 9faad9146f..0000000000 --- a/activesupport/test/core_ext/bigdecimal.rb +++ /dev/null @@ -1,10 +0,0 @@ -require 'abstract_unit' - -class BigDecimalTest < Test::Unit::TestCase - def test_to_yaml - assert_equal("--- 100000.30020320320000000000000000000000000000001\n", BigDecimal.new('100000.30020320320000000000000000000000000000001').to_yaml) - assert_equal("--- .Inf\n", BigDecimal.new('Infinity').to_yaml) - assert_equal("--- .NaN\n", BigDecimal.new('NaN').to_yaml) - assert_equal("--- -.Inf\n", BigDecimal.new('-Infinity').to_yaml) - end -end
\ No newline at end of file diff --git a/activesupport/test/core_ext/bigdecimal_test.rb b/activesupport/test/core_ext/bigdecimal_test.rb new file mode 100644 index 0000000000..b38e08a9f4 --- /dev/null +++ b/activesupport/test/core_ext/bigdecimal_test.rb @@ -0,0 +1,17 @@ +require 'abstract_unit' +require 'bigdecimal' +require 'active_support/core_ext/big_decimal' + +class BigDecimalTest < Test::Unit::TestCase + def test_to_yaml + assert_match("--- 100000.30020320320000000000000000000000000000001\n", BigDecimal.new('100000.30020320320000000000000000000000000000001').to_yaml) + assert_match("--- .Inf\n", BigDecimal.new('Infinity').to_yaml) + assert_match("--- .NaN\n", BigDecimal.new('NaN').to_yaml) + assert_match("--- -.Inf\n", BigDecimal.new('-Infinity').to_yaml) + end + + def test_to_d + bd = BigDecimal.new '10' + assert_equal bd, bd.to_d + end +end diff --git a/activesupport/test/core_ext/cgi_ext_test.rb b/activesupport/test/core_ext/cgi_ext_test.rb deleted file mode 100644 index c80362e382..0000000000 --- a/activesupport/test/core_ext/cgi_ext_test.rb +++ /dev/null @@ -1,15 +0,0 @@ -require 'abstract_unit' -require 'active_support/core_ext/cgi' - -class EscapeSkippingSlashesTest < Test::Unit::TestCase - def test_array - assert_equal 'hello/world', CGI.escape_skipping_slashes(%w(hello world)) - assert_equal 'hello+world/how/are/you', CGI.escape_skipping_slashes(['hello world', 'how', 'are', 'you']) - end - - def test_typical - assert_equal 'hi', CGI.escape_skipping_slashes('hi') - assert_equal 'hi/world', CGI.escape_skipping_slashes('hi/world') - assert_equal 'hi/world+you+funky+thing', CGI.escape_skipping_slashes('hi/world you funky thing') - end -end diff --git a/activesupport/test/core_ext/class/attribute_accessor_test.rb b/activesupport/test/core_ext/class/attribute_accessor_test.rb index 2c896d0cdb..456f4b7948 100644 --- a/activesupport/test/core_ext/class/attribute_accessor_test.rb +++ b/activesupport/test/core_ext/class/attribute_accessor_test.rb @@ -10,27 +10,27 @@ class ClassAttributeAccessorTest < Test::Unit::TestCase end @object = @class.new end - + def test_should_use_mattr_default assert_nil @class.foo assert_nil @object.foo end - + def test_should_set_mattr_value @class.foo = :test assert_equal :test, @object.foo - + @object.foo = :test2 assert_equal :test2, @class.foo end - + def test_should_not_create_instance_writer assert_respond_to @class, :foo assert_respond_to @class, :foo= assert_respond_to @object, :bar assert !@object.respond_to?(:bar=) end - + def test_should_not_create_instance_reader assert_respond_to @class, :shaq assert !@object.respond_to?(:shaq) diff --git a/activesupport/test/core_ext/class/attribute_test.rb b/activesupport/test/core_ext/class/attribute_test.rb index 24aa5c0eba..d58b60482b 100644 --- a/activesupport/test/core_ext/class/attribute_test.rb +++ b/activesupport/test/core_ext/class/attribute_test.rb @@ -65,4 +65,9 @@ class ClassAttributeTest < ActiveSupport::TestCase object.singleton_class.setting = 'foo' assert_equal 'foo', object.setting end + + test 'setter returns set value' do + val = @klass.send(:setting=, 1) + assert_equal 1, val + end end diff --git a/activesupport/test/core_ext/class/class_inheritable_attributes_test.rb b/activesupport/test/core_ext/class/class_inheritable_attributes_test.rb index 63ea46b564..020dfce56a 100644 --- a/activesupport/test/core_ext/class/class_inheritable_attributes_test.rb +++ b/activesupport/test/core_ext/class/class_inheritable_attributes_test.rb @@ -3,9 +3,14 @@ require 'active_support/core_ext/class/inheritable_attributes' class ClassInheritableAttributesTest < Test::Unit::TestCase def setup + ActiveSupport::Deprecation.silenced = true @klass = Class.new end + def teardown + ActiveSupport::Deprecation.silenced = false + end + def test_reader_declaration assert_nothing_raised do @klass.class_inheritable_reader :a @@ -21,7 +26,7 @@ class ClassInheritableAttributesTest < Test::Unit::TestCase assert_respond_to @klass.new, :a= end end - + def test_writer_declaration_without_instance_writer assert_nothing_raised do @klass.class_inheritable_writer :a, :instance_writer => false @@ -39,7 +44,7 @@ class ClassInheritableAttributesTest < Test::Unit::TestCase assert_respond_to @klass.new, :a= end end - + def test_accessor_declaration_without_instance_writer assert_nothing_raised do @klass.class_inheritable_accessor :a, :instance_writer => false @@ -176,37 +181,37 @@ class ClassInheritableAttributesTest < Test::Unit::TestCase assert_equal 'b', @klass.b assert_equal 'B', @sub.b end - + def test_array_inheritance @klass.class_inheritable_accessor :a @klass.a = [] @sub = eval("class SubbyArray < @klass; end; SubbyArray") - + assert_equal [], @klass.a assert_equal [], @sub.a - + @sub.a << :first - + assert_equal [:first], @sub.a assert_equal [], @klass.a end - + def test_array_inheritance_ @klass.class_inheritable_accessor :a @klass.a = {} @sub = eval("class SubbyHash < @klass; end; SubbyHash") - + assert_equal Hash.new, @klass.a assert_equal Hash.new, @sub.a - + @sub.a[:first] = :first - + assert_equal 1, @sub.a.keys.size assert_equal 0, @klass.a.keys.size end - + def test_reset_inheritable_attributes @klass.class_inheritable_accessor :a @klass.a = 'a' diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb index 59c168d33d..d81693209f 100644 --- a/activesupport/test/core_ext/date_ext_test.rb +++ b/activesupport/test/core_ext/date_ext_test.rb @@ -26,7 +26,7 @@ class DateExtCalculationsTest < ActiveSupport::TestCase assert_equal year, Date.new(year).to_time(format).year end end - end + end end def test_to_datetime @@ -110,6 +110,14 @@ class DateExtCalculationsTest < ActiveSupport::TestCase assert_equal Date.new(2005,1,1).to_s, Date.new(2005,2,22).beginning_of_year.to_s end + def test_weeks_ago + assert_equal Date.new(2005,5,10), Date.new(2005,5,17).weeks_ago(1) + assert_equal Date.new(2005,5,10), Date.new(2005,5,24).weeks_ago(2) + assert_equal Date.new(2005,5,10), Date.new(2005,5,31).weeks_ago(3) + assert_equal Date.new(2005,5,10), Date.new(2005,6,7).weeks_ago(4) + assert_equal Date.new(2006,12,31), Date.new(2007,2,4).weeks_ago(5) + end + def test_months_ago assert_equal Date.new(2005,5,5), Date.new(2005,6,5).months_ago(1) assert_equal Date.new(2004,11,5), Date.new(2005,6,5).months_ago(7) @@ -219,6 +227,14 @@ class DateExtCalculationsTest < ActiveSupport::TestCase end end + def test_prev_week + assert_equal Date.new(2005,5,9), Date.new(2005,5,17).prev_week + assert_equal Date.new(2006,12,25), Date.new(2007,1,7).prev_week + assert_equal Date.new(2010,2,12), Date.new(2010,2,19).prev_week(:friday) + assert_equal Date.new(2010,2,13), Date.new(2010,2,19).prev_week(:saturday) + assert_equal Date.new(2010,2,27), Date.new(2010,3,4).prev_week(:saturday) + end + def test_next_week assert_equal Date.new(2005,2,28), Date.new(2005,2,22).next_week assert_equal Date.new(2005,3,4), Date.new(2005,2,22).next_week(:friday) @@ -242,17 +258,16 @@ class DateExtCalculationsTest < ActiveSupport::TestCase def test_yesterday_constructor assert_equal Date.current - 1, Date.yesterday end - - def test_yesterday_constructor_when_zone_default_is_not_set + + def test_yesterday_constructor_when_zone_is_not_set with_env_tz 'UTC' do with_tz_default do - Time.stubs(:now).returns Time.local(2000, 1, 1) - assert_equal Date.new(1999, 12, 31), Date.yesterday + assert_equal(Date.today - 1, Date.yesterday) end end end - def test_yesterday_constructor_when_zone_default_is_set + def test_yesterday_constructor_when_zone_is_set with_env_tz 'UTC' do with_tz_default ActiveSupport::TimeZone['Eastern Time (US & Canada)'] do # UTC -5 Time.stubs(:now).returns Time.local(2000, 1, 1) @@ -265,16 +280,15 @@ class DateExtCalculationsTest < ActiveSupport::TestCase assert_equal Date.current + 1, Date.tomorrow end - def test_tomorrow_constructor_when_zone_default_is_not_set + def test_tomorrow_constructor_when_zone_is_not_set with_env_tz 'UTC' do with_tz_default do - Time.stubs(:now).returns Time.local(1999, 12, 31) - assert_equal Date.new(2000, 1, 1), Date.tomorrow + assert_equal(Date.today + 1, Date.tomorrow) end end end - def test_tomorrow_constructor_when_zone_default_is_set + def test_tomorrow_constructor_when_zone_is_set with_env_tz 'UTC' do with_tz_default ActiveSupport::TimeZone['Europe/Paris'] do # UTC +1 Time.stubs(:now).returns Time.local(1999, 12, 31, 23) @@ -286,8 +300,8 @@ class DateExtCalculationsTest < ActiveSupport::TestCase def test_since assert_equal Time.local(2005,2,21,0,0,45), Date.new(2005,2,21).since(45) end - - def test_since_when_zone_default_is_set + + def test_since_when_zone_is_set zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] with_env_tz 'UTC' do with_tz_default zone do @@ -300,8 +314,8 @@ class DateExtCalculationsTest < ActiveSupport::TestCase def test_ago assert_equal Time.local(2005,2,20,23,59,15), Date.new(2005,2,21).ago(45) end - - def test_ago_when_zone_default_is_set + + def test_ago_when_zone_is_set zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] with_env_tz 'UTC' do with_tz_default zone do @@ -314,8 +328,8 @@ class DateExtCalculationsTest < ActiveSupport::TestCase def test_beginning_of_day assert_equal Time.local(2005,2,21,0,0,0), Date.new(2005,2,21).beginning_of_day end - - def test_beginning_of_day_when_zone_default_is_set + + def test_beginning_of_day_when_zone_is_set zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] with_env_tz 'UTC' do with_tz_default zone do @@ -328,8 +342,8 @@ class DateExtCalculationsTest < ActiveSupport::TestCase def test_end_of_day assert_equal Time.local(2005,2,21,23,59,59,999999.999), Date.new(2005,2,21).end_of_day end - - def test_end_of_day_when_zone_default_is_set + + def test_end_of_day_when_zone_is_set zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] with_env_tz 'UTC' do with_tz_default zone do @@ -338,7 +352,7 @@ class DateExtCalculationsTest < ActiveSupport::TestCase end end end - + def test_xmlschema with_env_tz 'US/Eastern' do assert_match(/^1980-02-28T00:00:00-05:?00$/, Date.new(1980, 2, 28).xmlschema) @@ -350,8 +364,8 @@ class DateExtCalculationsTest < ActiveSupport::TestCase end end end - - def test_xmlschema_when_zone_default_is_set + + def test_xmlschema_when_zone_is_set with_env_tz 'UTC' do with_tz_default ActiveSupport::TimeZone['Eastern Time (US & Canada)'] do # UTC -5 assert_match(/^1980-02-28T00:00:00-05:?00$/, Date.new(1980, 2, 28).xmlschema) @@ -360,6 +374,16 @@ class DateExtCalculationsTest < ActiveSupport::TestCase end end + if RUBY_VERSION < '1.9' + def test_rfc3339 + assert_equal('1980-02-28', Date.new(1980, 2, 28).rfc3339) + end + + def test_iso8601 + assert_equal('1980-02-28', Date.new(1980, 2, 28).iso8601) + end + end + def test_today Date.stubs(:current).returns(Date.new(2000, 1, 1)) assert_equal false, Date.new(1999, 12, 31).today? @@ -381,23 +405,20 @@ class DateExtCalculationsTest < ActiveSupport::TestCase assert_equal true, Date.new(2000,1,2).future? end - def test_current_returns_date_today_when_zone_default_not_set + def test_current_returns_date_today_when_zone_not_set with_env_tz 'US/Central' do Time.stubs(:now).returns Time.local(1999, 12, 31, 23) - assert_equal Date.new(1999, 12, 31), Date.today - assert_equal Date.new(1999, 12, 31), Date.current + assert_equal Date.today, Date.current end end - def test_current_returns_time_zone_today_when_zone_default_set - Time.zone_default = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] + def test_current_returns_time_zone_today_when_zone_is_set + Time.zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] with_env_tz 'US/Central' do - Time.stubs(:now).returns Time.local(1999, 12, 31, 23) - assert_equal Date.new(1999, 12, 31), Date.today - assert_equal Date.new(2000, 1, 1), Date.current + assert_equal ::Time.zone.today, Date.current end ensure - Time.zone_default = nil + Time.zone = nil end def test_date_advance_should_not_change_passed_options_hash @@ -415,11 +436,11 @@ class DateExtCalculationsTest < ActiveSupport::TestCase end def with_tz_default(tz = nil) - old_tz = Time.zone_default - Time.zone_default = tz + old_tz = Time.zone + Time.zone = tz yield ensure - Time.zone_default = old_tz + Time.zone = old_tz end end diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb index e8506f5222..456736cbad 100644 --- a/activesupport/test/core_ext/date_time_ext_test.rb +++ b/activesupport/test/core_ext/date_time_ext_test.rb @@ -38,6 +38,8 @@ class DateTimeExtCalculationsTest < Test::Unit::TestCase assert_equal Time.utc_time(2039, 2, 21, 10, 11, 12), DateTime.new(2039, 2, 21, 10, 11, 12, 0, 0).to_time # DateTimes with offsets other than 0 are returned unaltered assert_equal DateTime.new(2005, 2, 21, 10, 11, 12, Rational(-5, 24)), DateTime.new(2005, 2, 21, 10, 11, 12, Rational(-5, 24)).to_time + # Fractional seconds are preserved + assert_equal Time.utc(2005, 2, 21, 10, 11, 12, 256), DateTime.new(2005, 2, 21, 10, 11, 12 + Rational(256, 1000000), 0).to_time end def test_civil_from_format @@ -92,6 +94,14 @@ class DateTimeExtCalculationsTest < Test::Unit::TestCase assert_equal DateTime.civil(2005,1,1,0,0,0), DateTime.civil(2005,2,22,10,10,10).beginning_of_year end + def test_weeks_ago + assert_equal DateTime.civil(2005,5,29,10), DateTime.civil(2005,6,5,10,0,0).weeks_ago(1) + assert_equal DateTime.civil(2005,5,1,10), DateTime.civil(2005,6,5,10,0,0).weeks_ago(5) + assert_equal DateTime.civil(2005,4,24,10), DateTime.civil(2005,6,5,10,0,0).weeks_ago(6) + assert_equal DateTime.civil(2005,2,27,10), DateTime.civil(2005,6,5,10,0,0).weeks_ago(14) + assert_equal DateTime.civil(2004,12,25,10), DateTime.civil(2005,1,1,10,0,0).weeks_ago(1) + end + def test_months_ago assert_equal DateTime.civil(2005,5,5,10), DateTime.civil(2005,6,5,10,0,0).months_ago(1) assert_equal DateTime.civil(2004,11,5,10), DateTime.civil(2005,6,5,10,0,0).months_ago(7) @@ -196,6 +206,14 @@ class DateTimeExtCalculationsTest < Test::Unit::TestCase assert_equal DateTime.civil(2010, 3, 29), DateTime.civil(2010, 2, 28, 22, 58, 59).advance(:months => 1, :hours => 1, :minutes => 1, :seconds => 1) end + def test_prev_week + assert_equal DateTime.civil(2005,2,21), DateTime.civil(2005,3,1,15,15,10).prev_week + assert_equal DateTime.civil(2005,2,22), DateTime.civil(2005,3,1,15,15,10).prev_week(:tuesday) + assert_equal DateTime.civil(2005,2,25), DateTime.civil(2005,3,1,15,15,10).prev_week(:friday) + assert_equal DateTime.civil(2006,10,30), DateTime.civil(2006,11,6,0,0,0).prev_week + assert_equal DateTime.civil(2006,11,15), DateTime.civil(2006,11,23,0,0,0).prev_week(:wednesday) + end + def test_next_week assert_equal DateTime.civil(2005,2,28), DateTime.civil(2005,2,22,15,15,10).next_week assert_equal DateTime.civil(2005,3,4), DateTime.civil(2005,2,22,15,15,10).next_week(:friday) @@ -242,7 +260,7 @@ class DateTimeExtCalculationsTest < Test::Unit::TestCase assert_equal false, DateTime.civil(2005,2,10,15,30,45, Rational(-18000, 86400)).past? assert_equal false, DateTime.civil(2005,2,10,15,30,46, Rational(-18000, 86400)).past? end - + def test_past_without_offset DateTime.stubs(:current).returns(DateTime.civil(2005,2,10,15,30,45, Rational(-18000, 86400))) assert_equal true, DateTime.civil(2005,2,10,20,30,44).past? @@ -264,21 +282,21 @@ class DateTimeExtCalculationsTest < Test::Unit::TestCase assert_equal true, DateTime.civil(2005,2,10,20,30,46).future? end - def test_current_returns_date_today_when_zone_default_not_set + def test_current_returns_date_today_when_zone_is_not_set with_env_tz 'US/Eastern' do Time.stubs(:now).returns Time.local(1999, 12, 31, 23, 59, 59) assert_equal DateTime.new(1999, 12, 31, 23, 59, 59, Rational(-18000, 86400)), DateTime.current end end - def test_current_returns_time_zone_today_when_zone_default_set - Time.zone_default = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] + def test_current_returns_time_zone_today_when_zone_is_set + Time.zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] with_env_tz 'US/Eastern' do Time.stubs(:now).returns Time.local(1999, 12, 31, 23, 59, 59) assert_equal DateTime.new(1999, 12, 31, 23, 59, 59, Rational(-18000, 86400)), DateTime.current end ensure - Time.zone_default = nil + Time.zone = nil end def test_current_without_time_zone diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb index 710c221fac..c0b529d9f8 100644 --- a/activesupport/test/core_ext/duration_test.rb +++ b/activesupport/test/core_ext/duration_test.rb @@ -1,5 +1,6 @@ require 'abstract_unit' require 'active_support/time' +require 'active_support/json' class DurationTest < ActiveSupport::TestCase def test_is_a @@ -53,8 +54,8 @@ class DurationTest < ActiveSupport::TestCase flunk("no exception was raised") rescue ArgumentError => e assert_equal 'expected a time or date, got ""', e.message, "ensure ArgumentError is not being raised by dependencies.rb" - rescue Exception - flunk("ArgumentError should be raised, but we got #{$!.class} instead") + rescue Exception => e + flunk("ArgumentError should be raised, but we got #{e.class} instead") end end @@ -88,8 +89,8 @@ class DurationTest < ActiveSupport::TestCase assert_in_delta((7 * 24 * 1.7).hours.ago(t), 1.7.weeks.ago(t), 1) end - def test_since_and_ago_anchored_to_time_now_when_time_zone_default_not_set - Time.zone_default = nil + def test_since_and_ago_anchored_to_time_now_when_time_zone_is_not_set + Time.zone = nil with_env_tz 'US/Eastern' do Time.stubs(:now).returns Time.local(2000) # since @@ -101,8 +102,8 @@ class DurationTest < ActiveSupport::TestCase end end - def test_since_and_ago_anchored_to_time_zone_now_when_time_zone_default_set - Time.zone_default = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] + def test_since_and_ago_anchored_to_time_zone_now_when_time_zone_is_set + Time.zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] with_env_tz 'US/Eastern' do Time.stubs(:now).returns Time.local(2000) # since @@ -115,20 +116,32 @@ class DurationTest < ActiveSupport::TestCase assert_equal 'Eastern Time (US & Canada)', 5.seconds.ago.time_zone.name end ensure - Time.zone_default = nil + Time.zone = nil end - + def test_adding_hours_across_dst_boundary with_env_tz 'CET' do assert_equal Time.local(2009,3,29,0,0,0) + 24.hours, Time.local(2009,3,30,1,0,0) end end - + def test_adding_day_across_dst_boundary with_env_tz 'CET' do assert_equal Time.local(2009,3,29,0,0,0) + 1.day, Time.local(2009,3,30,0,0,0) end end + + def test_delegation_with_block_works + counter = 0 + assert_nothing_raised do + 1.minute.times {counter += 1} + end + assert_equal counter, 60 + end + + def test_to_json + assert_equal '172800', 2.days.to_json + end protected def with_env_tz(new_tz = 'US/Eastern') diff --git a/activesupport/test/core_ext/enumerable_test.rb b/activesupport/test/core_ext/enumerable_test.rb index 4650b796b6..4655bfe519 100644 --- a/activesupport/test/core_ext/enumerable_test.rb +++ b/activesupport/test/core_ext/enumerable_test.rb @@ -89,7 +89,7 @@ class EnumerableTests < Test::Unit::TestCase assert ![ 1, 2 ].many? {|x| x > 1 } assert [ 1, 2, 2 ].many? {|x| x > 1 } end - + def test_exclude? assert [ 1 ].exclude?(2) assert ![ 1 ].exclude?(1) diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index 5d9846a216..a0479d45ac 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -4,8 +4,18 @@ require 'bigdecimal' require 'active_support/core_ext/string/access' require 'active_support/ordered_hash' require 'active_support/core_ext/object/conversions' +require 'active_support/inflections' class HashExtTest < Test::Unit::TestCase + class IndifferentHash < HashWithIndifferentAccess + end + + class SubclassingArray < Array + end + + class SubclassingHash < Hash + end + def setup @strings = { 'a' => 1, 'b' => 2 } @symbols = { :a => 1, :b => 2 } @@ -99,6 +109,11 @@ class HashExtTest < Test::Unit::TestCase assert_equal @strings, @mixed.with_indifferent_access.dup.stringify_keys! end + def test_hash_subclass + flash = { "foo" => SubclassingHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access + assert_kind_of SubclassingHash, flash["foo"] + end + def test_indifferent_assorted @strings = @strings.with_indifferent_access @symbols = @symbols.with_indifferent_access @@ -248,6 +263,20 @@ class HashExtTest < Test::Unit::TestCase hash = { "urls" => { "url" => [ { "address" => "1" }, { "address" => "2" } ] }}.with_indifferent_access assert_equal "1", hash[:urls][:url].first[:address] end + + def test_should_preserve_array_subclass_when_value_is_array + array = SubclassingArray.new + array << { "address" => "1" } + hash = { "urls" => { "url" => array }}.with_indifferent_access + assert_equal SubclassingArray, hash[:urls][:url].class + end + + def test_should_preserve_array_class_when_hash_value_is_frozen_array + array = SubclassingArray.new + array << { "address" => "1" } + hash = { "urls" => { "url" => array.freeze }}.with_indifferent_access + assert_equal SubclassingArray, hash[:urls][:url].class + end def test_stringify_and_symbolize_keys_on_indifferent_preserves_hash h = HashWithIndifferentAccess.new @@ -267,7 +296,6 @@ class HashExtTest < Test::Unit::TestCase assert_equal 1, h['first'] end - def test_indifferent_subhashes h = {'user' => {'id' => 5}}.with_indifferent_access ['user', :user].each {|user| [:id, 'id'].each {|id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5"}} @@ -276,13 +304,24 @@ class HashExtTest < Test::Unit::TestCase ['user', :user].each {|user| [:id, 'id'].each {|id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5"}} end + def test_indifferent_duplication + # Should preserve default value + h = HashWithIndifferentAccess.new + h.default = '1234' + assert_equal h.default, h.dup.default + + # Should preserve class for subclasses + h = IndifferentHash.new + assert_equal h.class, h.dup.class + end + def test_assert_valid_keys assert_nothing_raised do { :failure => "stuff", :funny => "business" }.assert_valid_keys([ :failure, :funny ]) { :failure => "stuff", :funny => "business" }.assert_valid_keys(:failure, :funny) end - assert_raise(ArgumentError, "Unknown key(s): failore") do + assert_raise(ArgumentError, "Unknown key: failore") do { :failore => "stuff", :funny => "business" }.assert_valid_keys([ :failure, :funny ]) { :failore => "stuff", :funny => "business" }.assert_valid_keys(:failure, :funny) end @@ -316,6 +355,31 @@ class HashExtTest < Test::Unit::TestCase assert_equal expected, hash_1 end + def test_deep_dup + hash = { :a => { :b => 'b' } } + dup = hash.deep_dup + dup[:a][:c] = 'c' + assert_equal nil, hash[:a][:c] + assert_equal 'c', dup[:a][:c] + end + + def test_deep_dup_initialize + zero_hash = Hash.new 0 + hash = { :a => zero_hash } + dup = hash.deep_dup + assert_equal 0, dup[:a][44] + end + + def test_store_on_indifferent_access + hash = HashWithIndifferentAccess.new + hash.store(:test1, 1) + hash.store('test1', 11) + hash[:test2] = 2 + hash['test2'] = 22 + expected = { "test1" => 11, "test2" => 22 } + assert_equal expected, hash + end + def test_reverse_merge defaults = { :a => "x", :b => "y", :c => 10 }.freeze options = { :a => 1, :b => 2 } @@ -462,20 +526,24 @@ class HashExtToParamTests < Test::Unit::TestCase assert_equal '', {}.to_param assert_equal 'hello=world', { :hello => "world" }.to_param assert_equal 'hello=10', { "hello" => 10 }.to_param - assert_equal 'hello=world&say_bye=true', ActiveSupport::OrderedHash[:hello, "world", "say_bye", true].to_param + assert_equal 'hello=world&say_bye=true', {:hello => "world", "say_bye" => true}.to_param end def test_number_hash - assert_equal '10=20&30=40&50=60', ActiveSupport::OrderedHash[10, 20, 30, 40, 50, 60].to_param + assert_equal '10=20&30=40&50=60', {10 => 20, 30 => 40, 50 => 60}.to_param end def test_to_param_hash - assert_equal 'custom=param-1&custom2=param2-1', ActiveSupport::OrderedHash[ToParam.new('custom'), ToParam.new('param'), ToParam.new('custom2'), ToParam.new('param2')].to_param + assert_equal 'custom2=param2-1&custom=param-1', {ToParam.new('custom') => ToParam.new('param'), ToParam.new('custom2') => ToParam.new('param2')}.to_param end def test_to_param_hash_escapes_its_keys_and_values assert_equal 'param+1=A+string+with+%2F+characters+%26+that+should+be+%3F+escaped', { 'param 1' => 'A string with / characters & that should be ? escaped' }.to_param end + + def test_to_param_orders_by_key_in_ascending_order + assert_equal 'a=2&b=1&c=0', ActiveSupport::OrderedHash[*%w(b 1 c 0 a 2)].to_param + end end class HashToXmlTest < Test::Unit::TestCase @@ -511,6 +579,13 @@ class HashToXmlTest < Test::Unit::TestCase assert xml.include?(%(<Name>David</Name>)) end + def test_one_level_camelize_lower + xml = { :name => "David", :street_name => "Paulina" }.to_xml(@xml_options.merge(:camelize => :lower)) + assert_equal "<person>", xml.first(8) + assert xml.include?(%(<streetName>Paulina</streetName>)) + assert xml.include?(%(<name>David</name>)) + end + def test_one_level_with_types xml = { :name => "David", :street => "Paulina", :age => 26, :age_in_millis => 820497600000, :moved_on => Date.new(2005, 11, 15), :resident => :yes }.to_xml(@xml_options) assert_equal "<person>", xml.first(8) @@ -640,12 +715,12 @@ class HashToXmlTest < Test::Unit::TestCase EOT expected_topic_hash = { - :title => nil, + :title => nil, :id => nil, :approved => nil, :written_on => nil, :viewed_at => nil, - :content => nil, + :content => nil, :parent_id => nil }.stringify_keys @@ -723,7 +798,7 @@ class HashToXmlTest < Test::Unit::TestCase assert_equal expected_topic_hash, Hash.from_xml(topic_xml)["rsp"]["photos"]["photo"] end - + def test_all_caps_key_from_xml test_xml = <<-EOT <ABC3XYZ> @@ -839,13 +914,13 @@ class HashToXmlTest < Test::Unit::TestCase assert_equal expected_bacon_hash, Hash.from_xml(bacon_xml)["bacon"] end - + def test_type_trickles_through_when_unknown product_xml = <<-EOT <product> <weight type="double">0.5</weight> <image type="ProductImage"><filename>image.gif</filename></image> - + </product> EOT @@ -854,7 +929,7 @@ class HashToXmlTest < Test::Unit::TestCase :image => {'type' => 'ProductImage', 'filename' => 'image.gif' }, }.stringify_keys - assert_equal expected_product_hash, Hash.from_xml(product_xml)["product"] + assert_equal expected_product_hash, Hash.from_xml(product_xml)["product"] end def test_should_use_default_value_for_unknown_key @@ -888,41 +963,41 @@ class HashToXmlTest < Test::Unit::TestCase assert_equal expected, hash.to_xml(@xml_options) end end - - def test_empty_string_works_for_typecast_xml_value + + def test_empty_string_works_for_typecast_xml_value assert_nothing_raised do Hash.__send__(:typecast_xml_value, "") end end - + def test_escaping_to_xml - hash = { - :bare_string => 'First & Last Name', + hash = { + :bare_string => 'First & Last Name', :pre_escaped_string => 'First & Last Name' }.stringify_keys - + expected_xml = '<person><bare-string>First & Last Name</bare-string><pre-escaped-string>First &amp; Last Name</pre-escaped-string></person>' assert_equal expected_xml, hash.to_xml(@xml_options) end - + def test_unescaping_from_xml xml_string = '<person><bare-string>First & Last Name</bare-string><pre-escaped-string>First &amp; Last Name</pre-escaped-string></person>' - expected_hash = { - :bare_string => 'First & Last Name', + expected_hash = { + :bare_string => 'First & Last Name', :pre_escaped_string => 'First & Last Name' }.stringify_keys assert_equal expected_hash, Hash.from_xml(xml_string)['person'] end - + def test_roundtrip_to_xml_from_xml - hash = { - :bare_string => 'First & Last Name', + hash = { + :bare_string => 'First & Last Name', :pre_escaped_string => 'First & Last Name' }.stringify_keys assert_equal hash, Hash.from_xml(hash.to_xml(@xml_options))['person'] end - + def test_datetime_xml_type_with_utc_time alert_xml = <<-XML <alert> @@ -933,7 +1008,7 @@ class HashToXmlTest < Test::Unit::TestCase assert alert_at.utc? assert_equal Time.utc(2008, 2, 10, 15, 30, 45), alert_at end - + def test_datetime_xml_type_with_non_utc_time alert_xml = <<-XML <alert> @@ -944,7 +1019,7 @@ class HashToXmlTest < Test::Unit::TestCase assert alert_at.utc? assert_equal Time.utc(2008, 2, 10, 15, 30, 45), alert_at end - + def test_datetime_xml_type_with_far_future_date alert_xml = <<-XML <alert> diff --git a/activesupport/test/core_ext/kernel_test.rb b/activesupport/test/core_ext/kernel_test.rb index c22af89918..ede9b0a6aa 100644 --- a/activesupport/test/core_ext/kernel_test.rb +++ b/activesupport/test/core_ext/kernel_test.rb @@ -46,22 +46,27 @@ class KernelTest < Test::Unit::TestCase o = Object.new assert_equal class << o; self end, o.singleton_class end - + def test_class_eval o = Object.new class << o; @x = 1; end assert_equal 1, o.class_eval { @x } end + + def test_capture + assert_equal 'STDERR', capture(:stderr) {$stderr.print 'STDERR'} + assert_equal 'STDOUT', capture(:stdout) {print 'STDOUT'} + end end -class KernelSupressTest < Test::Unit::TestCase +class KernelSuppressTest < Test::Unit::TestCase def test_reraise assert_raise(LoadError) do suppress(ArgumentError) { raise LoadError } end end - def test_supression + def test_suppression suppress(ArgumentError) { raise ArgumentError } suppress(LoadError) { raise LoadError } suppress(LoadError, ArgumentError) { raise LoadError } diff --git a/activesupport/test/core_ext/module/anonymous_test.rb b/activesupport/test/core_ext/module/anonymous_test.rb index 7a78a3b012..cb556af772 100644 --- a/activesupport/test/core_ext/module/anonymous_test.rb +++ b/activesupport/test/core_ext/module/anonymous_test.rb @@ -6,7 +6,7 @@ class AnonymousTest < ActiveSupport::TestCase assert Module.new.anonymous? assert Class.new.anonymous? end - + test "a named class or module are not anonymous" do assert !Kernel.anonymous? assert !Object.anonymous? diff --git a/activesupport/test/core_ext/module/attr_accessor_with_default_test.rb b/activesupport/test/core_ext/module/attr_accessor_with_default_test.rb index 7c0d0bb242..b9b60c4d6d 100644 --- a/activesupport/test/core_ext/module/attr_accessor_with_default_test.rb +++ b/activesupport/test/core_ext/module/attr_accessor_with_default_test.rb @@ -7,25 +7,25 @@ class AttrAccessorWithDefaultTest < Test::Unit::TestCase def helper 'helper' end - end + end @instance = @target.new end - + def test_default_arg @target.attr_accessor_with_default :foo, :bar assert_equal(:bar, @instance.foo) @instance.foo = nil assert_nil(@instance.foo) end - + def test_default_proc @target.attr_accessor_with_default(:foo) {helper.upcase} assert_equal('HELPER', @instance.foo) @instance.foo = nil assert_nil(@instance.foo) end - + def test_invalid_args - assert_raise(RuntimeError) {@target.attr_accessor_with_default :foo} + assert_raise(ArgumentError) {@target.attr_accessor_with_default :foo} end end diff --git a/activesupport/test/core_ext/module/attribute_accessor_test.rb b/activesupport/test/core_ext/module/attribute_accessor_test.rb index 67fcd437d0..118fb070a0 100644 --- a/activesupport/test/core_ext/module/attribute_accessor_test.rb +++ b/activesupport/test/core_ext/module/attribute_accessor_test.rb @@ -32,7 +32,7 @@ class ModuleAttributeAccessorTest < Test::Unit::TestCase assert_respond_to @object, :bar assert !@object.respond_to?(:bar=) end - + def test_should_not_create_instance_reader assert_respond_to @module, :shaq assert !@object.respond_to?(:shaq) diff --git a/activesupport/test/core_ext/module/attribute_aliasing_test.rb b/activesupport/test/core_ext/module/attribute_aliasing_test.rb index f17d031662..065c3531e0 100644 --- a/activesupport/test/core_ext/module/attribute_aliasing_test.rb +++ b/activesupport/test/core_ext/module/attribute_aliasing_test.rb @@ -44,14 +44,14 @@ class AttributeAliasingTest < Test::Unit::TestCase # upper-case attributes, and when people want to alias those names # to more sensible ones, everything goes *foof*. e = AttributeAliasing::Email.new - + assert !e.body? assert !e.Data? - + e.body = "No, really, this is not a joke." assert_equal "No, really, this is not a joke.", e.Data assert e.Data? - + e.Data = "Uppercased methods are teh suck" assert_equal "Uppercased methods are teh suck", e.body assert e.body? diff --git a/activesupport/test/core_ext/module/reachable_test.rb b/activesupport/test/core_ext/module/reachable_test.rb index 72892b77d5..80eb31a5c4 100644 --- a/activesupport/test/core_ext/module/reachable_test.rb +++ b/activesupport/test/core_ext/module/reachable_test.rb @@ -6,33 +6,33 @@ class AnonymousTest < ActiveSupport::TestCase assert !Module.new.reachable? assert !Class.new.reachable? end - + test "ordinary named classes or modules are reachable" do assert Kernel.reachable? assert Object.reachable? end - + test "a named class or module whose constant has gone is not reachable" do c = eval "class C; end; C" m = eval "module M; end; M" - + self.class.send(:remove_const, :C) self.class.send(:remove_const, :M) - + assert !c.reachable? assert !m.reachable? end - + test "a named class or module whose constants store different objects are not reachable" do c = eval "class C; end; C" m = eval "module M; end; M" - + self.class.send(:remove_const, :C) - self.class.send(:remove_const, :M) + self.class.send(:remove_const, :M) eval "class C; end" eval "module M; end" - + assert C.reachable? assert M.reachable? assert !c.reachable? diff --git a/activesupport/test/core_ext/module/synchronization_test.rb b/activesupport/test/core_ext/module/synchronization_test.rb index eb850893f0..6c407e2260 100644 --- a/activesupport/test/core_ext/module/synchronization_test.rb +++ b/activesupport/test/core_ext/module/synchronization_test.rb @@ -1,3 +1,4 @@ +require 'thread' require 'abstract_unit' require 'active_support/core_ext/class/attribute_accessors' diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb index 5d9cdf22c2..a95cf1591f 100644 --- a/activesupport/test/core_ext/module_test.rb +++ b/activesupport/test/core_ext/module_test.rb @@ -26,14 +26,10 @@ module Yz end end -class De -end - Somewhere = Struct.new(:street, :city) Someone = Struct.new(:name, :place) do delegate :street, :city, :to_f, :to => :place - delegate :state, :to => :@place delegate :upcase, :to => "place.city" end @@ -47,6 +43,14 @@ Project = Struct.new(:description, :person) do delegate :to_f, :to => :description, :allow_nil => true end +Developer = Struct.new(:client) do + delegate :name, :to => :client, :prefix => nil +end + +Tester = Struct.new(:client) do + delegate :name, :to => :client, :prefix => false +end + class Name delegate :upcase, :to => :@full_name @@ -97,6 +101,11 @@ class ModuleTest < Test::Unit::TestCase assert_equal invoice.customer_city, "Chicago" end + def test_delegation_prefix_with_nil_or_false + assert_equal Developer.new(@david).name, "David" + assert_equal Tester.new(@david).name, "David" + end + def test_delegation_prefix_with_instance_variable assert_raise ArgumentError do Class.new do diff --git a/activesupport/test/core_ext/numeric_ext_test.rb b/activesupport/test/core_ext/numeric_ext_test.rb index 992ec60302..3a2452b4b0 100644 --- a/activesupport/test/core_ext/numeric_ext_test.rb +++ b/activesupport/test/core_ext/numeric_ext_test.rb @@ -38,7 +38,7 @@ class NumericExtTimeAndDateTimeTest < Test::Unit::TestCase assert seconds.from_now >= now + seconds end end - + def test_irregular_durations assert_equal @now.advance(:days => 3000), 3000.days.since(@now) assert_equal @now.advance(:months => 1), 1.month.since(@now) @@ -49,16 +49,16 @@ class NumericExtTimeAndDateTimeTest < Test::Unit::TestCase assert_equal @dtnow.advance(:months => -1), 1.month.until(@dtnow) assert_equal @dtnow.advance(:years => 20), 20.years.since(@dtnow) end - + def test_duration_addition assert_equal @now.advance(:days => 1).advance(:months => 1), (1.day + 1.month).since(@now) assert_equal @now.advance(:days => 7), (1.week + 5.seconds - 5.seconds).since(@now) assert_equal @now.advance(:years => 2), (4.years - 2.years).since(@now) assert_equal @dtnow.advance(:days => 1).advance(:months => 1), (1.day + 1.month).since(@dtnow) assert_equal @dtnow.advance(:days => 7), (1.week + 5.seconds - 5.seconds).since(@dtnow) - assert_equal @dtnow.advance(:years => 2), (4.years - 2.years).since(@dtnow) + assert_equal @dtnow.advance(:years => 2), (4.years - 2.years).since(@dtnow) end - + def test_time_plus_duration assert_equal @now + 8, @now + 8.seconds assert_equal @now + 22.9, @now + 22.9.seconds @@ -69,25 +69,63 @@ class NumericExtTimeAndDateTimeTest < Test::Unit::TestCase assert_equal @dtnow.advance(:days => 15), @dtnow + 15.days assert_equal @dtnow.advance(:months => 1), @dtnow + 1.month end - + def test_chaining_duration_operations assert_equal @now.advance(:days => 2).advance(:months => -3), @now + 2.days - 3.months assert_equal @now.advance(:days => 1).advance(:months => 2), @now + 1.day + 2.months assert_equal @dtnow.advance(:days => 2).advance(:months => -3), @dtnow + 2.days - 3.months - assert_equal @dtnow.advance(:days => 1).advance(:months => 2), @dtnow + 1.day + 2.months + assert_equal @dtnow.advance(:days => 1).advance(:months => 2), @dtnow + 1.day + 2.months end - + def test_duration_after_convertion_is_no_longer_accurate assert_equal 30.days.to_i.since(@now), 1.month.to_i.since(@now) assert_equal 365.25.days.to_f.since(@now), 1.year.to_f.since(@now) assert_equal 30.days.to_i.since(@dtnow), 1.month.to_i.since(@dtnow) - assert_equal 365.25.days.to_f.since(@dtnow), 1.year.to_f.since(@dtnow) + assert_equal 365.25.days.to_f.since(@dtnow), 1.year.to_f.since(@dtnow) end - + def test_add_one_year_to_leap_day assert_equal Time.utc(2005,2,28,15,15,10), Time.utc(2004,2,29,15,15,10) + 1.year assert_equal DateTime.civil(2005,2,28,15,15,10), DateTime.civil(2004,2,29,15,15,10) + 1.year end + + def test_since_and_ago_anchored_to_time_now_when_time_zone_is_not_set + Time.zone = nil + with_env_tz 'US/Eastern' do + Time.stubs(:now).returns Time.local(2000) + # since + assert_equal false, 5.since.is_a?(ActiveSupport::TimeWithZone) + assert_equal Time.local(2000,1,1,0,0,5), 5.since + # ago + assert_equal false, 5.ago.is_a?(ActiveSupport::TimeWithZone) + assert_equal Time.local(1999,12,31,23,59,55), 5.ago + end + end + + def test_since_and_ago_anchored_to_time_zone_now_when_time_zone_is_set + Time.zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] + with_env_tz 'US/Eastern' do + Time.stubs(:now).returns Time.local(2000) + # since + assert_equal true, 5.since.is_a?(ActiveSupport::TimeWithZone) + assert_equal Time.utc(2000,1,1,0,0,5), 5.since.time + assert_equal 'Eastern Time (US & Canada)', 5.since.time_zone.name + # ago + assert_equal true, 5.ago.is_a?(ActiveSupport::TimeWithZone) + assert_equal Time.utc(1999,12,31,23,59,55), 5.ago.time + assert_equal 'Eastern Time (US & Canada)', 5.ago.time_zone.name + end + ensure + Time.zone = nil + end + + protected + def with_env_tz(new_tz = 'US/Eastern') + old_tz, ENV['TZ'] = ENV['TZ'], new_tz + yield + ensure + old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ') + end end class NumericExtDateTest < Test::Unit::TestCase @@ -102,12 +140,12 @@ class NumericExtDateTest < Test::Unit::TestCase assert_equal @today.to_time.since(60), @today + 1.minute assert_equal @today.to_time.since(60*60), @today + 1.hour end - + def test_chaining_duration_operations assert_equal @today.advance(:days => 2).advance(:months => -3), @today + 2.days - 3.months assert_equal @today.advance(:days => 1).advance(:months => 2), @today + 1.day + 2.months end - + def test_add_one_year_to_leap_day assert_equal Date.new(2005,2,28), Date.new(2004,2,29) + 1.year end @@ -132,7 +170,7 @@ class NumericExtSizeTest < Test::Unit::TestCase assert_equal right, left end end - + def test_units_as_bytes_independently assert_equal 3145728, 3.megabytes assert_equal 3145728, 3.megabyte diff --git a/activesupport/test/core_ext/object/to_query_test.rb b/activesupport/test/core_ext/object/to_query_test.rb index e28b4cd493..84da52f4bf 100644 --- a/activesupport/test/core_ext/object/to_query_test.rb +++ b/activesupport/test/core_ext/object/to_query_test.rb @@ -18,22 +18,22 @@ class ToQueryTest < Test::Unit::TestCase end def test_nested_conversion - assert_query_equal 'person[login]=seckar&person[name]=Nicholas', + assert_query_equal 'person%5Blogin%5D=seckar&person%5Bname%5D=Nicholas', :person => ActiveSupport::OrderedHash[:login, 'seckar', :name, 'Nicholas'] end def test_multiple_nested - assert_query_equal 'account[person][id]=20&person[id]=10', + assert_query_equal 'account%5Bperson%5D%5Bid%5D=20&person%5Bid%5D=10', ActiveSupport::OrderedHash[:account, {:person => {:id => 20}}, :person, {:id => 10}] end def test_array_values - assert_query_equal 'person[id][]=10&person[id][]=20', + assert_query_equal 'person%5Bid%5D%5B%5D=10&person%5Bid%5D%5B%5D=20', :person => {:id => [10, 20]} end def test_array_values_are_not_sorted - assert_query_equal 'person[id][]=20&person[id][]=10', + assert_query_equal 'person%5Bid%5D%5B%5D=20&person%5Bid%5D%5B%5D=10', :person => {:id => [20, 10]} end diff --git a/activesupport/test/core_ext/object_and_class_ext_test.rb b/activesupport/test/core_ext/object_and_class_ext_test.rb index 6588f2e345..5d68b198f2 100644 --- a/activesupport/test/core_ext/object_and_class_ext_test.rb +++ b/activesupport/test/core_ext/object_and_class_ext_test.rb @@ -28,18 +28,6 @@ module Nested end end -module Bar - def bar; end -end - -module Baz - def baz; end -end - -class Foo - include Bar -end - class ObjectTests < ActiveSupport::TestCase class DuckTime def acts_like_time? @@ -82,37 +70,6 @@ class ObjectInstanceVariableTest < Test::Unit::TestCase assert_equal %w(@bar @baz), @source.instance_variable_names.sort end - def test_copy_instance_variables_from_without_explicit_excludes - assert_equal [], @dest.instance_variables - @dest.copy_instance_variables_from(@source) - - assert_equal %w(@bar @baz), @dest.instance_variables.sort.map(&:to_s) - %w(@bar @baz).each do |name| - assert_equal @source.instance_variable_get(name).object_id, - @dest.instance_variable_get(name).object_id - end - end - - def test_copy_instance_variables_from_with_explicit_excludes - @dest.copy_instance_variables_from(@source, ['@baz']) - assert !@dest.instance_variable_defined?('@baz') - assert_equal 'bar', @dest.instance_variable_get('@bar') - end - - def test_copy_instance_variables_automatically_excludes_protected_instance_variables - @source.instance_variable_set(:@quux, 'quux') - class << @source - def protected_instance_variables - ['@bar', :@quux] - end - end - - @dest.copy_instance_variables_from(@source) - assert !@dest.instance_variable_defined?('@bar') - assert !@dest.instance_variable_defined?('@quux') - assert_equal 'baz', @dest.instance_variable_get('@baz') - end - def test_instance_values object = Object.new object.instance_variable_set :@a, 1 @@ -144,7 +101,7 @@ class ObjectTryTest < Test::Unit::TestCase assert !@string.respond_to?(method) assert_raise(NoMethodError) { @string.try(method) } end - + def test_valid_method assert_equal 5, @string.try(:size) end @@ -165,4 +122,14 @@ class ObjectTryTest < Test::Unit::TestCase def test_false_try assert_equal 'false', false.try(:to_s) end + + def test_try_only_block + assert_equal @string.reverse, @string.try { |s| s.reverse } + end + + def test_try_only_block_nil + ran = false + nil.try { ran = true } + assert_equal false, ran + end end diff --git a/activesupport/test/core_ext/range_ext_test.rb b/activesupport/test/core_ext/range_ext_test.rb index 5701eeef28..1424fa4aca 100644 --- a/activesupport/test/core_ext/range_ext_test.rb +++ b/activesupport/test/core_ext/range_ext_test.rb @@ -62,4 +62,16 @@ class RangeTest < Test::Unit::TestCase (1..10).step(2) {|i| array << i } assert_equal [1,3,5,7,9], array end + + if RUBY_VERSION < '1.9' + def test_cover + assert((1..3).cover?(2)) + assert !(1..3).cover?(4) + end + else + def test_cover_is_not_override + range = (1..3) + assert range.method(:include?) != range.method(:cover?) + end + end end diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index affa1b5e18..bb865cae91 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -6,10 +6,42 @@ require 'inflector_test_cases' require 'active_support/core_ext/string' require 'active_support/time' require 'active_support/core_ext/kernel/reporting' +require 'active_support/core_ext/string/strip' class StringInflectionsTest < Test::Unit::TestCase include InflectorTestCases + def test_strip_heredoc_on_an_empty_string + assert_equal '', ''.strip_heredoc + end + + def test_strip_heredoc_on_a_string_with_no_lines + assert_equal 'x', 'x'.strip_heredoc + assert_equal 'x', ' x'.strip_heredoc + end + + def test_strip_heredoc_on_a_heredoc_with_no_margin + assert_equal "foo\nbar", "foo\nbar".strip_heredoc + assert_equal "foo\n bar", "foo\n bar".strip_heredoc + end + + def test_strip_heredoc_on_a_regular_indented_heredoc + assert_equal "foo\n bar\nbaz\n", <<-EOS.strip_heredoc + foo + bar + baz + EOS + end + + def test_strip_heredoc_on_a_regular_indented_heredoc_with_blank_lines + assert_equal "foo\n bar\n\nbaz\n", <<-EOS.strip_heredoc + foo + bar + + baz + EOS + end + def test_pluralize SingularToPlural.each do |singular, plural| assert_equal(plural, singular.pluralize) @@ -218,7 +250,7 @@ class StringInflectionsTest < Test::Unit::TestCase # And changes the original string: assert_equal original, expected end - + def test_truncate assert_equal "Hello World!", "Hello World!".truncate(12) assert_equal "Hello Wor...", "Hello World!!".truncate(12) @@ -289,75 +321,6 @@ class CoreExtStringMultibyteTest < ActiveSupport::TestCase end end -=begin - string.rb - Interpolation for String. - - Copyright (C) 2005-2009 Masao Mutoh - - You may redistribute it and/or modify it under the same - license terms as Ruby. -=end -class TestGetTextString < Test::Unit::TestCase - def test_sprintf - assert_equal("foo is a number", "%{msg} is a number" % {:msg => "foo"}) - assert_equal("bar is a number", "%s is a number" % ["bar"]) - assert_equal("bar is a number", "%s is a number" % "bar") - assert_equal("1, test", "%{num}, %{record}" % {:num => 1, :record => "test"}) - assert_equal("test, 1", "%{record}, %{num}" % {:num => 1, :record => "test"}) - assert_equal("1, test", "%d, %s" % [1, "test"]) - assert_equal("test, 1", "%2$s, %1$d" % [1, "test"]) - assert_raise(ArgumentError) { "%-%" % [1] } - end - - def test_percent - assert_equal("% 1", "%% %<num>d" % {:num => 1.0}) - assert_equal("%{num} %<num>d 1", "%%{num} %%<num>d %<num>d" % {:num => 1}) - end - - def test_sprintf_percent_in_replacement - assert_equal("%<not_translated>s", "%{msg}" % { :msg => '%<not_translated>s', :not_translated => 'should not happen' }) - end - - def test_sprintf_lack_argument - assert_raises(KeyError) { "%{num}, %{record}" % {:record => "test"} } - assert_raises(KeyError) { "%{record}" % {:num => 1} } - end - - def test_no_placeholder - # Causes a "too many arguments for format string" warning - # on 1.8.7 and 1.9 but we still want to make sure the behavior works - silence_warnings do - assert_equal("aaa", "aaa" % {:num => 1}) - assert_equal("bbb", "bbb" % [1]) - end - end - - def test_sprintf_ruby19_style - assert_equal("1", "%<num>d" % {:num => 1}) - assert_equal("0b1", "%<num>#b" % {:num => 1}) - assert_equal("foo", "%<msg>s" % {:msg => "foo"}) - assert_equal("1.000000", "%<num>f" % {:num => 1.0}) - assert_equal(" 1", "%<num>3.0f" % {:num => 1.0}) - assert_equal("100.00", "%<num>2.2f" % {:num => 100.0}) - assert_equal("0x64", "%<num>#x" % {:num => 100.0}) - assert_raise(ArgumentError) { "%<num>,d" % {:num => 100} } - assert_raise(ArgumentError) { "%<num>/d" % {:num => 100} } - end - - def test_sprintf_old_style - assert_equal("foo 1.000000", "%s %f" % ["foo", 1.0]) - end - - def test_sprintf_mix_unformatted_and_formatted_named_placeholders - assert_equal("foo 1.000000", "%{name} %<num>f" % {:name => "foo", :num => 1.0}) - end - - def test_string_interpolation_raises_an_argument_error_when_mixing_named_and_unnamed_placeholders - assert_raises(ArgumentError) { "%{name} %f" % [1.0] } - assert_raises(ArgumentError) { "%{name} %f" % [1.0, 2.0] } - end -end - class OutputSafetyTest < ActiveSupport::TestCase def setup @string = "hello" diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb index 1cf84df386..53d497013a 100644 --- a/activesupport/test/core_ext/time_ext_test.rb +++ b/activesupport/test/core_ext/time_ext_test.rb @@ -130,6 +130,14 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase assert_equal Time.local(2005,1,1,0,0,0), Time.local(2005,2,22,10,10,10).beginning_of_year end + def test_weeks_ago + assert_equal Time.local(2005,5,29,10), Time.local(2005,6,5,10,0,0).weeks_ago(1) + assert_equal Time.local(2005,5,1,10), Time.local(2005,6,5,10,0,0).weeks_ago(5) + assert_equal Time.local(2005,4,24,10), Time.local(2005,6,5,10,0,0).weeks_ago(6) + assert_equal Time.local(2005,2,27,10), Time.local(2005,6,5,10,0,0).weeks_ago(14) + assert_equal Time.local(2004,12,25,10), Time.local(2005,1,1,10,0,0).weeks_ago(1) + end + def test_months_ago assert_equal Time.local(2005,5,5,10), Time.local(2005,6,5,10,0,0).months_ago(1) assert_equal Time.local(2004,11,5,10), Time.local(2005,6,5,10,0,0).months_ago(7) @@ -187,7 +195,7 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase assert_equal Time.local(2005,4,2,3,18,0), Time.local(2005,4,3,4,18,0).ago(24.hours), 'dt-24.hours=>st' assert_equal Time.local(2005,4,2,3,18,0), Time.local(2005,4,3,4,18,0).ago(86400), 'dt-86400=>st' assert_equal Time.local(2005,4,2,3,18,0), Time.local(2005,4,3,4,18,0).ago(86400.seconds), 'dt-86400.seconds=>st' - + assert_equal Time.local(2005,4,1,4,18,0), Time.local(2005,4,2,4,18,0).ago(24.hours), 'st-24.hours=>st' assert_equal Time.local(2005,4,1,4,18,0), Time.local(2005,4,2,4,18,0).ago(86400), 'st-86400=>st' assert_equal Time.local(2005,4,1,4,18,0), Time.local(2005,4,2,4,18,0).ago(86400.seconds), 'st-86400.seconds=>st' @@ -197,7 +205,7 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase assert_equal Time.local(2006,9,30,3,18,0), Time.local(2006,10,1,4,18,0).ago(24.hours), 'dt-24.hours=>st' assert_equal Time.local(2006,9,30,3,18,0), Time.local(2006,10,1,4,18,0).ago(86400), 'dt-86400=>st' assert_equal Time.local(2006,9,30,3,18,0), Time.local(2006,10,1,4,18,0).ago(86400.seconds), 'dt-86400.seconds=>st' - + assert_equal Time.local(2006,9,29,4,18,0), Time.local(2006,9,30,4,18,0).ago(24.hours), 'st-24.hours=>st' assert_equal Time.local(2006,9,29,4,18,0), Time.local(2006,9,30,4,18,0).ago(86400), 'st-86400=>st' assert_equal Time.local(2006,9,29,4,18,0), Time.local(2006,9,30,4,18,0).ago(86400.seconds), 'st-86400.seconds=>st' @@ -210,7 +218,7 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase assert_equal Time.local(2005,10,29,5,3), Time.local(2005,10,30,4,3,0).ago(24.hours), 'st-24.hours=>dt' assert_equal Time.local(2005,10,29,5,3), Time.local(2005,10,30,4,3,0).ago(86400), 'st-86400=>dt' assert_equal Time.local(2005,10,29,5,3), Time.local(2005,10,30,4,3,0).ago(86400.seconds), 'st-86400.seconds=>dt' - + assert_equal Time.local(2005,10,28,4,3), Time.local(2005,10,29,4,3,0).ago(24.hours), 'dt-24.hours=>dt' assert_equal Time.local(2005,10,28,4,3), Time.local(2005,10,29,4,3,0).ago(86400), 'dt-86400=>dt' assert_equal Time.local(2005,10,28,4,3), Time.local(2005,10,29,4,3,0).ago(86400.seconds), 'dt-86400.seconds=>dt' @@ -220,7 +228,7 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase assert_equal Time.local(2006,3,18,5,3), Time.local(2006,3,19,4,3,0).ago(24.hours), 'st-24.hours=>dt' assert_equal Time.local(2006,3,18,5,3), Time.local(2006,3,19,4,3,0).ago(86400), 'st-86400=>dt' assert_equal Time.local(2006,3,18,5,3), Time.local(2006,3,19,4,3,0).ago(86400.seconds), 'st-86400.seconds=>dt' - + assert_equal Time.local(2006,3,17,4,3), Time.local(2006,3,18,4,3,0).ago(24.hours), 'dt-24.hours=>dt' assert_equal Time.local(2006,3,17,4,3), Time.local(2006,3,18,4,3,0).ago(86400), 'dt-86400=>dt' assert_equal Time.local(2006,3,17,4,3), Time.local(2006,3,18,4,3,0).ago(86400.seconds), 'dt-86400.seconds=>dt' @@ -252,7 +260,7 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase assert_equal Time.local(2006,3,17,4,3), Time.local(2006,3,18,4,3,0).ago(1.day), 'dt-1.day=>dt' end end - + def test_since assert_equal Time.local(2005,2,22,10,10,11), Time.local(2005,2,22,10,10,10).since(1) assert_equal Time.local(2005,2,22,11,10,10), Time.local(2005,2,22,10,10,10).since(3600) @@ -268,7 +276,7 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase assert_equal Time.local(2005,4,3,20,27,0), Time.local(2005,4,2,19,27,0).since(24.hours), 'st+24.hours=>dt' assert_equal Time.local(2005,4,3,20,27,0), Time.local(2005,4,2,19,27,0).since(86400), 'st+86400=>dt' assert_equal Time.local(2005,4,3,20,27,0), Time.local(2005,4,2,19,27,0).since(86400.seconds), 'st+86400.seconds=>dt' - + assert_equal Time.local(2005,4,4,19,27,0), Time.local(2005,4,3,19,27,0).since(24.hours), 'dt+24.hours=>dt' assert_equal Time.local(2005,4,4,19,27,0), Time.local(2005,4,3,19,27,0).since(86400), 'dt+86400=>dt' assert_equal Time.local(2005,4,4,19,27,0), Time.local(2005,4,3,19,27,0).since(86400.seconds), 'dt+86400.seconds=>dt' @@ -278,7 +286,7 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase assert_equal Time.local(2006,10,1,20,27,0), Time.local(2006,9,30,19,27,0).since(24.hours), 'st+24.hours=>dt' assert_equal Time.local(2006,10,1,20,27,0), Time.local(2006,9,30,19,27,0).since(86400), 'st+86400=>dt' assert_equal Time.local(2006,10,1,20,27,0), Time.local(2006,9,30,19,27,0).since(86400.seconds), 'st+86400.seconds=>dt' - + assert_equal Time.local(2006,10,2,19,27,0), Time.local(2006,10,1,19,27,0).since(24.hours), 'dt+24.hours=>dt' assert_equal Time.local(2006,10,2,19,27,0), Time.local(2006,10,1,19,27,0).since(86400), 'dt+86400=>dt' assert_equal Time.local(2006,10,2,19,27,0), Time.local(2006,10,1,19,27,0).since(86400.seconds), 'dt+86400.seconds=>dt' @@ -330,7 +338,7 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase assert_equal Time.local(2005,10,30,23,45,0), Time.local(2005,10,30,0,45,0).since(24.hours), 'dt+24.hours=>st' assert_equal Time.local(2005,10,30,23,45,0), Time.local(2005,10,30,0,45,0).since(86400), 'dt+86400=>st' assert_equal Time.local(2005,10,30,23,45,0), Time.local(2005,10,30,0,45,0).since(86400.seconds), 'dt+86400.seconds=>st' - + assert_equal Time.local(2005,11, 1,0,45,0), Time.local(2005,10,31,0,45,0).since(24.hours), 'st+24.hours=>st' assert_equal Time.local(2005,11, 1,0,45,0), Time.local(2005,10,31,0,45,0).since(86400), 'st+86400=>st' assert_equal Time.local(2005,11, 1,0,45,0), Time.local(2005,10,31,0,45,0).since(86400.seconds), 'st+86400.seconds=>st' @@ -340,7 +348,7 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase assert_equal Time.local(2006,3,20,0,45,0), Time.local(2006,3,19,1,45,0).since(24.hours), 'dt+24.hours=>st' assert_equal Time.local(2006,3,20,0,45,0), Time.local(2006,3,19,1,45,0).since(86400), 'dt+86400=>st' assert_equal Time.local(2006,3,20,0,45,0), Time.local(2006,3,19,1,45,0).since(86400.seconds), 'dt+86400.seconds=>st' - + assert_equal Time.local(2006,3,21,1,45,0), Time.local(2006,3,20,1,45,0).since(24.hours), 'st+24.hours=>st' assert_equal Time.local(2006,3,21,1,45,0), Time.local(2006,3,20,1,45,0).since(86400), 'st+86400=>st' assert_equal Time.local(2006,3,21,1,45,0), Time.local(2006,3,20,1,45,0).since(86400.seconds), 'st+86400.seconds=>st' @@ -463,6 +471,16 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase assert_equal Time.utc(2013,10,17,20,22,19), Time.utc(2005,2,28,15,15,10).advance(:years => 7, :months => 19, :weeks => 2, :days => 5, :hours => 5, :minutes => 7, :seconds => 9) end + def test_prev_week + with_env_tz 'US/Eastern' do + assert_equal Time.local(2005,2,21), Time.local(2005,3,1,15,15,10).prev_week + assert_equal Time.local(2005,2,22), Time.local(2005,3,1,15,15,10).prev_week(:tuesday) + assert_equal Time.local(2005,2,25), Time.local(2005,3,1,15,15,10).prev_week(:friday) + assert_equal Time.local(2006,10,30), Time.local(2006,11,6,0,0,0).prev_week + assert_equal Time.local(2006,11,15), Time.local(2006,11,23,0,0,0).prev_week(:wednesday) + end + end + def test_next_week with_env_tz 'US/Eastern' do assert_equal Time.local(2005,2,28), Time.local(2005,2,22,15,15,10).next_week @@ -729,7 +747,7 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase def test_minus_with_time_with_zone assert_equal 86_400.0, Time.utc(2000, 1, 2) - ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1), ActiveSupport::TimeZone['UTC'] ) end - + def test_minus_with_datetime assert_equal 86_400.0, Time.utc(2000, 1, 2) - DateTime.civil(2000, 1, 1) end diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb index 5ce4277672..bafa335a09 100644 --- a/activesupport/test/core_ext/time_with_zone_test.rb +++ b/activesupport/test/core_ext/time_with_zone_test.rb @@ -36,6 +36,10 @@ class TimeWithZoneTest < Test::Unit::TestCase assert_equal @twz.object_id, @twz.in_time_zone(ActiveSupport::TimeZone['Eastern Time (US & Canada)']).object_id end + def test_localtime + assert_equal @twz.localtime, @twz.utc.getlocal + end + def test_utc? assert_equal false, @twz.utc? assert_equal true, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['UTC']).utc? @@ -102,11 +106,11 @@ class TimeWithZoneTest < Test::Unit::TestCase end def test_to_yaml - assert_equal "--- 1999-12-31 19:00:00 -05:00\n", @twz.to_yaml + assert_match(/^--- 2000-01-01 00:00:00(\.0+)?\s*Z\n/, @twz.to_yaml) end def test_ruby_to_yaml - assert_equal "--- \n:twz: 2000-01-01 00:00:00 Z\n", {:twz => @twz}.to_yaml + assert_match(/---\s*\n:twz: 2000-01-01 00:00:00(\.0+)?\s*Z\n/, {:twz => @twz}.to_yaml) end def test_httpdate @@ -287,7 +291,7 @@ class TimeWithZoneTest < Test::Unit::TestCase assert_equal 946684800, result assert_kind_of Integer, result end - + def test_to_i_with_wrapped_datetime datetime = DateTime.civil(2000, 1, 1, 0) twz = ActiveSupport::TimeWithZone.new(datetime, @time_zone) @@ -328,7 +332,7 @@ class TimeWithZoneTest < Test::Unit::TestCase assert_kind_of Time, @twz assert_kind_of ActiveSupport::TimeWithZone, @twz end - + def test_class_name assert_equal 'Time', ActiveSupport::TimeWithZone.name end @@ -703,7 +707,7 @@ class TimeWithZoneTest < Test::Unit::TestCase assert_equal "Sun, 15 Jul 2007 10:30:00 EDT -04:00", twz.years_ago(1).inspect assert_equal "Sun, 15 Jul 2007 10:30:00 EDT -04:00", (twz - 1.year).inspect end - + protected def with_env_tz(new_tz = 'US/Eastern') old_tz, ENV['TZ'] = ENV['TZ'], new_tz @@ -763,6 +767,13 @@ class TimeWithZoneMethodsForTimeAndDateTimeTest < Test::Unit::TestCase end end + def test_localtime + Time.zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] + assert_equal @dt.in_time_zone.localtime, @dt.in_time_zone.utc.to_time.getlocal + ensure + Time.zone = nil + end + def test_use_zone Time.zone = 'Alaska' Time.use_zone 'Hawaii' do @@ -790,7 +801,7 @@ class TimeWithZoneMethodsForTimeAndDateTimeTest < Test::Unit::TestCase assert_equal nil, Time.zone end - def test_time_zone_getter_and_setter_with_zone_default + def test_time_zone_getter_and_setter_with_zone_default_set Time.zone_default = ActiveSupport::TimeZone['Alaska'] assert_equal ActiveSupport::TimeZone['Alaska'], Time.zone Time.zone = ActiveSupport::TimeZone['Hawaii'] @@ -798,6 +809,7 @@ class TimeWithZoneMethodsForTimeAndDateTimeTest < Test::Unit::TestCase Time.zone = nil assert_equal ActiveSupport::TimeZone['Alaska'], Time.zone ensure + Time.zone = nil Time.zone_default = nil end @@ -838,7 +850,7 @@ class TimeWithZoneMethodsForTimeAndDateTimeTest < Test::Unit::TestCase assert_nil Time.zone end - def test_current_returns_time_now_when_zone_default_not_set + def test_current_returns_time_now_when_zone_not_set with_env_tz 'US/Eastern' do Time.stubs(:now).returns Time.local(2000) assert_equal false, Time.current.is_a?(ActiveSupport::TimeWithZone) @@ -846,8 +858,8 @@ class TimeWithZoneMethodsForTimeAndDateTimeTest < Test::Unit::TestCase end end - def test_current_returns_time_zone_now_when_zone_default_set - Time.zone_default = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] + def test_current_returns_time_zone_now_when_zone_set + Time.zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] with_env_tz 'US/Eastern' do Time.stubs(:now).returns Time.local(2000) assert_equal true, Time.current.is_a?(ActiveSupport::TimeWithZone) @@ -855,7 +867,7 @@ class TimeWithZoneMethodsForTimeAndDateTimeTest < Test::Unit::TestCase assert_equal Time.utc(2000), Time.current.time end ensure - Time.zone_default = nil + Time.zone = nil end protected diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb index f98d823f00..ef017d7436 100644 --- a/activesupport/test/dependencies_test.rb +++ b/activesupport/test/dependencies_test.rb @@ -477,15 +477,15 @@ class DependenciesTest < Test::Unit::TestCase def test_references_should_work with_loading 'dependencies' do - c = ActiveSupport::Dependencies.ref("ServiceOne") + c = ActiveSupport::Dependencies.reference("ServiceOne") service_one_first = ServiceOne - assert_equal service_one_first, c.get + assert_equal service_one_first, c.get("ServiceOne") ActiveSupport::Dependencies.clear assert ! defined?(ServiceOne) service_one_second = ServiceOne - assert_not_equal service_one_first, c.get - assert_equal service_one_second, c.get + assert_not_equal service_one_first, c.get("ServiceOne") + assert_equal service_one_second, c.get("ServiceOne") end end @@ -574,16 +574,27 @@ class DependenciesTest < Test::Unit::TestCase end end + def test_unloadable_constants_should_receive_callback + Object.const_set :C, Class.new + C.unloadable + C.expects(:before_remove_const).once + assert C.respond_to?(:before_remove_const) + ActiveSupport::Dependencies.clear + assert !defined?(C) + ensure + Object.class_eval { remove_const :C } if defined?(C) + end + def test_new_contants_in_without_constants assert_equal [], (ActiveSupport::Dependencies.new_constants_in(Object) { }) - assert ActiveSupport::Dependencies.constant_watch_stack.empty? + assert ActiveSupport::Dependencies.constant_watch_stack.all? {|k,v| v.empty? } end def test_new_constants_in_with_a_single_constant assert_equal ["Hello"], ActiveSupport::Dependencies.new_constants_in(Object) { Object.const_set :Hello, 10 }.map(&:to_s) - assert ActiveSupport::Dependencies.constant_watch_stack.empty? + assert ActiveSupport::Dependencies.constant_watch_stack.all? {|k,v| v.empty? } ensure Object.class_eval { remove_const :Hello } end @@ -600,7 +611,7 @@ class DependenciesTest < Test::Unit::TestCase end assert_equal ["OuterAfter", "OuterBefore"], outer.sort.map(&:to_s) - assert ActiveSupport::Dependencies.constant_watch_stack.empty? + assert ActiveSupport::Dependencies.constant_watch_stack.all? {|k,v| v.empty? } ensure %w(OuterBefore Inner OuterAfter).each do |name| Object.class_eval { remove_const name if const_defined?(name) } @@ -621,7 +632,7 @@ class DependenciesTest < Test::Unit::TestCase M.const_set :OuterAfter, 30 end assert_equal ["M::OuterAfter", "M::OuterBefore"], outer.sort - assert ActiveSupport::Dependencies.constant_watch_stack.empty? + assert ActiveSupport::Dependencies.constant_watch_stack.all? {|k,v| v.empty? } ensure Object.class_eval { remove_const :M } end @@ -639,7 +650,7 @@ class DependenciesTest < Test::Unit::TestCase M.const_set :OuterAfter, 30 end assert_equal ["M::OuterAfter", "M::OuterBefore"], outer.sort - assert ActiveSupport::Dependencies.constant_watch_stack.empty? + assert ActiveSupport::Dependencies.constant_watch_stack.all? {|k,v| v.empty? } ensure Object.class_eval { remove_const :M } end diff --git a/activesupport/test/file_watcher_test.rb b/activesupport/test/file_watcher_test.rb new file mode 100644 index 0000000000..7b4d4be24f --- /dev/null +++ b/activesupport/test/file_watcher_test.rb @@ -0,0 +1,233 @@ +require 'abstract_unit' +require 'fssm' +require "fileutils" +require "timeout" + + +class FileWatcherTest < ActiveSupport::TestCase + class DumbBackend < ActiveSupport::FileWatcher::Backend + end + + def setup + @watcher = ActiveSupport::FileWatcher.new + + # In real life, the backend would take the path and use it to observe the file + # system. In our case, we will manually trigger the events for unit testing, + # so we can pass any path. + @backend = DumbBackend.new("RAILS_WOOT", @watcher) + + @payload = [] + @watcher.watch %r{^app/assets/.*\.scss$} do |pay| + pay.each do |status, files| + files.sort! + end + @payload << pay + end + end + + def test_use_triple_equals + fw = ActiveSupport::FileWatcher.new + called = [] + fw.watch("some_arbitrary_file.rb") do |file| + called << "omg" + end + fw.trigger(%w{ some_arbitrary_file.rb }) + assert_equal ['omg'], called + end + + def test_one_change + @backend.trigger("app/assets/main.scss" => :changed) + assert_equal({:changed => ["app/assets/main.scss"]}, @payload.first) + end + + def test_multiple_changes + @backend.trigger("app/assets/main.scss" => :changed, "app/assets/javascripts/foo.coffee" => :changed) + assert_equal([{:changed => ["app/assets/main.scss"]}], @payload) + end + + def test_multiple_changes_match + @backend.trigger("app/assets/main.scss" => :changed, "app/assets/print.scss" => :changed, "app/assets/javascripts/foo.coffee" => :changed) + assert_equal([{:changed => ["app/assets/main.scss", "app/assets/print.scss"]}], @payload) + end + + def test_multiple_state_changes + @backend.trigger("app/assets/main.scss" => :created, "app/assets/print.scss" => :changed) + assert_equal([{:changed => ["app/assets/print.scss"], :created => ["app/assets/main.scss"]}], @payload) + end + + def test_more_blocks + payload = [] + @watcher.watch %r{^config/routes\.rb$} do |pay| + payload << pay + end + + @backend.trigger "config/routes.rb" => :changed + assert_equal [:changed => ["config/routes.rb"]], payload + assert_equal [], @payload + end + + def test_overlapping_watchers + payload = [] + @watcher.watch %r{^app/assets/main\.scss$} do |pay| + payload << pay + end + + @backend.trigger "app/assets/print.scss" => :changed, "app/assets/main.scss" => :changed + assert_equal [:changed => ["app/assets/main.scss"]], payload + assert_equal [:changed => ["app/assets/main.scss", "app/assets/print.scss"]], @payload + end +end + +module FSSM::Backends + class Polling + def initialize_with_low_latency(options={}) + initialize_without_low_latency(options.merge(:latency => 0.1)) + end + alias_method_chain :initialize, :low_latency + end +end + +class FSSMFileWatcherTest < ActiveSupport::TestCase + class FSSMBackend < ActiveSupport::FileWatcher::Backend + def initialize(path, watcher) + super + + monitor = FSSM::Monitor.new + monitor.path(path, '**/*') do |p| + p.update { |base, relative| trigger relative => :changed } + p.delete { |base, relative| trigger relative => :deleted } + p.create { |base, relative| trigger relative => :created } + end + + @thread = Thread.new do + monitor.run + end + end + + def stop + @thread.kill + end + end + + def setup + Thread.abort_on_exception = true + + @payload = [] + @triggered = false + + @watcher = ActiveSupport::FileWatcher.new + + @path = path = File.expand_path("../tmp", __FILE__) + FileUtils.rm_rf path + + create "app/assets/main.scss", true + create "app/assets/javascripts/foo.coffee", true + create "app/assets/print.scss", true + create "app/assets/videos.scss", true + + @backend = FSSMBackend.new(path, @watcher) + + @watcher.watch %r{^app/assets/.*\.scss$} do |pay| + pay.each do |status, files| + files.sort! + end + @payload << pay + trigger + end + end + + def teardown + @backend.stop + Thread.abort_on_exception = false + end + + def create(path, past = false) + wait(past) do + path = File.join(@path, path) + FileUtils.mkdir_p(File.dirname(path)) + + FileUtils.touch(path) + File.utime(Time.now - 100, Time.now - 100, path) if past + end + end + + def change(path) + wait do + FileUtils.touch(File.join(@path, path)) + end + end + + def delete(path) + wait do + FileUtils.rm(File.join(@path, path)) + end + end + + def wait(past = false) + yield + return if past + + begin + Timeout.timeout(1) do + sleep 0.05 until @triggered + end + rescue Timeout::Error + end + + @triggered = false + end + + def trigger + @triggered = true + end + + def test_one_change + change "app/assets/main.scss" + assert_equal({:changed => ["app/assets/main.scss"]}, @payload.first) + end + + def test_multiple_changes + change "app/assets/main.scss" + change "app/assets/javascripts/foo.coffee" + assert_equal([{:changed => ["app/assets/main.scss"]}], @payload) + end + + def test_multiple_changes_match + change "app/assets/main.scss" + change "app/assets/print.scss" + change "app/assets/javascripts/foo.coffee" + assert_equal([{:changed => ["app/assets/main.scss"]}, {:changed => ["app/assets/print.scss"]}], @payload) + end + + def test_multiple_state_changes + create "app/assets/new.scss" + change "app/assets/print.scss" + delete "app/assets/videos.scss" + assert_equal([{:created => ["app/assets/new.scss"]}, {:changed => ["app/assets/print.scss"]}, {:deleted => ["app/assets/videos.scss"]}], @payload) + end + + def test_more_blocks + payload = [] + @watcher.watch %r{^config/routes\.rb$} do |pay| + payload << pay + trigger + end + + create "config/routes.rb" + assert_equal [{:created => ["config/routes.rb"]}], payload + assert_equal [], @payload + end + + def test_overlapping_watchers + payload = [] + @watcher.watch %r{^app/assets/main\.scss$} do |pay| + payload << pay + trigger + end + + change "app/assets/main.scss" + change "app/assets/print.scss" + assert_equal [{:changed => ["app/assets/main.scss"]}], payload + assert_equal [{:changed => ["app/assets/main.scss"]}, {:changed => ["app/assets/print.scss"]}], @payload + end +end diff --git a/activesupport/test/gzip_test.rb b/activesupport/test/gzip_test.rb index 2a24c0bd0d..f564e63f29 100644 --- a/activesupport/test/gzip_test.rb +++ b/activesupport/test/gzip_test.rb @@ -1,7 +1,18 @@ require 'abstract_unit' +require 'active_support/core_ext/object/blank' class GzipTest < Test::Unit::TestCase def test_compress_should_decompress_to_the_same_value assert_equal "Hello World", ActiveSupport::Gzip.decompress(ActiveSupport::Gzip.compress("Hello World")) end -end
\ No newline at end of file + + def test_compress_should_return_a_binary_string + compressed = ActiveSupport::Gzip.compress('') + + if "".encoding_aware? + assert_equal Encoding.find('binary'), compressed.encoding + end + + assert !compressed.blank?, "a compressed blank string should not be blank" + end +end diff --git a/activesupport/test/i18n_test.rb b/activesupport/test/i18n_test.rb index dfcd4f822d..34825c9b8f 100644 --- a/activesupport/test/i18n_test.rb +++ b/activesupport/test/i18n_test.rb @@ -7,68 +7,68 @@ class I18nTest < Test::Unit::TestCase @date = Date.parse("2008-7-2") @time = Time.utc(2008, 7, 2, 16, 47, 1) end - + def test_time_zone_localization_with_default_format now = Time.local(2000) assert_equal now.strftime("%a, %d %b %Y %H:%M:%S %z"), I18n.localize(now) end - + def test_date_localization_should_use_default_format assert_equal @date.strftime("%Y-%m-%d"), I18n.localize(@date) end - + def test_date_localization_with_default_format assert_equal @date.strftime("%Y-%m-%d"), I18n.localize(@date, :format => :default) end - + def test_date_localization_with_short_format assert_equal @date.strftime("%b %d"), I18n.localize(@date, :format => :short) end - + def test_date_localization_with_long_format assert_equal @date.strftime("%B %d, %Y"), I18n.localize(@date, :format => :long) end - - def test_time_localization_should_use_default_format + + def test_time_localization_should_use_default_format assert_equal @time.strftime("%a, %d %b %Y %H:%M:%S %z"), I18n.localize(@time) end - + def test_time_localization_with_default_format assert_equal @time.strftime("%a, %d %b %Y %H:%M:%S %z"), I18n.localize(@time, :format => :default) end - + def test_time_localization_with_short_format assert_equal @time.strftime("%d %b %H:%M"), I18n.localize(@time, :format => :short) end - + def test_time_localization_with_long_format assert_equal @time.strftime("%B %d, %Y %H:%M"), I18n.localize(@time, :format => :long) end - + def test_day_names assert_equal Date::DAYNAMES, I18n.translate(:'date.day_names') end - + def test_abbr_day_names assert_equal Date::ABBR_DAYNAMES, I18n.translate(:'date.abbr_day_names') end - + def test_month_names assert_equal Date::MONTHNAMES, I18n.translate(:'date.month_names') end - + def test_abbr_month_names assert_equal Date::ABBR_MONTHNAMES, I18n.translate(:'date.abbr_month_names') end - + def test_date_order assert_equal [:year, :month, :day], I18n.translate(:'date.order') end - + def test_time_am assert_equal 'am', I18n.translate(:'time.am') end - + def test_time_pm assert_equal 'pm', I18n.translate(:'time.pm') end diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb index 2990177bed..1670d9ee7d 100644 --- a/activesupport/test/inflector_test.rb +++ b/activesupport/test/inflector_test.rb @@ -22,20 +22,55 @@ class InflectorTest < Test::Unit::TestCase assert_equal "", ActiveSupport::Inflector.pluralize("") end + ActiveSupport::Inflector.inflections.uncountable.each do |word| + define_method "test_uncountability_of_#{word}" do + assert_equal word, ActiveSupport::Inflector.singularize(word) + assert_equal word, ActiveSupport::Inflector.pluralize(word) + assert_equal ActiveSupport::Inflector.pluralize(word), ActiveSupport::Inflector.singularize(word) + end + end + + def test_uncountable_word_is_not_greedy + uncountable_word = "ors" + countable_word = "sponsor" + + cached_uncountables = ActiveSupport::Inflector.inflections.uncountables + + ActiveSupport::Inflector.inflections.uncountable << uncountable_word + + assert_equal uncountable_word, ActiveSupport::Inflector.singularize(uncountable_word) + assert_equal uncountable_word, ActiveSupport::Inflector.pluralize(uncountable_word) + assert_equal ActiveSupport::Inflector.pluralize(uncountable_word), ActiveSupport::Inflector.singularize(uncountable_word) + + assert_equal "sponsor", ActiveSupport::Inflector.singularize(countable_word) + assert_equal "sponsors", ActiveSupport::Inflector.pluralize(countable_word) + assert_equal "sponsor", ActiveSupport::Inflector.singularize(ActiveSupport::Inflector.pluralize(countable_word)) + + ensure + ActiveSupport::Inflector.inflections.instance_variable_set :@uncountables, cached_uncountables + end + SingularToPlural.each do |singular, plural| - define_method "test_pluralize_#{singular}" do + define_method "test_pluralize_singular_#{singular}" do assert_equal(plural, ActiveSupport::Inflector.pluralize(singular)) assert_equal(plural.capitalize, ActiveSupport::Inflector.pluralize(singular.capitalize)) end end SingularToPlural.each do |singular, plural| - define_method "test_singularize_#{plural}" do + define_method "test_singularize_plural_#{plural}" do assert_equal(singular, ActiveSupport::Inflector.singularize(plural)) assert_equal(singular.capitalize, ActiveSupport::Inflector.singularize(plural.capitalize)) end end + SingularToPlural.each do |singular, plural| + define_method "test_pluralize_plural_#{plural}" do + assert_equal(plural, ActiveSupport::Inflector.pluralize(plural)) + assert_equal(plural.capitalize, ActiveSupport::Inflector.pluralize(plural.capitalize)) + end + end + def test_overwrite_previous_inflectors assert_equal("series", ActiveSupport::Inflector.singularize("series")) ActiveSupport::Inflector.inflections.singular "series", "serie" diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb index 59515dad32..2b144e5931 100644 --- a/activesupport/test/inflector_test_cases.rb +++ b/activesupport/test/inflector_test_cases.rb @@ -44,6 +44,7 @@ module InflectorTestCases "datum" => "data", "medium" => "media", + "stadium" => "stadia", "analysis" => "analyses", "node_child" => "node_children", diff --git a/activesupport/test/json/decoding_test.rb b/activesupport/test/json/decoding_test.rb index d2e3efaa6b..88cf97de7e 100644 --- a/activesupport/test/json/decoding_test.rb +++ b/activesupport/test/json/decoding_test.rb @@ -17,8 +17,16 @@ class TestJSONDecoding < ActiveSupport::TestCase %({"matzue": "松江", "asakusa": "浅草"}) => {"matzue" => "松江", "asakusa" => "浅草"}, %({"a": "2007-01-01"}) => {'a' => Date.new(2007, 1, 1)}, %({"a": "2007-01-01 01:12:34 Z"}) => {'a' => Time.utc(2007, 1, 1, 1, 12, 34)}, + %(["2007-01-01 01:12:34 Z"]) => [Time.utc(2007, 1, 1, 1, 12, 34)], + %(["2007-01-01 01:12:34 Z", "2007-01-01 01:12:35 Z"]) => [Time.utc(2007, 1, 1, 1, 12, 34), Time.utc(2007, 1, 1, 1, 12, 35)], # no time zone %({"a": "2007-01-01 01:12:34"}) => {'a' => "2007-01-01 01:12:34"}, + # invalid date + %({"a": "1089-10-40"}) => {'a' => "1089-10-40"}, + # xmlschema date notation + %({"a": "2009-08-10T19:01:02Z"}) => {'a' => Time.utc(2009, 8, 10, 19, 1, 2)}, + %({"a": "2009-08-10T19:01:02+02:00"}) => {'a' => Time.utc(2009, 8, 10, 17, 1, 2)}, + %({"a": "2009-08-10T19:01:02-05:00"}) => {'a' => Time.utc(2009, 8, 11, 00, 1, 2)}, # needs to be *exact* %({"a": " 2007-01-01 01:12:34 Z "}) => {'a' => " 2007-01-01 01:12:34 Z "}, %({"a": "2007-01-01 : it's your birthday"}) => {'a' => "2007-01-01 : it's your birthday"}, @@ -41,7 +49,11 @@ class TestJSONDecoding < ActiveSupport::TestCase [{'d' => Date.new(1970, 1, 1), 's' => ' escape'},{'d' => Date.new(1970, 1, 1), 's' => ' escape'}], %q([{"d":"1970-01-01","s":"http:\/\/example.com"},{"d":"1970-01-01","s":"http:\/\/example.com"}]) => [{'d' => Date.new(1970, 1, 1), 's' => 'http://example.com'}, - {'d' => Date.new(1970, 1, 1), 's' => 'http://example.com'}] + {'d' => Date.new(1970, 1, 1), 's' => 'http://example.com'}], + # tests escaping of "\n" char with Yaml backend + %q({"a":"\n"}) => {"a"=>"\n"}, + %q({"a":"\u000a"}) => {"a"=>"\n"}, + %q({"a":"Line1\u000aLine2"}) => {"a"=>"Line1\nLine2"} } # load the default JSON backend @@ -57,20 +69,16 @@ class TestJSONDecoding < ActiveSupport::TestCase ActiveSupport.parse_json_times = true silence_warnings do ActiveSupport::JSON.with_backend backend do - assert_nothing_raised do - assert_equal expected, ActiveSupport::JSON.decode(json) - end + assert_equal expected, ActiveSupport::JSON.decode(json) end end end end - end - if backends.include?("JSONGem") - test "json decodes time json with time parsing disabled" do + test "json decodes time json with time parsing disabled with the #{backend} backend" do ActiveSupport.parse_json_times = false expected = {"a" => "2007-01-01 01:12:34 Z"} - ActiveSupport::JSON.with_backend "JSONGem" do + ActiveSupport::JSON.with_backend backend do assert_equal expected, ActiveSupport::JSON.decode(%({"a": "2007-01-01 01:12:34 Z"})) end end diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb index 1527d02d16..d5fcbf15b7 100644 --- a/activesupport/test/json/encoding_test.rb +++ b/activesupport/test/json/encoding_test.rb @@ -1,5 +1,6 @@ # encoding: utf-8 require 'abstract_unit' +require 'active_support/core_ext/string/inflections' require 'active_support/json' class TestJSONEncoding < Test::Unit::TestCase @@ -108,12 +109,24 @@ class TestJSONEncoding < Test::Unit::TestCase end end - def test_exception_raised_when_encoding_circular_reference + def test_exception_raised_when_encoding_circular_reference_in_array a = [1] a << a assert_raise(ActiveSupport::JSON::Encoding::CircularReferenceError) { ActiveSupport::JSON.encode(a) } end + def test_exception_raised_when_encoding_circular_reference_in_hash + a = { :name => 'foo' } + a[:next] = a + assert_raise(ActiveSupport::JSON::Encoding::CircularReferenceError) { ActiveSupport::JSON.encode(a) } + end + + def test_exception_raised_when_encoding_circular_reference_in_hash_inside_array + a = { :name => 'foo', :sub => [] } + a[:sub] << a + assert_raise(ActiveSupport::JSON::Encoding::CircularReferenceError) { ActiveSupport::JSON.encode(a) } + end + def test_hash_key_identifiers_are_always_quoted values = {0 => 0, 1 => 1, :_ => :_, "$" => "$", "a" => "a", :A => :A, :A0 => :A0, "A0B" => "A0B"} assert_equal %w( "$" "A" "A0" "A0B" "_" "a" "0" "1" ).sort, object_keys(ActiveSupport::JSON.encode(values)) @@ -152,6 +165,87 @@ class TestJSONEncoding < Test::Unit::TestCase end end + def test_hash_should_pass_encoding_options_to_children_in_as_json + person = { + :name => 'John', + :address => { + :city => 'London', + :country => 'UK' + } + } + json = person.as_json :only => [:address, :city] + + assert_equal({ 'address' => { 'city' => 'London' }}, json) + end + + def test_hash_should_pass_encoding_options_to_children_in_to_json + person = { + :name => 'John', + :address => { + :city => 'London', + :country => 'UK' + } + } + json = person.to_json :only => [:address, :city] + + assert_equal(%({"address":{"city":"London"}}), json) + end + + def test_array_should_pass_encoding_options_to_children_in_as_json + people = [ + { :name => 'John', :address => { :city => 'London', :country => 'UK' }}, + { :name => 'Jean', :address => { :city => 'Paris' , :country => 'France' }} + ] + json = people.as_json :only => [:address, :city] + expected = [ + { 'address' => { 'city' => 'London' }}, + { 'address' => { 'city' => 'Paris' }} + ] + + assert_equal(expected, json) + end + + def test_array_should_pass_encoding_options_to_children_in_to_json + people = [ + { :name => 'John', :address => { :city => 'London', :country => 'UK' }}, + { :name => 'Jean', :address => { :city => 'Paris' , :country => 'France' }} + ] + json = people.to_json :only => [:address, :city] + + assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json) + end + + def test_struct_encoding + Struct.new('UserNameAndEmail', :name, :email) + Struct.new('UserNameAndDate', :name, :date) + Struct.new('Custom', :name, :sub) + user_email = Struct::UserNameAndEmail.new 'David', 'sample@example.com' + user_birthday = Struct::UserNameAndDate.new 'David', Date.new(2010, 01, 01) + custom = Struct::Custom.new 'David', user_birthday + + + json_strings = "" + json_string_and_date = "" + json_custom = "" + + assert_nothing_raised do + json_strings = user_email.to_json + json_string_and_date = user_birthday.to_json + json_custom = custom.to_json + end + + assert_equal({"name" => "David", + "sub" => { + "name" => "David", + "date" => "2010/01/01" }}, JSON.parse(json_custom)) + + assert_equal({"name" => "David", "email" => "sample@example.com"}, + JSON.parse(json_strings)) + + assert_equal({"name" => "David", "date" => "2010/01/01"}, + JSON.parse(json_string_and_date)) + end + protected def object_keys(json_object) diff --git a/activesupport/test/memoizable_test.rb b/activesupport/test/memoizable_test.rb index b11fa8d346..bceac1315b 100644 --- a/activesupport/test/memoizable_test.rb +++ b/activesupport/test/memoizable_test.rb @@ -36,6 +36,13 @@ class MemoizableTest < ActiveSupport::TestCase memoize :name, :age + protected + + def memoize_protected_test + 'protected' + end + memoize :memoize_protected_test + private def is_developer? @@ -237,6 +244,13 @@ class MemoizableTest < ActiveSupport::TestCase assert_raise(RuntimeError) { company.memoize :name } end + def test_protected_method_memoization + person = Person.new + + assert_raise(NoMethodError) { person.memoize_protected_test } + assert_equal "protected", person.send(:memoize_protected_test) + end + def test_private_method_memoization person = Person.new diff --git a/activesupport/test/message_encryptor_test.rb b/activesupport/test/message_encryptor_test.rb index 684b931176..419ac14283 100644 --- a/activesupport/test/message_encryptor_test.rb +++ b/activesupport/test/message_encryptor_test.rb @@ -14,18 +14,18 @@ class MessageEncryptorTest < Test::Unit::TestCase @encryptor = ActiveSupport::MessageEncryptor.new(ActiveSupport::SecureRandom.hex(64)) @data = { :some => "data", :now => Time.local(2010) } end - + def test_simple_round_tripping message = @encryptor.encrypt(@data) assert_equal @data, @encryptor.decrypt(message) end - + def test_encrypting_twice_yields_differing_cipher_text first_messqage = @encryptor.encrypt(@data) second_message = @encryptor.encrypt(@data) assert_not_equal first_messqage, second_message end - + def test_messing_with_either_value_causes_failure text, iv = @encryptor.encrypt(@data).split("--") assert_not_decrypted([iv, text] * "--") @@ -33,20 +33,20 @@ class MessageEncryptorTest < Test::Unit::TestCase assert_not_decrypted([munge(text), iv] * "--") assert_not_decrypted([munge(text), munge(iv)] * "--") end - + def test_signed_round_tripping message = @encryptor.encrypt_and_sign(@data) assert_equal @data, @encryptor.decrypt_and_verify(message) end - - + + private def assert_not_decrypted(value) assert_raise(ActiveSupport::MessageEncryptor::InvalidMessage) do @encryptor.decrypt(value) end end - + def munge(base64_string) bits = ActiveSupport::Base64.decode64(base64_string) bits.reverse! diff --git a/activesupport/test/multibyte_conformance.rb b/activesupport/test/multibyte_conformance.rb index 6ec9a839af..b3b477bb75 100644 --- a/activesupport/test/multibyte_conformance.rb +++ b/activesupport/test/multibyte_conformance.rb @@ -27,21 +27,21 @@ end class MultibyteConformanceTest < Test::Unit::TestCase include MultibyteTestHelpers - + UNIDATA_URL = "http://www.unicode.org/Public/#{ActiveSupport::Multibyte::Unicode::UNICODE_VERSION}/ucd" UNIDATA_FILE = '/NormalizationTest.txt' CACHE_DIR = File.join(Dir.tmpdir, 'cache') - + def setup FileUtils.mkdir_p(CACHE_DIR) Downloader.download(UNIDATA_URL + UNIDATA_FILE, CACHE_DIR + UNIDATA_FILE) @proxy = ActiveSupport::Multibyte::Chars end - + def test_normalizations_C each_line_of_norm_tests do |*cols| col1, col2, col3, col4, col5, comment = *cols - + # CONFORMANCE: # 1. The following invariants must be true for all conformant implementations # @@ -56,7 +56,7 @@ class MultibyteConformanceTest < Test::Unit::TestCase assert_equal_codepoints col4, @proxy.new(col5).normalize(:c), "Form C - Col 4 has to be C(5) - #{comment}" end end - + def test_normalizations_D each_line_of_norm_tests do |*cols| col1, col2, col3, col4, col5, comment = *cols @@ -71,10 +71,10 @@ class MultibyteConformanceTest < Test::Unit::TestCase assert_equal_codepoints col5, @proxy.new(col5).normalize(:d), "Form D - Col 5 has to be NFD(5) - #{comment}" end end - + def test_normalizations_KC each_line_of_norm_tests do | *cols | - col1, col2, col3, col4, col5, comment = *cols + col1, col2, col3, col4, col5, comment = *cols # # NFKC # c4 == NFKC(c1) == NFKC(c2) == NFKC(c3) == NFKC(c4) == NFKC(c5) @@ -85,10 +85,10 @@ class MultibyteConformanceTest < Test::Unit::TestCase assert_equal_codepoints col4, @proxy.new(col5).normalize(:kc), "Form D - Col 4 has to be NFKC(5) - #{comment}" end end - + def test_normalizations_KD each_line_of_norm_tests do | *cols | - col1, col2, col3, col4, col5, comment = *cols + col1, col2, col3, col4, col5, comment = *cols # # NFKD # c5 == NFKD(c1) == NFKD(c2) == NFKD(c3) == NFKD(c4) == NFKD(c5) @@ -99,7 +99,7 @@ class MultibyteConformanceTest < Test::Unit::TestCase assert_equal_codepoints col5, @proxy.new(col5).normalize(:kd), "Form KD - Col 5 has to be NFKD(5) - #{comment}" end end - + protected def each_line_of_norm_tests(&block) lines = 0 @@ -108,21 +108,21 @@ class MultibyteConformanceTest < Test::Unit::TestCase until f.eof? || (max_test_lines > 38 and lines > max_test_lines) lines += 1 line = f.gets.chomp! - next if (line.empty? || line =~ /^\#/) - + next if (line.empty? || line =~ /^\#/) + cols, comment = line.split("#") cols = cols.split(";").map{|e| e.strip}.reject{|e| e.empty? } next unless cols.length == 5 - + # codepoints are in hex in the test suite, pack wants them as integers cols.map!{|c| c.split.map{|codepoint| codepoint.to_i(16)}.pack("U*") } cols << comment - + yield(*cols) end end end - + def inspect_codepoints(str) str.to_s.unpack("U*").map{|cp| cp.to_s(16) }.join(' ') end diff --git a/activesupport/test/notifications_test.rb b/activesupport/test/notifications_test.rb index 9faa11efbc..7b48b3f85b 100644 --- a/activesupport/test/notifications_test.rb +++ b/activesupport/test/notifications_test.rb @@ -3,14 +3,19 @@ require 'abstract_unit' module Notifications class TestCase < ActiveSupport::TestCase def setup - ActiveSupport::Notifications.notifier = nil - @notifier = ActiveSupport::Notifications.notifier + @old_notifier = ActiveSupport::Notifications.notifier + @notifier = ActiveSupport::Notifications::Fanout.new + ActiveSupport::Notifications.notifier = @notifier @events = [] @named_events = [] @subscription = @notifier.subscribe { |*args| @events << event(*args) } @named_subscription = @notifier.subscribe("named.subscription") { |*args| @named_events << event(*args) } end + def teardown + ActiveSupport::Notifications.notifier = @old_notifier + end + private def event(*args) diff --git a/activesupport/test/option_merger_test.rb b/activesupport/test/option_merger_test.rb index 33e3e69666..5b2e16a212 100644 --- a/activesupport/test/option_merger_test.rb +++ b/activesupport/test/option_merger_test.rb @@ -65,7 +65,7 @@ class OptionMergerTest < Test::Unit::TestCase end end end - + def test_nested_method_with_options_using_lamdba local_lamdba = lambda { { :lambda => true } } with_options(@options) do |o| diff --git a/activesupport/test/ordered_hash_test.rb b/activesupport/test/ordered_hash_test.rb index f47e896487..50168fa78f 100644 --- a/activesupport/test/ordered_hash_test.rb +++ b/activesupport/test/ordered_hash_test.rb @@ -78,20 +78,26 @@ class OrderedHashTest < Test::Unit::TestCase def test_each_key keys = [] - @ordered_hash.each_key { |k| keys << k } + assert_equal @ordered_hash, @ordered_hash.each_key { |k| keys << k } assert_equal @keys, keys + expected_class = RUBY_VERSION < '1.9' ? Enumerable::Enumerator : Enumerator + assert_kind_of expected_class, @ordered_hash.each_key end def test_each_value values = [] - @ordered_hash.each_value { |v| values << v } + assert_equal @ordered_hash, @ordered_hash.each_value { |v| values << v } assert_equal @values, values + expected_class = RUBY_VERSION < '1.9' ? Enumerable::Enumerator : Enumerator + assert_kind_of expected_class, @ordered_hash.each_value end def test_each values = [] - @ordered_hash.each {|key, value| values << value} + assert_equal @ordered_hash, @ordered_hash.each {|key, value| values << value} assert_equal @values, values + expected_class = RUBY_VERSION < '1.9' ? Enumerable::Enumerator : Enumerator + assert_kind_of expected_class, @ordered_hash.each end def test_each_with_index @@ -109,6 +115,14 @@ class OrderedHashTest < Test::Unit::TestCase assert_equal @keys, keys end + def test_find_all + assert_equal @keys, @ordered_hash.find_all { true }.map(&:first) + end + + def test_select + assert_equal @keys, @ordered_hash.select { true }.map(&:first) + end + def test_delete_if copy = @ordered_hash.dup copy.delete('pink') @@ -217,8 +231,8 @@ class OrderedHashTest < Test::Unit::TestCase ActiveSupport::OrderedHash[1,2,3,4,5] flunk "Hash::[] should have raised an exception on initialization " + "with an odd number of parameters" - rescue - assert_equal "odd number of arguments for Hash", $!.message + rescue ArgumentError => e + assert_equal "odd number of arguments for Hash", e.message end end @@ -231,14 +245,14 @@ class OrderedHashTest < Test::Unit::TestCase def test_each_after_yaml_serialization values = [] - @deserialized_ordered_hash = YAML::load(YAML::dump(@ordered_hash)) + @deserialized_ordered_hash = YAML.load(YAML.dump(@ordered_hash)) @deserialized_ordered_hash.each {|key, value| values << value} assert_equal @values, values end def test_order_after_yaml_serialization - @deserialized_ordered_hash = YAML::load(YAML::dump(@ordered_hash)) + @deserialized_ordered_hash = YAML.load(YAML.dump(@ordered_hash)) assert_equal @keys, @deserialized_ordered_hash.keys assert_equal @values, @deserialized_ordered_hash.values @@ -247,12 +261,34 @@ class OrderedHashTest < Test::Unit::TestCase def test_order_after_yaml_serialization_with_nested_arrays @ordered_hash[:array] = %w(a b c) - @deserialized_ordered_hash = YAML::load(YAML::dump(@ordered_hash)) + @deserialized_ordered_hash = YAML.load(YAML.dump(@ordered_hash)) assert_equal @ordered_hash.keys, @deserialized_ordered_hash.keys assert_equal @ordered_hash.values, @deserialized_ordered_hash.values end + begin + require 'psych' + + def test_psych_serialize + @deserialized_ordered_hash = Psych.load(Psych.dump(@ordered_hash)) + + values = @deserialized_ordered_hash.map { |_, value| value } + assert_equal @values, values + end + + def test_psych_serialize_tag + yaml = Psych.dump(@ordered_hash) + assert_match '!omap', yaml + end + rescue LoadError + end + + def test_has_yaml_tag + @ordered_hash[:array] = %w(a b c) + assert_match '!omap', YAML.dump(@ordered_hash) + end + def test_update_sets_keys @updated_ordered_hash = ActiveSupport::OrderedHash.new @updated_ordered_hash.update(:name => "Bob") diff --git a/activesupport/test/rescuable_test.rb b/activesupport/test/rescuable_test.rb index 1c74ce8b2a..bf4f5265ef 100644 --- a/activesupport/test/rescuable_test.rb +++ b/activesupport/test/rescuable_test.rb @@ -24,7 +24,7 @@ class Stargate rescue_from NuclearExplosion do @result = 'alldead' end - + rescue_from MadRonon do |e| @result = e.message end @@ -80,16 +80,16 @@ class RescueableTest < Test::Unit::TestCase @stargate.dispatch :attack assert_equal 'killed', @stargate.result end - + def test_rescue_from_with_block @stargate.dispatch :nuke assert_equal 'alldead', @stargate.result end - + def test_rescue_from_with_block_with_args @stargate.dispatch :ronanize assert_equal 'dex', @stargate.result - end + end def test_rescues_defined_later_are_added_at_end_of_the_rescue_handlers_array expected = ["WraithAttack", "WraithAttack", "NuclearExplosion", "MadRonon"] diff --git a/activesupport/test/safe_buffer_test.rb b/activesupport/test/safe_buffer_test.rb index bf61f9e58c..a4e2acbb32 100644 --- a/activesupport/test/safe_buffer_test.rb +++ b/activesupport/test/safe_buffer_test.rb @@ -1,4 +1,10 @@ require 'abstract_unit' +begin + require 'psych' +rescue LoadError +end + +require 'yaml' class SafeBufferTest < ActiveSupport::TestCase def setup @@ -38,4 +44,20 @@ class SafeBufferTest < ActiveSupport::TestCase new_buffer = @buffer.to_s assert_equal ActiveSupport::SafeBuffer, new_buffer.class end + + def test_to_yaml + str = 'hello!' + buf = ActiveSupport::SafeBuffer.new str + yaml = buf.to_yaml + + assert_match(/^--- #{str}/, yaml) + assert_equal 'hello!', YAML.load(yaml) + end + + def test_nested + str = 'hello!' + data = { 'str' => ActiveSupport::SafeBuffer.new(str) } + yaml = YAML.dump data + assert_equal({'str' => str}, YAML.load(yaml)) + end end diff --git a/activesupport/test/test_case_test.rb b/activesupport/test/test_case_test.rb new file mode 100644 index 0000000000..756d21b3e4 --- /dev/null +++ b/activesupport/test/test_case_test.rb @@ -0,0 +1,61 @@ +require 'abstract_unit' + +module ActiveSupport + class TestCaseTest < ActiveSupport::TestCase + class FakeRunner + attr_reader :puked + + def initialize + @puked = [] + end + + def puke(klass, name, e) + @puked << [klass, name, e] + end + + def options + nil + end + end + + if defined?(MiniTest::Assertions) && TestCase < MiniTest::Assertions + def test_callback_with_exception + tc = Class.new(TestCase) do + setup :bad_callback + def bad_callback; raise 'oh noes' end + def test_true; assert true end + end + + test_name = 'test_true' + fr = FakeRunner.new + + test = tc.new test_name + test.run fr + klass, name, exception = *fr.puked.first + + assert_equal tc, klass + assert_equal test_name, name + assert_equal 'oh noes', exception.message + end + + def test_teardown_callback_with_exception + tc = Class.new(TestCase) do + teardown :bad_callback + def bad_callback; raise 'oh noes' end + def test_true; assert true end + end + + test_name = 'test_true' + fr = FakeRunner.new + + test = tc.new test_name + test.run fr + klass, name, exception = *fr.puked.first + + assert_equal tc, klass + assert_equal test_name, name + assert_equal 'oh noes', exception.message + end + end + end +end diff --git a/activesupport/test/test_test.rb b/activesupport/test/test_test.rb index 633d3b212b..ee5a20c789 100644 --- a/activesupport/test/test_test.rb +++ b/activesupport/test/test_test.rb @@ -89,19 +89,19 @@ end class AssertBlankTest < ActiveSupport::TestCase BLANK = [ EmptyTrue.new, nil, false, '', ' ', " \n\t \r ", [], {} ] NOT_BLANK = [ EmptyFalse.new, Object.new, true, 0, 1, 'x', [nil], { nil => 0 } ] - + def test_assert_blank_true BLANK.each { |v| assert_blank v } end - + def test_assert_blank_false NOT_BLANK.each { |v| - begin + begin assert_blank v fail 'should not get to here' rescue Exception => e - assert_match(/is not blank/, e.message) - end + assert_match(/is not blank/, e.message) + end } end end @@ -109,30 +109,23 @@ end class AssertPresentTest < ActiveSupport::TestCase BLANK = [ EmptyTrue.new, nil, false, '', ' ', " \n\t \r ", [], {} ] NOT_BLANK = [ EmptyFalse.new, Object.new, true, 0, 1, 'x', [nil], { nil => 0 } ] - + def test_assert_blank_true NOT_BLANK.each { |v| assert_present v } end - + def test_assert_blank_false BLANK.each { |v| - begin + begin assert_present v fail 'should not get to here' rescue Exception => e - assert_match(/is blank/, e.message) - end + assert_match(/is blank/, e.message) + end } end end -# These should always pass -if ActiveSupport::Testing.const_defined?(:Default) - class NotTestingThingsTest < Test::Unit::TestCase - include ActiveSupport::Testing::Default - end -end - class AlsoDoingNothingTest < ActiveSupport::TestCase end diff --git a/activesupport/test/test_xml_mini.rb b/activesupport/test/test_xml_mini.rb index 585eb15c6e..6dbcd1f40b 100644 --- a/activesupport/test/test_xml_mini.rb +++ b/activesupport/test/test_xml_mini.rb @@ -1,49 +1,100 @@ require 'abstract_unit' require 'active_support/xml_mini' +require 'active_support/builder' -class XmlMiniTest < Test::Unit::TestCase - def test_rename_key_dasherizes_by_default - assert_equal "my-key", ActiveSupport::XmlMini.rename_key("my_key") - end +module XmlMiniTest + class RenameKeyTest < Test::Unit::TestCase + def test_rename_key_dasherizes_by_default + assert_equal "my-key", ActiveSupport::XmlMini.rename_key("my_key") + end - def test_rename_key_does_nothing_with_dasherize_true - assert_equal "my-key", ActiveSupport::XmlMini.rename_key("my_key", :dasherize => true) - end + def test_rename_key_does_nothing_with_dasherize_true + assert_equal "my-key", ActiveSupport::XmlMini.rename_key("my_key", :dasherize => true) + end - def test_rename_key_does_nothing_with_dasherize_false - assert_equal "my_key", ActiveSupport::XmlMini.rename_key("my_key", :dasherize => false) - end + def test_rename_key_does_nothing_with_dasherize_false + assert_equal "my_key", ActiveSupport::XmlMini.rename_key("my_key", :dasherize => false) + end - def test_rename_key_camelizes_with_camelize_true - assert_equal "MyKey", ActiveSupport::XmlMini.rename_key("my_key", :camelize => true) - end + def test_rename_key_camelizes_with_camelize_false + assert_equal "my_key", ActiveSupport::XmlMini.rename_key("my_key", :camelize => false) + end - def test_rename_key_camelizes_with_camelize_true - assert_equal "MyKey", ActiveSupport::XmlMini.rename_key("my_key", :camelize => true) - end + def test_rename_key_camelizes_with_camelize_nil + assert_equal "my_key", ActiveSupport::XmlMini.rename_key("my_key", :camelize => nil) + end - def test_rename_key_does_not_dasherize_leading_underscores - assert_equal "_id", ActiveSupport::XmlMini.rename_key("_id") - end + def test_rename_key_camelizes_with_camelize_true + assert_equal "MyKey", ActiveSupport::XmlMini.rename_key("my_key", :camelize => true) + end - def test_rename_key_with_leading_underscore_dasherizes_interior_underscores - assert_equal "_my-key", ActiveSupport::XmlMini.rename_key("_my_key") - end + def test_rename_key_lower_camelizes_with_camelize_lower + assert_equal "myKey", ActiveSupport::XmlMini.rename_key("my_key", :camelize => :lower) + end - def test_rename_key_does_not_dasherize_trailing_underscores - assert_equal "id_", ActiveSupport::XmlMini.rename_key("id_") - end + def test_rename_key_lower_camelizes_with_camelize_upper + assert_equal "MyKey", ActiveSupport::XmlMini.rename_key("my_key", :camelize => :upper) + end - def test_rename_key_with_trailing_underscore_dasherizes_interior_underscores - assert_equal "my-key_", ActiveSupport::XmlMini.rename_key("my_key_") - end + def test_rename_key_does_not_dasherize_leading_underscores + assert_equal "_id", ActiveSupport::XmlMini.rename_key("_id") + end - def test_rename_key_does_not_dasherize_multiple_leading_underscores - assert_equal "__id", ActiveSupport::XmlMini.rename_key("__id") - end + def test_rename_key_with_leading_underscore_dasherizes_interior_underscores + assert_equal "_my-key", ActiveSupport::XmlMini.rename_key("_my_key") + end + + def test_rename_key_does_not_dasherize_trailing_underscores + assert_equal "id_", ActiveSupport::XmlMini.rename_key("id_") + end - def test_rename_key_does_not_dasherize_multiple_leading_underscores - assert_equal "id__", ActiveSupport::XmlMini.rename_key("id__") + def test_rename_key_with_trailing_underscore_dasherizes_interior_underscores + assert_equal "my-key_", ActiveSupport::XmlMini.rename_key("my_key_") + end + + def test_rename_key_does_not_dasherize_multiple_leading_underscores + assert_equal "__id", ActiveSupport::XmlMini.rename_key("__id") + end + + def test_rename_key_does_not_dasherize_multiple_leading_underscores + assert_equal "id__", ActiveSupport::XmlMini.rename_key("id__") + end end + class ToTagTest < ActiveSupport::TestCase + def assert_xml(xml) + assert_equal xml, @options[:builder].target! + end + + setup do + @xml = ActiveSupport::XmlMini + @options = {:skip_instruct => true, :builder => Builder::XmlMarkup.new} + end + + test "#to_tag accepts a callable object and passes options with the builder" do + @xml.to_tag(:some_tag, lambda {|o| o[:builder].br }, @options) + assert_xml "<br/>" + end + + test "#to_tag accepts a callable object and passes options and tag name" do + @xml.to_tag(:tag, lambda {|o, t| o[:builder].b(t) }, @options) + assert_xml "<b>tag</b>" + end + + test "#to_tag accepts an object responding to #to_xml and passes the options, where :root is key" do + obj = Object.new + obj.instance_eval do + def to_xml(options) options[:builder].yo(options[:root].to_s) end + end + + @xml.to_tag(:tag, obj, @options) + assert_xml "<yo>tag</yo>" + end + + test "#to_tag accepts arbitrary objects responding to #to_str" do + @xml.to_tag(:b, "Howdy", @options) + assert_xml "<b>Howdy</b>" + end + # TODO: test the remaining functions hidden in #to_tag. + end end diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb index af6eee69e5..49cefc6e82 100644 --- a/activesupport/test/time_zone_test.rb +++ b/activesupport/test/time_zone_test.rb @@ -216,7 +216,7 @@ class TimeZoneTest < Test::Unit::TestCase assert_equal "+0000", ActiveSupport::TimeZone.seconds_to_utc_offset(0, false) assert_equal "+0500", ActiveSupport::TimeZone.seconds_to_utc_offset(18_000, false) end - + def test_seconds_to_utc_offset_with_negative_offset assert_equal "-01:00", ActiveSupport::TimeZone.seconds_to_utc_offset(-3_600) assert_equal "-00:59", ActiveSupport::TimeZone.seconds_to_utc_offset(-3_599) diff --git a/activesupport/test/whiny_nil_test.rb b/activesupport/test/whiny_nil_test.rb index 4b9f06dead..ec3ca99ee6 100644 --- a/activesupport/test/whiny_nil_test.rb +++ b/activesupport/test/whiny_nil_test.rb @@ -33,9 +33,11 @@ class WhinyNilTest < Test::Unit::TestCase end def test_id + nil.stubs(:object_id).returns(999) nil.id rescue RuntimeError => nme assert_no_match(/nil:NilClass/, nme.message) + assert_match(/999/, nme.message) end def test_no_to_ary_coercion |