diff options
Diffstat (limited to 'activesupport/lib/active_support')
120 files changed, 1448 insertions, 1051 deletions
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? {} |