diff options
Diffstat (limited to 'activesupport/lib')
9 files changed, 271 insertions, 73 deletions
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index 95d936b32f..6535cc1eb5 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. @@ -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 @@ -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 @@ -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 @@ -615,13 +613,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..85e7e21624 100644 --- a/activesupport/lib/active_support/cache/file_store.rb +++ b/activesupport/lib/active_support/cache/file_store.rb @@ -14,6 +14,7 @@ module ActiveSupport 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/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 fd91b3cacb..1e57b586d9 100644 --- a/activesupport/lib/active_support/core_ext/string/inflections.rb +++ b/activesupport/lib/active_support/core_ext/string/inflections.rb @@ -117,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/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index c072a8e646..b3ac271778 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 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 |