diff options
author | José Valim <jose.valim@gmail.com> | 2011-11-23 20:43:06 +0000 |
---|---|---|
committer | José Valim <jose.valim@gmail.com> | 2011-11-23 20:43:06 +0000 |
commit | e62de52aa398341a29b7ecef4ec9f9df8e1743e2 (patch) | |
tree | 3a87677e000259ecc0ced6bc93afc03fea73493b /activesupport/lib | |
parent | afd7140b66e7cb32e1be58d9e44489e6bcbde0dc (diff) | |
parent | fd86a1b6b068df87164d5763bdcd4a323a1e76f4 (diff) | |
download | rails-e62de52aa398341a29b7ecef4ec9f9df8e1743e2.tar.gz rails-e62de52aa398341a29b7ecef4ec9f9df8e1743e2.tar.bz2 rails-e62de52aa398341a29b7ecef4ec9f9df8e1743e2.zip |
Merge branch 'master' into serializers
Diffstat (limited to 'activesupport/lib')
21 files changed, 512 insertions, 207 deletions
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb index cc9ea5cffa..ff78e718f2 100644 --- a/activesupport/lib/active_support.rb +++ b/activesupport/lib/active_support.rb @@ -71,6 +71,7 @@ module ActiveSupport autoload :OrderedOptions autoload :Rescuable autoload :StringInquirer + autoload :TaggedLogging autoload :XmlMini end diff --git a/activesupport/lib/active_support/buffered_logger.rb b/activesupport/lib/active_support/buffered_logger.rb index 26412cd7f4..136e245859 100644 --- a/activesupport/lib/active_support/buffered_logger.rb +++ b/activesupport/lib/active_support/buffered_logger.rb @@ -25,22 +25,28 @@ module ActiveSupport # Silences the logger for the duration of the block. def silence(temporary_level = ERROR) if silencer + old_logger_level = @tmp_levels[Thread.current] begin - old_logger_level, self.level = level, temporary_level + @tmp_levels[Thread.current] = temporary_level yield self ensure - self.level = old_logger_level + if old_logger_level + @tmp_levels[Thread.current] = old_logger_level + else + @tmp_levels.delete(Thread.current) + end end else yield self end end - attr_accessor :level + attr_writer :level attr_reader :auto_flushing def initialize(log, level = DEBUG) @level = level + @tmp_levels = {} @buffer = Hash.new { |h,k| h[k] = [] } @auto_flushing = 1 @guard = Mutex.new @@ -62,8 +68,12 @@ module ActiveSupport end end + def level + @tmp_levels[Thread.current] || @level + end + def add(severity, message = nil, progname = nil, &block) - return if @level > severity + return if level > severity message = (message || (block && block.call) || progname).to_s # If a newline is necessary then create a new message ending with a newline. # Ensures that the original message is not mutated. @@ -84,7 +94,7 @@ module ActiveSupport end # end def #{severity.downcase}? # def debug? - #{severity} >= @level # DEBUG >= @level + #{severity} >= level # DEBUG >= @level end # end EOT end @@ -105,13 +115,15 @@ module ActiveSupport def flush @guard.synchronize do - buffer.each do |content| - @log.write(content) - end + write_buffer(buffer) # Important to do this even if buffer was empty or else @buffer will # accumulate empty arrays for each request where nothing was logged. clear_buffer + + # Clear buffers associated with dead threads or else spawned threads + # that don't call flush will result in a memory leak. + flush_dead_buffers end end @@ -133,5 +145,21 @@ module ActiveSupport def clear_buffer @buffer.delete(Thread.current) end + + # Find buffers created by threads that are no longer alive and flush them to the log + # in order to prevent memory leaks from spawned threads. + def flush_dead_buffers #:nodoc: + @buffer.keys.reject{|thread| thread.alive?}.each do |thread| + buffer = @buffer[thread] + write_buffer(buffer) + @buffer.delete(thread) + end + end + + def write_buffer(buffer) + buffer.each do |content| + @log.write(content) + end + end end end diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index 95d936b32f..07f5fcdeb3 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -16,8 +16,6 @@ module ActiveSupport autoload :FileStore, 'active_support/cache/file_store' autoload :MemoryStore, 'active_support/cache/memory_store' autoload :MemCacheStore, 'active_support/cache/mem_cache_store' - autoload :SynchronizedMemoryStore, 'active_support/cache/synchronized_memory_store' - autoload :CompressedMemCacheStore, 'active_support/cache/compressed_mem_cache_store' # These options mean something to all cache implementations. Individual cache # implementations may support additional options. @@ -27,75 +25,75 @@ module ActiveSupport autoload :LocalCache, 'active_support/cache/strategy/local_cache' end - # Creates a new CacheStore object according to the given options. - # - # If no arguments are passed to this method, then a new - # ActiveSupport::Cache::MemoryStore object will be returned. - # - # If you pass a Symbol as the first argument, then a corresponding cache - # store class under the ActiveSupport::Cache namespace will be created. - # For example: - # - # ActiveSupport::Cache.lookup_store(:memory_store) - # # => returns a new ActiveSupport::Cache::MemoryStore object - # - # ActiveSupport::Cache.lookup_store(:mem_cache_store) - # # => returns a new ActiveSupport::Cache::MemCacheStore object - # - # Any additional arguments will be passed to the corresponding cache store - # class's constructor: - # - # ActiveSupport::Cache.lookup_store(:file_store, "/tmp/cache") - # # => same as: ActiveSupport::Cache::FileStore.new("/tmp/cache") - # - # If the first argument is not a Symbol, then it will simply be returned: - # - # ActiveSupport::Cache.lookup_store(MyOwnCacheStore.new) - # # => returns MyOwnCacheStore.new - def self.lookup_store(*store_option) - store, *parameters = *Array.wrap(store_option).flatten - - case store - when Symbol - store_class_name = store.to_s.camelize - store_class = - begin - require "active_support/cache/#{store}" - rescue LoadError => e - raise "Could not find cache store adapter for #{store} (#{e})" - else - ActiveSupport::Cache.const_get(store_class_name) - end - store_class.new(*parameters) - when nil - ActiveSupport::Cache::MemoryStore.new - else - store + class << self + # Creates a new CacheStore object according to the given options. + # + # If no arguments are passed to this method, then a new + # ActiveSupport::Cache::MemoryStore object will be returned. + # + # If you pass a Symbol as the first argument, then a corresponding cache + # store class under the ActiveSupport::Cache namespace will be created. + # For example: + # + # ActiveSupport::Cache.lookup_store(:memory_store) + # # => returns a new ActiveSupport::Cache::MemoryStore object + # + # ActiveSupport::Cache.lookup_store(:mem_cache_store) + # # => returns a new ActiveSupport::Cache::MemCacheStore object + # + # Any additional arguments will be passed to the corresponding cache store + # class's constructor: + # + # ActiveSupport::Cache.lookup_store(:file_store, "/tmp/cache") + # # => same as: ActiveSupport::Cache::FileStore.new("/tmp/cache") + # + # If the first argument is not a Symbol, then it will simply be returned: + # + # ActiveSupport::Cache.lookup_store(MyOwnCacheStore.new) + # # => returns MyOwnCacheStore.new + def lookup_store(*store_option) + store, *parameters = *Array.wrap(store_option).flatten + + case store + when Symbol + store_class_name = store.to_s.camelize + store_class = + begin + require "active_support/cache/#{store}" + rescue LoadError => e + raise "Could not find cache store adapter for #{store} (#{e})" + else + ActiveSupport::Cache.const_get(store_class_name) + end + store_class.new(*parameters) + when nil + ActiveSupport::Cache::MemoryStore.new + else + store + end end - end - def self.expand_cache_key(key, namespace = nil) - expanded_cache_key = namespace ? "#{namespace}/" : "" + def expand_cache_key(key, namespace = nil) + expanded_cache_key = namespace ? "#{namespace}/" : "" + + prefix = ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"] + if prefix + expanded_cache_key << "#{prefix}/" + end - prefix = ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"] - if prefix - expanded_cache_key << "#{prefix}/" + expanded_cache_key << retrieve_cache_key(key) + expanded_cache_key end - expanded_cache_key << - if key.respond_to?(:cache_key) - key.cache_key - elsif key.is_a?(Array) - if key.size > 1 - key.collect { |element| expand_cache_key(element) }.to_param - else - key.first.to_param - end - elsif key - key.to_param - end.to_s + private - expanded_cache_key + def retrieve_cache_key(key) + case + when key.respond_to?(:cache_key) then key.cache_key + when key.is_a?(Array) then key.map { |element| retrieve_cache_key(element) }.to_param + else key.to_param + end.to_s + end end # An abstract cache store class. There are multiple cache store @@ -141,7 +139,7 @@ module ActiveSupport # large enough to warrant compression. To turn on compression either pass # <tt>:compress => true</tt> in the initializer or as an option to +fetch+ # or +write+. To specify the threshold at which to compress values, set the - # <tt>:compress_threshold</tt> option. The default threshold is 32K. + # <tt>:compress_threshold</tt> option. The default threshold is 16K. class Store cattr_accessor :logger, :instance_writer => true @@ -151,7 +149,7 @@ module ActiveSupport # Create a new cache. The options will be passed to any write method calls except # for :namespace which can be used to set the global namespace for the cache. - def initialize (options = nil) + def initialize(options = nil) @options = options ? options.dup : {} end @@ -232,7 +230,7 @@ module ActiveSupport # <tt>:race_condition_ttl</tt> does not play any role. # # # Set all values to expire after one minute. - # cache = ActiveSupport::Cache::MemoryCache.new(:expires_in => 1.minute) + # cache = ActiveSupport::Cache::MemoryStore.new(:expires_in => 1.minute) # # cache.write("foo", "original value") # val_1 = nil @@ -540,11 +538,11 @@ module ActiveSupport # Create an entry with internal attributes set. This method is intended to be # used by implementations that store cache entries in a native format instead # of as serialized Ruby objects. - def create (raw_value, created_at, options = {}) + def create(raw_value, created_at, options = {}) entry = new(nil) entry.instance_variable_set(:@value, raw_value) entry.instance_variable_set(:@created_at, created_at.to_f) - entry.instance_variable_set(:@compressed, !!options[:compressed]) + entry.instance_variable_set(:@compressed, options[:compressed]) entry.instance_variable_set(:@expires_in, options[:expires_in]) entry end @@ -561,7 +559,7 @@ module ActiveSupport @value = nil else @value = Marshal.dump(value) - if should_compress?(value, options) + if should_compress?(@value, options) @value = Zlib::Deflate.deflate(@value) @compressed = true end @@ -575,6 +573,9 @@ module ActiveSupport # Get the value stored in the cache. def value + # If the original value was exactly false @value is still true because + # it is marshalled and eventually compressed. Both operations yield + # strings. if @value Marshal.load(compressed? ? Zlib::Inflate.inflate(@value) : @value) end @@ -615,13 +616,10 @@ module ActiveSupport end private - def should_compress?(value, options) - if options[:compress] && value - unless value.is_a?(Numeric) - compress_threshold = options[:compress_threshold] || DEFAULT_COMPRESS_LIMIT - serialized_value = value.is_a?(String) ? value : Marshal.dump(value) - return true if serialized_value.size >= compress_threshold - end + def should_compress?(serialized_value, options) + if options[:compress] + compress_threshold = options[:compress_threshold] || DEFAULT_COMPRESS_LIMIT + return true if serialized_value.size >= compress_threshold end false end diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb index b431041b76..9460532af0 100644 --- a/activesupport/lib/active_support/cache/file_store.rb +++ b/activesupport/lib/active_support/cache/file_store.rb @@ -8,12 +8,13 @@ module ActiveSupport # A cache store implementation which stores everything on the filesystem. # # FileStore implements the Strategy::LocalCache strategy which implements - # an in memory cache inside of a block. + # an in-memory cache inside of a block. class FileStore < Store attr_reader :cache_path DIR_FORMATTER = "%03X" FILENAME_MAX_SIZE = 230 # max filename size on file system is 255, minus room for timestamp and random characters appended by Tempfile (used by atomic write) + EXCLUDED_DIRS = ['.', '..'].freeze def initialize(cache_path, options = nil) super(options) @@ -22,7 +23,7 @@ module ActiveSupport end def clear(options = nil) - root_dirs = Dir.entries(cache_path).reject{|f| f.in?(['.', '..'])} + root_dirs = Dir.entries(cache_path).reject{|f| f.in?(EXCLUDED_DIRS)} FileUtils.rm_r(root_dirs.collect{|f| File.join(cache_path, f)}) end @@ -149,7 +150,7 @@ module ActiveSupport # Delete empty directories in the cache. def delete_empty_directories(dir) return if dir == cache_path - if Dir.entries(dir).reject{|f| f.in?(['.', '..'])}.empty? + if Dir.entries(dir).reject{|f| f.in?(EXCLUDED_DIRS)}.empty? File.delete(dir) rescue nil delete_empty_directories(File.dirname(dir)) end @@ -163,7 +164,7 @@ module ActiveSupport def search_dir(dir, &callback) return if !File.exist?(dir) Dir.foreach(dir) do |d| - next if d == "." || d == ".." + next if d.in?(EXCLUDED_DIRS) name = File.join(dir, d) if File.directory?(name) search_dir(name, &callback) diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb index e07294178b..530839b24d 100644 --- a/activesupport/lib/active_support/cache/mem_cache_store.rb +++ b/activesupport/lib/active_support/cache/mem_cache_store.rb @@ -11,7 +11,7 @@ require 'active_support/core_ext/string/encoding' module ActiveSupport module Cache # A cache store implementation which stores data in Memcached: - # http://www.danga.com/memcached/ + # http://memcached.org/ # # This is currently the most popular cache store for production websites. # @@ -21,7 +21,7 @@ module ActiveSupport # server goes down, then MemCacheStore will ignore it until it comes back up. # # MemCacheStore implements the Strategy::LocalCache strategy which implements - # an in memory cache inside of a block. + # an in-memory cache inside of a block. class MemCacheStore < Store module Response # :nodoc: STORED = "STORED\r\n" diff --git a/activesupport/lib/active_support/cache/strategy/local_cache.rb b/activesupport/lib/active_support/cache/strategy/local_cache.rb index 0649a058aa..db5f228a70 100644 --- a/activesupport/lib/active_support/cache/strategy/local_cache.rb +++ b/activesupport/lib/active_support/cache/strategy/local_cache.rb @@ -4,9 +4,9 @@ require 'active_support/core_ext/string/inflections' module ActiveSupport module Cache module Strategy - # Caches that implement LocalCache will be backed by an in memory cache for the + # Caches that implement LocalCache will be backed by an in-memory cache for the # duration of a block. Repeated calls to the cache for the same key will hit the - # in memory cache for faster access. + # in-memory cache for faster access. module LocalCache # Simple memory backed cache. This cache is not thread safe and is intended only # for serving as a temporary memory cache for a single thread. diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index 656cba625c..ea37355fc1 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -153,7 +153,7 @@ module ActiveSupport @klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 def _one_time_conditions_valid_#{@callback_id}? - true #{key_options[0]} + true if #{key_options} end RUBY_EVAL end @@ -171,8 +171,8 @@ module ActiveSupport # if condition # before_save :filter_name, :if => :condition # filter_name # end - filter = <<-RUBY_EVAL - unless halted + <<-RUBY_EVAL + if !halted && #{@compiled_options} # 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 @@ -181,8 +181,6 @@ module ActiveSupport 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. @@ -202,7 +200,7 @@ module ActiveSupport name = "_conditional_callback_#{@kind}_#{next_id}" @klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 def #{name}(halted) - #{@compiled_options[0] || "if true"} && !halted + if #{@compiled_options} && !halted #{@filter} do yield self end @@ -222,10 +220,12 @@ module ActiveSupport case @kind when :after - # if condition # after_save :filter_name, :if => :condition - # filter_name - # end - [@compiled_options[0], @filter, @compiled_options[1]].compact.join("\n") + # after_save :filter_name, :if => :condition + <<-RUBY_EVAL + if #{@compiled_options} + #{@filter} + end + RUBY_EVAL when :around <<-RUBY_EVAL value @@ -240,9 +240,7 @@ module ActiveSupport # symbols, string, procs, and objects), so compile a conditional # expression based on the options def _compile_options(options) - return [] if options[:if].empty? && options[:unless].empty? - - conditions = [] + conditions = ["true"] unless options[:if].empty? conditions << Array.wrap(_compile_filter(options[:if])) @@ -252,7 +250,7 @@ module ActiveSupport conditions << Array.wrap(_compile_filter(options[:unless])).map {|f| "!#{f}"} end - ["if #{conditions.flatten.join(" && ")}", "end"] + conditions.flatten.join(" && ") end # Filters support: @@ -371,42 +369,37 @@ module ActiveSupport # Generate the internal runner method called by +run_callbacks+. def __define_runner(symbol) #:nodoc: body = send("_#{symbol}_callbacks").compile + runner_method = "_run_#{symbol}_callbacks" silence_warnings do - undef_method "_run_#{symbol}_callbacks" if method_defined?("_run_#{symbol}_callbacks") + undef_method runner_method if method_defined?(runner_method) class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 - def _run_#{symbol}_callbacks(key = nil, &blk) + def #{runner_method}(key = nil, &blk) if key - name = "_run__\#{self.class.name.hash.abs}__#{symbol}__\#{key.hash.abs}__callbacks" - - unless respond_to?(name) - self.class.__create_keyed_callback(name, :#{symbol}, self, &blk) - end - - send(name, &blk) + self.class.__run_keyed_callback(key, :#{symbol}, self, &blk) else #{body} end end - private :_run_#{symbol}_callbacks + private :#{runner_method} RUBY_EVAL end end - # This is called the first time a callback is called with a particular - # key. It creates a new callback method for the key, calculating - # which callbacks can be omitted because of per_key conditions. + # This method calls the callback method for the given key. + # If this called first time it creates a new callback method for the key, + # calculating which callbacks can be omitted because of per_key conditions. # - def __create_keyed_callback(name, kind, object, &blk) #:nodoc: - @_keyed_callbacks ||= {} - @_keyed_callbacks[name] ||= begin + def __run_keyed_callback(key, kind, object, &blk) #:nodoc: + name = "_run__#{self.name.hash.abs}__#{kind}__#{key.hash.abs}__callbacks" + unless object.respond_to?(name) str = send("_#{kind}_callbacks").compile(name, object) class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 def #{name}() #{str} end protected :#{name} RUBY_EVAL - true end + object.send(name, &blk) end # This is used internally to append, prepend and skip callbacks to the diff --git a/activesupport/lib/active_support/concern.rb b/activesupport/lib/active_support/concern.rb index 81fb859334..af3da937c7 100644 --- a/activesupport/lib/active_support/concern.rb +++ b/activesupport/lib/active_support/concern.rb @@ -4,17 +4,12 @@ module ActiveSupport # 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: @@ -31,10 +26,6 @@ module ActiveSupport # module ClassMethods # ... # end - # - # module InstanceMethods - # ... - # end # end # # Moreover, it gracefully handles module dependencies. Given a +Foo+ module and a +Bar+ @@ -118,7 +109,11 @@ module ActiveSupport @_dependencies.each { |dep| base.send(:include, dep) } super base.extend const_get("ClassMethods") if const_defined?("ClassMethods") - base.send :include, const_get("InstanceMethods") if const_defined?("InstanceMethods") + if const_defined?("InstanceMethods") + base.send :include, const_get("InstanceMethods") + ActiveSupport::Deprecation.warn "The InstanceMethods module inside ActiveSupport::Concern will be " \ + "no longer included automatically. Please define instance methods directly in #{base} instead.", caller + end base.class_eval(&@_included_block) if instance_variable_defined?("@_included_block") end end diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb index 3b22e8b4f9..f3d06ecb2f 100644 --- a/activesupport/lib/active_support/core_ext/array/conversions.rb +++ b/activesupport/lib/active_support/core_ext/array/conversions.rb @@ -39,10 +39,10 @@ class Array # # Blog.all.to_formatted_s # => "First PostSecond PostThird Post" # - # Adding in the <tt>:db</tt> argument as the format yields a prettier - # output: + # Adding in the <tt>:db</tt> argument as the format yields a comma separated + # id list: # - # Blog.all.to_formatted_s(:db) # => "First Post,Second Post,Third Post" + # Blog.all.to_formatted_s(:db) # => "1,2,3" def to_formatted_s(format = :default) case format when :db diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb index 26a99658cc..c6d5f29690 100644 --- a/activesupport/lib/active_support/core_ext/date/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date/calculations.rb @@ -192,13 +192,13 @@ 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). + # 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). + # Returns a new Date/DateTime representing the start of the given day in next week (default is :monday). def next_week(day = :monday) result = (self + 7).beginning_of_week + DAYS_INTO_WEEK[day] self.acts_like?(:time) ? result.change(:hour => 0) : result diff --git a/activesupport/lib/active_support/core_ext/module.rb b/activesupport/lib/active_support/core_ext/module.rb index 9fed346b7c..f399fce410 100644 --- a/activesupport/lib/active_support/core_ext/module.rb +++ b/activesupport/lib/active_support/core_ext/module.rb @@ -8,4 +8,5 @@ require 'active_support/core_ext/module/delegation' require 'active_support/core_ext/module/synchronization' require 'active_support/core_ext/module/deprecation' require 'active_support/core_ext/module/remove_method' -require 'active_support/core_ext/module/method_names'
\ No newline at end of file +require 'active_support/core_ext/module/method_names' +require 'active_support/core_ext/module/qualified_const'
\ No newline at end of file diff --git a/activesupport/lib/active_support/core_ext/module/qualified_const.rb b/activesupport/lib/active_support/core_ext/module/qualified_const.rb new file mode 100644 index 0000000000..d1a0ee2f83 --- /dev/null +++ b/activesupport/lib/active_support/core_ext/module/qualified_const.rb @@ -0,0 +1,64 @@ +require 'active_support/core_ext/string/inflections' + +#-- +# Allows code reuse in the methods below without polluting Module. +#++ +module QualifiedConstUtils + def self.raise_if_absolute(path) + raise NameError, "wrong constant name #$&" if path =~ /\A::[^:]+/ + end + + def self.names(path) + path.split('::') + end +end + +## +# Extends the API for constants to be able to deal with qualified names. Arguments +# are assumed to be relative to the receiver. +# +#-- +# Qualified names are required to be relative because we are extending existing +# methods that expect constant names, ie, relative paths of length 1. For example, +# Object.const_get("::String") raises NameError and so does qualified_const_get. +#++ +class Module + if method(:const_defined?).arity == 1 + def qualified_const_defined?(path) + QualifiedConstUtils.raise_if_absolute(path) + + QualifiedConstUtils.names(path).inject(self) do |mod, name| + return unless mod.const_defined?(name) + mod.const_get(name) + end + return true + end + else + def qualified_const_defined?(path, search_parents=true) + QualifiedConstUtils.raise_if_absolute(path) + + QualifiedConstUtils.names(path).inject(self) do |mod, name| + return unless mod.const_defined?(name, search_parents) + mod.const_get(name) + end + return true + end + end + + def qualified_const_get(path) + QualifiedConstUtils.raise_if_absolute(path) + + QualifiedConstUtils.names(path).inject(self) do |mod, name| + mod.const_get(name) + end + end + + def qualified_const_set(path, value) + QualifiedConstUtils.raise_if_absolute(path) + + const_name = path.demodulize + mod_name = path.deconstantize + mod = mod_name.empty? ? self : qualified_const_get(mod_name) + mod.const_set(const_name, value) + end +end diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb index c7ceeb9de4..1e57b586d9 100644 --- a/activesupport/lib/active_support/core_ext/string/inflections.rb +++ b/activesupport/lib/active_support/core_ext/string/inflections.rb @@ -9,14 +9,25 @@ require 'active_support/inflector/transliterate' class String # Returns the plural form of the word in the string. # + # If the optional parameter +count+ is specified, + # the singular form will be returned if <tt>count == 1</tt>. + # For any other value of +count+ the plural will be returned. + # + # ==== Examples # "post".pluralize # => "posts" # "octopus".pluralize # => "octopi" # "sheep".pluralize # => "sheep" # "words".pluralize # => "words" # "the blue mailman".pluralize # => "the blue mailmen" # "CamelOctopus".pluralize # => "CamelOctopi" - def pluralize - ActiveSupport::Inflector.pluralize(self) + # "apple".pluralize(1) # => "apple" + # "apple".pluralize(2) # => "apples" + def pluralize(count = nil) + if count == 1 + self + else + ActiveSupport::Inflector.pluralize(self) + end end # The reverse of +pluralize+, returns the singular form of a word in a string. @@ -42,7 +53,7 @@ class String def constantize ActiveSupport::Inflector.constantize(self) end - + # +safe_constantize+ tries to find a declared constant with the name specified # in the string. It returns nil when the name is not in CamelCase # or is not initialized. See ActiveSupport::Inflector.safe_constantize @@ -106,10 +117,25 @@ class String # # "ActiveRecord::CoreExtensions::String::Inflections".demodulize # => "Inflections" # "Inflections".demodulize # => "Inflections" + # + # See also +deconstantize+. def demodulize ActiveSupport::Inflector.demodulize(self) end + # Removes the rightmost segment from the constant expression in the string. + # + # "Net::HTTP".deconstantize # => "Net" + # "::Net::HTTP".deconstantize # => "::Net" + # "String".deconstantize # => "" + # "::String".deconstantize # => "" + # "".deconstantize # => "" + # + # See also +demodulize+. + def deconstantize + ActiveSupport::Inflector.deconstantize(self) + end + # Replaces special characters in a string so that it may be used as part of a 'pretty' URL. # # ==== Examples diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb index 372dd69212..1e15529569 100644 --- a/activesupport/lib/active_support/core_ext/time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/time/calculations.rb @@ -176,12 +176,12 @@ 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). + # 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). + # Returns a new Time representing the start of the given day in next week (default is :monday). def next_week(day = :monday) since(1.week).beginning_of_week.since(DAYS_INTO_WEEK[day].day).change(:hour => 0) end @@ -312,4 +312,14 @@ class Time end alias_method :compare_without_coercion, :<=> alias_method :<=>, :compare_with_coercion + + # Layers additional behavior on Time#eql? so that ActiveSupport::TimeWithZone instances + # can be eql? to an equivalent Time + def eql_with_coercion(other) + # if other is an ActiveSupport::TimeWithZone, coerce a Time instance from it so we can do eql? comparison + other = other.comparable_time if other.respond_to?(:comparable_time) + eql_without_coercion(other) + end + alias_method :eql_without_coercion, :eql? + alias_method :eql?, :eql_with_coercion end diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index c072a8e646..db90de4682 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/qualified_const' require 'active_support/core_ext/object/blank' require 'active_support/core_ext/load_error' require 'active_support/core_ext/name_error' @@ -357,12 +358,13 @@ module ActiveSupport #:nodoc: end # Is the provided constant path defined? - def qualified_const_defined?(path) - names = path.sub(/^::/, '').to_s.split('::') - - names.inject(Object) do |mod, name| - return false unless local_const_defined?(mod, name) - mod.const_get name + if Module.method(:const_defined?).arity == 1 + def qualified_const_defined?(path) + Object.qualified_const_defined?(path.sub(/^::/, '')) + end + else + def qualified_const_defined?(path) + Object.qualified_const_defined?(path.sub(/^::/, ''), false) end end @@ -426,7 +428,7 @@ module ActiveSupport #:nodoc: end # Attempt to autoload the provided module name by searching for a directory - # matching the expect path suffix. If found, the module is created and assigned + # matching the expected path suffix. If found, the module is created and assigned # to +into+'s constants with the name +const_name+. Provided that the directory # was loaded from a reloadable base path, it is added to the set of constants # that are to be unloaded. diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb index 934529d496..144cdd3c8f 100644 --- a/activesupport/lib/active_support/inflector/methods.rb +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -21,14 +21,7 @@ module ActiveSupport # "words".pluralize # => "words" # "CamelOctopus".pluralize # => "CamelOctopi" def pluralize(word) - result = word.to_s.dup - - if word.empty? || inflections.uncountables.include?(result.downcase) - result - else - inflections.plurals.each { |(rule, replacement)| break if result.gsub!(rule, replacement) } - result - end + apply_inflections(word, inflections.plurals) end # The reverse of +pluralize+, returns the singular form of a word in a string. @@ -40,14 +33,7 @@ module ActiveSupport # "word".singularize # => "word" # "CamelOctopi".singularize # => "CamelOctopus" def singularize(word) - result = word.to_s.dup - - if inflections.uncountables.any? { |inflection| result =~ /\b(#{inflection})\Z/i } - result - else - inflections.singulars.each { |(rule, replacement)| break if result.gsub!(rule, replacement) } - result - end + apply_inflections(word, inflections.singulars) end # By default, +camelize+ converts strings to UpperCamelCase. If the argument to +camelize+ @@ -160,13 +146,32 @@ module ActiveSupport underscored_word.gsub(/_/, '-') end - # Removes the module part from the expression in the string. + # Removes the module part from the expression in the string: # - # Examples: # "ActiveRecord::CoreExtensions::String::Inflections".demodulize # => "Inflections" # "Inflections".demodulize # => "Inflections" - def demodulize(class_name_in_module) - class_name_in_module.to_s.gsub(/^.*::/, '') + # + # See also +deconstantize+. + def demodulize(path) + path = path.to_s + if i = path.rindex('::') + path[(i+2)..-1] + else + path + end + end + + # Removes the rightmost segment from the constant expression in the string: + # + # "Net::HTTP".deconstantize # => "Net" + # "::Net::HTTP".deconstantize # => "::Net" + # "String".deconstantize # => "" + # "::String".deconstantize # => "" + # "".deconstantize # => "" + # + # See also +demodulize+. + def deconstantize(path) + path.to_s[0...(path.rindex('::') || 0)] # implementation based on the one in facets' Module#spacename end # Creates a foreign key name from a class name. @@ -292,5 +297,21 @@ module ActiveSupport part.empty? ? acc : "#{part}(::#{acc})?" end end + + # Applies inflection rules for +singularize+ and +pluralize+. + # + # Examples: + # apply_inflections("post", inflections.plurals) # => "posts" + # apply_inflections("posts", inflections.singulars) # => "post" + def apply_inflections(word, rules) + result = word.to_s.dup + + if word.empty? || inflections.uncountables.any? { |inflection| result =~ /\b#{inflection}\Z/i } + result + else + rules.each { |(rule, replacement)| break if result.gsub!(rule, replacement) } + result + end + end end end diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb index e14386a85d..9ef2b29580 100644 --- a/activesupport/lib/active_support/message_encryptor.rb +++ b/activesupport/lib/active_support/message_encryptor.rb @@ -10,6 +10,16 @@ module ActiveSupport # This can be used in situations similar to the <tt>MessageVerifier</tt>, but where you don't # want users to be able to determine the value of the payload. class MessageEncryptor + module NullSerializer #:nodoc: + def self.load(value) + value + end + + def self.dump(value) + value + end + end + class InvalidMessage < StandardError; end OpenSSLCipherError = OpenSSL::Cipher.const_defined?(:CipherError) ? OpenSSL::Cipher::CipherError : OpenSSL::CipherError @@ -18,13 +28,40 @@ module ActiveSupport ActiveSupport::Deprecation.warn "The second parameter should be an options hash. Use :cipher => 'algorithm' to specify the cipher algorithm." options = { :cipher => options } end - + @secret = secret @cipher = options[:cipher] || 'aes-256-cbc' + @verifier = MessageVerifier.new(@secret, :serializer => NullSerializer) @serializer = options[:serializer] || Marshal end def encrypt(value) + ActiveSupport::Deprecation.warn "MessageEncryptor#encrypt is deprecated as it is not safe without a signature. " \ + "Please use MessageEncryptor#encrypt_and_sign instead." + _encrypt(value) + end + + def decrypt(value) + ActiveSupport::Deprecation.warn "MessageEncryptor#decrypt is deprecated as it is not safe without a signature. " \ + "Please use MessageEncryptor#decrypt_and_verify instead." + _decrypt(value) + end + + # Encrypt and sign a message. We need to sign the message in order to avoid padding attacks. + # Reference: http://www.limited-entropy.com/padding-oracle-attacks + def encrypt_and_sign(value) + verifier.generate(_encrypt(value)) + end + + # Decrypt and verify a message. We need to verify the message in order to avoid padding attacks. + # Reference: http://www.limited-entropy.com/padding-oracle-attacks + def decrypt_and_verify(value) + _decrypt(verifier.verify(value)) + end + + private + + def _encrypt(value) cipher = new_cipher # Rely on OpenSSL for the initialization vector iv = cipher.random_iv @@ -39,7 +76,7 @@ module ActiveSupport [encrypted_data, iv].map {|v| ActiveSupport::Base64.encode64s(v)}.join("--") end - def decrypt(encrypted_message) + def _decrypt(encrypted_message) cipher = new_cipher encrypted_data, iv = encrypted_message.split("--").map {|v| ActiveSupport::Base64.decode64(v)} @@ -55,23 +92,12 @@ module ActiveSupport raise InvalidMessage end - def encrypt_and_sign(value) - verifier.generate(encrypt(value)) + def new_cipher + OpenSSL::Cipher::Cipher.new(@cipher) end - def decrypt_and_verify(value) - decrypt(verifier.verify(value)) + def verifier + @verifier end - - - - private - def new_cipher - OpenSSL::Cipher::Cipher.new(@cipher) - end - - def verifier - MessageVerifier.new(@secret) - end end end diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb index b5a70d5933..f549d2fff3 100644 --- a/activesupport/lib/active_support/notifications.rb +++ b/activesupport/lib/active_support/notifications.rb @@ -1,36 +1,102 @@ module ActiveSupport - # Notifications provides an instrumentation API for Ruby. To instrument an - # action in Ruby you just need to do: + # = Notifications # - # ActiveSupport::Notifications.instrument(:render, :extra => :information) do + # +ActiveSupport::Notifications+ provides an instrumentation API for Ruby. + # + # == Instrumenters + # + # To instrument an event you just need to do: + # + # ActiveSupport::Notifications.instrument("render", :extra => :information) do # render :text => "Foo" # end # + # That executes the block first and notifies all subscribers once done. + # + # In the example above "render" is the name of the event, and the rest is called + # the _payload_. The payload is a mechanism that allows instrumenters to pass + # extra information to subscribers. Payloads consist of a hash whose contents + # are arbitrary and generally depend on the event. + # + # == Subscribers + # # You can consume those events and the information they provide by registering - # a log subscriber. For instance, let's store all instrumented events in an array: + # a subscriber. For instance, let's store all "render" events in an array: # - # @events = [] + # events = [] # - # ActiveSupport::Notifications.subscribe do |*args| - # @events << ActiveSupport::Notifications::Event.new(*args) + # ActiveSupport::Notifications.subscribe("render") do |*args| + # events << ActiveSupport::Notifications::Event.new(*args) # end # - # ActiveSupport::Notifications.instrument(:render, :extra => :information) do + # That code returns right away, you are just subscribing to "render" events. + # The block will be called asynchronously whenever someone instruments "render": + # + # ActiveSupport::Notifications.instrument("render", :extra => :information) do # render :text => "Foo" # end # - # event = @events.first - # event.name # => :render + # event = events.first + # event.name # => "render" # event.duration # => 10 (in milliseconds) # event.payload # => { :extra => :information } # - # When subscribing to Notifications, you can pass a pattern, to only consume - # events that match the pattern: + # The block in the +subscribe+ call gets the name of the event, start + # timestamp, end timestamp, a string with a unique identifier for that event + # (something like "535801666f04d0298cd6"), and a hash with the payload, in + # that order. # - # ActiveSupport::Notifications.subscribe(/render/) do |event| - # @render_events << event + # If an exception happens during that particular instrumentation the payload will + # have a key +:exception+ with an array of two elements as value: a string with + # the name of the exception class, and the exception message. + # + # As the previous example depicts, the class +ActiveSupport::Notifications::Event+ + # is able to take the arguments as they come and provide an object-oriented + # interface to that data. + # + # You can also subscribe to all events whose name matches a certain regexp: + # + # ActiveSupport::Notifications.subscribe(/render/) do |*args| + # ... # end # + # and even pass no argument to +subscribe+, in which case you are subscribing + # to all events. + # + # == Temporary Subscriptions + # + # Sometimes you do not want to subscribe to an event for the entire life of + # the application. There are two ways to unsubscribe. + # + # === Subscribe While a Block Runs + # + # You can subscribe to some event temporarily while some block runs. For + # example, in + # + # callback = lambda {|*args| ... } + # ActiveSupport::Notifications.subscribed(callback, "sql.active_record") do + # ... + # end + # + # the callback will be called for all "sql.active_record" events instrumented + # during the execution of the block. The callback is unsubscribed automatically + # after that. + # + # === Manual Unsubscription + # + # The +subscribe+ method returns a subscriber object: + # + # subscriber = ActiveSupport::Notifications.subscribe("render") do |*args| + # ... + # end + # + # To prevent that block from being called anymore, just unsubscribe passing + # that reference: + # + # ActiveSupport::Notifications.unsubscribe(subscriber) + # + # == Default Queue + # # Notifications ships with a queue implementation that consumes and publish events # to log subscribers in a thread. You can use any queue implementation you want. # @@ -62,6 +128,13 @@ module ActiveSupport end end + def subscribed(callback, *args, &block) + subscriber = subscribe(*args, &callback) + yield + ensure + unsubscribe(subscriber) + end + def unsubscribe(args) notifier.unsubscribe(args) @instrumenters.clear diff --git a/activesupport/lib/active_support/ordered_hash.rb b/activesupport/lib/active_support/ordered_hash.rb index 0264133581..b0d4f2bd86 100644 --- a/activesupport/lib/active_support/ordered_hash.rb +++ b/activesupport/lib/active_support/ordered_hash.rb @@ -20,7 +20,7 @@ module ActiveSupport # oh.keys # => [:a, :b], this order is guaranteed # # <tt>ActiveSupport::OrderedHash</tt> is namespaced to prevent conflicts with other implementations. - class OrderedHash < ::Hash #:nodoc: + class OrderedHash < ::Hash def to_yaml_type "!tag:yaml.org,2002:omap" end diff --git a/activesupport/lib/active_support/tagged_logging.rb b/activesupport/lib/active_support/tagged_logging.rb new file mode 100644 index 0000000000..a59fc26d5d --- /dev/null +++ b/activesupport/lib/active_support/tagged_logging.rb @@ -0,0 +1,63 @@ +require 'active_support/core_ext/object/blank' +require 'logger' + +module ActiveSupport + # Wraps any standard Logger class to provide tagging capabilities. Examples: + # + # Logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT)) + # Logger.tagged("BCX") { Logger.info "Stuff" } # Logs "[BCX] Stuff" + # Logger.tagged("BCX", "Jason") { Logger.info "Stuff" } # Logs "[BCX] [Jason] Stuff" + # Logger.tagged("BCX") { Logger.tagged("Jason") { Logger.info "Stuff" } } # Logs "[BCX] [Jason] Stuff" + # + # This is used by the default Rails.logger as configured by Railties to make it easy to stamp log lines + # with subdomains, request ids, and anything else to aid debugging of multi-user production applications. + class TaggedLogging + def initialize(logger) + @logger = logger + @tags = Hash.new { |h,k| h[k] = [] } + end + + def tagged(*new_tags) + tags = current_tags + new_tags = Array.wrap(new_tags).flatten.reject(&:blank?) + tags.concat new_tags + yield + ensure + new_tags.size.times { tags.pop } + end + + def add(severity, message = nil, progname = nil, &block) + @logger.add(severity, "#{tags_text}#{message}", progname, &block) + end + + %w( fatal error warn info debug unkown ).each do |severity| + eval <<-EOM, nil, __FILE__, __LINE__ + 1 + def #{severity}(progname = nil, &block) + add(Logger::#{severity.upcase}, progname, &block) + end + EOM + end + + def flush(*args) + @tags.delete(Thread.current) + @logger.flush(*args) if @logger.respond_to?(:flush) + end + + def method_missing(method, *args) + @logger.send(method, *args) + end + + protected + + def tags_text + tags = current_tags + if tags.any? + tags.collect { |tag| "[#{tag}]" }.join(" ") + " " + end + end + + def current_tags + @tags[Thread.current] + end + end +end diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index 63279d0e6d..d3adf671a0 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -203,7 +203,11 @@ module ActiveSupport end def eql?(other) - utc == other + utc.eql?(other) + end + + def hash + utc.hash end def +(other) @@ -277,7 +281,6 @@ module ActiveSupport def to_i utc.to_i end - alias_method :hash, :to_i alias_method :tv_sec, :to_i # A TimeWithZone acts like a Time, so just return +self+. |