Compare commits

..

15 Commits

Author SHA1 Message Date
Charlie Somerville
775febba74 use load_paths.rb in railties abstract unit 2013-11-12 14:30:04 -08:00
Charlie Somerville
d33a754a92 pull deleted ActiveSupport::ModelName into ActiveRecord::ModelName 2013-11-12 14:30:04 -08:00
Charlie Somerville
c12cd651c2 remove ActiveSupport::SecureRandom 2013-11-12 14:30:04 -08:00
Charlie Somerville
f709b5d1c8 remove bytesize require 2013-11-12 14:30:03 -08:00
Charlie Somerville
c6f9ec2d8d bring back inheritable_attributes, too much of Rails 2 internals uses it 2013-11-12 14:30:03 -08:00
Charlie Somerville
d7440a463c make actionpack depend on erubis 2013-11-12 14:30:03 -08:00
Charlie Somerville
7655b80261 require active_support/all everywhere 2013-11-12 14:30:03 -08:00
Charlie Somerville
c5be730a2e use load_paths in actionpack, activeresource and activerecord 2013-11-12 14:30:03 -08:00
Charlie Somerville
052556e5cf add tzinfo and builder to the gemfile 2013-11-12 14:30:03 -08:00
Charlie Somerville
173bc3c9e5 we don't need the isolated environment when using bundler 2013-11-12 14:30:03 -08:00
Charlie Somerville
01280149f2 bring over deps from activesupport 3 2013-11-12 14:30:03 -08:00
Charlie Somerville
79c1106fb2 try to use bundler install of Gemfile.sh 2013-11-12 14:30:03 -08:00
Charlie Somerville
8f6982c04b import ActiveSupport 3 tests 2013-11-12 14:30:03 -08:00
Charlie Somerville
a471098ab8 add ActiveSupport 3 dependencies 2013-11-12 14:30:02 -08:00
Charlie Somerville
87ef1f0e73 upgrade to ActiveSupport 3 2013-11-12 14:30:02 -08:00
586 changed files with 20047 additions and 28416 deletions

101
Gemfile Normal file
View File

@@ -0,0 +1,101 @@
source 'https://rubygems.org'
gemspec
if ENV['AREL']
gem 'arel', :path => ENV['AREL']
else
gem 'arel'
end
gem 'bcrypt-ruby', '~> 3.0.0'
#gem 'jquery-rails'
if ENV['JOURNEY']
gem 'journey', :path => ENV['JOURNEY']
else
gem 'journey', :git => 'git://github.com/rails/journey.git', :branch => '1-0-stable'
end
# This needs to be with require false to avoid
# it being automatically loaded by sprockets
#gem 'uglifier', '>= 1.0.3', :require => false
gem 'rake', '>= 0.8.7'
gem 'mocha', '>= 0.13.0', :require => false
group :doc do
# The current sdoc cannot generate GitHub links due
# to a bug, but the PR that fixes it has been there
# for some weeks unapplied. As a temporary solution
# this is our own fork with the fix.
gem 'sdoc', :git => 'git://github.com/fxn/sdoc.git'
gem 'RedCloth', '~> 4.2'
gem 'w3c_validators'
end
# AS
gem 'memcache-client', '>= 1.8.5'
platforms :mri_18 do
gem 'system_timer'
gem 'json'
end
# Add your own local bundler stuff
instance_eval File.read '.Gemfile' if File.exists? '.Gemfile'
platforms :mri do
group :test do
gem 'ruby-prof', '~> 0.11.2' if RUBY_VERSION < '2.0'
end
end
platforms :ruby do
gem 'yajl-ruby'
gem 'nokogiri', '>= 1.4.5'
# AR
gem 'sqlite3', '~> 1.3.5'
group :db do
gem 'pg', '>= 0.11.0'
gem 'mysql', '>= 2.8.1'
gem 'mysql2', '>= 0.3.10'
end
end
platforms :jruby do
gem 'json'
gem 'activerecord-jdbcsqlite3-adapter', '>= 1.2.0'
# This is needed by now to let tests work on JRuby
# TODO: When the JRuby guys merge jruby-openssl in
# jruby this will be removed
gem 'jruby-openssl'
group :db do
gem 'activerecord-jdbcmysql-adapter', '>= 1.2.0'
gem 'activerecord-jdbcpostgresql-adapter', '>= 1.2.0'
end
end
# gems that are necessary for ActiveRecord tests with Oracle database
if ENV['ORACLE_ENHANCED_PATH'] || ENV['ORACLE_ENHANCED']
platforms :ruby do
gem 'ruby-oci8', '>= 2.0.4'
end
if ENV['ORACLE_ENHANCED_PATH']
gem 'activerecord-oracle_enhanced-adapter', :path => ENV['ORACLE_ENHANCED_PATH']
else
gem 'activerecord-oracle_enhanced-adapter', :git => 'git://github.com/rsim/oracle-enhanced.git'
end
end
# A gem necessary for ActiveRecord tests with IBM DB
gem 'ibm_db' if ENV['IBM_DB']
gem 'benchmark-ips'
gem "tzinfo"
gem "builder"

View File

@@ -1,9 +1,9 @@
gem install mocha -v=0.13.1
gem install rake -v=10.1.0
gem install rdoc -v=4.0.1
gem install sqlite3 -v=1.3.7
gem install rack -v=1.4.5
gem install erubis -v=2.7.0
gem install json -v=1.8.0
gem install i18n -v=0.6.9
gem install builder -v=3.2.2
gem install mocha -v=0.13.1
gem install rake -v=10.1.0
gem install rdoc -v=4.0.1
gem install sqlite3 -v=1.3.7
gem install rack -v=1.4.5
gem install erubis -v=2.7.0
gem install json -v=1.8.0
gem install multi_json -v=1.8.2
gem install i18n -v=0.6.5

View File

@@ -1 +1 @@
2.3.14.github45
2.3.14.github30

View File

@@ -14,4 +14,5 @@ Gem::Specification.new do |s|
s.add_dependency 'activesupport', "= #{version}"
s.add_dependency 'rack', '~> 1.4'
s.add_dependency 'erubis', '~> 2.7.0'
end

View File

@@ -22,12 +22,12 @@
#++
begin
require 'active_support'
require 'active_support/all'
rescue LoadError
activesupport_path = "#{File.dirname(__FILE__)}/../../activesupport/lib"
if File.directory?(activesupport_path)
$:.unshift activesupport_path
require 'active_support'
require 'active_support/all'
end
end
@@ -38,7 +38,7 @@ module ActionController
# TODO: Review explicit to see if they will automatically be handled by
# the initilizer if they are really needed.
def self.load_all!
[Base, Request, Response, Http::Headers, UrlRewriter, UrlWriter]
[Base, CGIHandler, CgiRequest, Request, Response, Http::Headers, UrlRewriter, UrlWriter]
end
autoload :Base, 'action_controller/base'
@@ -99,6 +99,10 @@ module ActionController
autoload :CookieStore, 'action_controller/session/cookie_store'
autoload :MemCacheStore, 'action_controller/session/mem_cache_store'
end
# DEPRECATE: Remove CGI support
autoload :CgiRequest, 'action_controller/cgi_process'
autoload :CGIHandler, 'action_controller/cgi_process'
end
autoload :Mime, 'action_controller/mime_type'

View File

@@ -1320,14 +1320,7 @@ module ActionController #:nodoc:
render
end
CVE_2014_0310 = Class.new(StandardError)
def perform_action
# CVE-2014-0130 protection
if action_name.include? "/"
raise CVE_2014_0310
end
if action_methods.include?(action_name)
send(action_name)
default_render unless performed?

View File

@@ -87,6 +87,7 @@ module ActionController #:nodoc:
log_message << " [#{complete_request_uri rescue "unknown"}]"
logger.info(log_message)
response.headers["X-Runtime"] = "%.0f" % ms
else
perform_action_without_benchmark
end

View File

@@ -39,9 +39,9 @@ module ActionController #:nodoc:
if cache = read_fragment(name, options)
buffer.safe_concat(cache.html_safe)
else
pos = buffer.bytesize
pos = buffer.length
block.call
write_fragment(name, buffer.byteslice(pos..-1), options)
write_fragment(name, buffer[pos..-1], options)
end
else
block.call

View File

@@ -1,6 +1,10 @@
require 'action_controller/cgi_ext/stdinput'
require 'action_controller/cgi_ext/query_extension'
require 'action_controller/cgi_ext/cookie'
class CGI #:nodoc:
include ActionController::CgiExt::Stdinput
class << self
alias :escapeHTML_fail_on_nil :escapeHTML

View File

@@ -1,6 +1,4 @@
require 'delegate'
require 'cgi'
require 'cgi/cookie'
CGI.module_eval { remove_const "Cookie" }
@@ -26,7 +24,7 @@ class CGI #:nodoc:
# * <tt>:secure</tt> - Whether this cookie is a secure cookie or not (defaults to
# +false+). Secure cookies are only transmitted to HTTPS servers.
# * <tt>:http_only</tt> - Whether this cookie can be accessed by client side scripts (e.g. document.cookie) or only over HTTP.
# More details in http://msdn2.microsoft.com/en-us/library/system.web.httpcookie.httponly.aspx. Defaults to +false+.
# More details in http://msdn2.microsoft.com/en-us/library/system.web.httpcookie.httponly.aspx. Defaults to +false+.
#
# These keywords correspond to attributes of the cookie object.
def initialize(name = '', *value)

View File

@@ -0,0 +1,22 @@
require 'cgi'
class CGI #:nodoc:
module QueryExtension
# Remove the old initialize_query method before redefining it.
remove_method :initialize_query
# Neuter CGI parameter parsing.
def initialize_query
# Fix some strange request environments.
env_table['REQUEST_METHOD'] ||= 'GET'
# POST assumes missing Content-Type is application/x-www-form-urlencoded.
if env_table['CONTENT_TYPE'].blank? && env_table['REQUEST_METHOD'] == 'POST'
env_table['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'
end
@cookies = CGI::Cookie::parse(env_table['HTTP_COOKIE'] || env_table['COOKIE'])
@params = {}
end
end
end

View File

@@ -0,0 +1,24 @@
require 'cgi'
module ActionController
module CgiExt
# Publicize the CGI's internal input stream so we can lazy-read
# request.body. Make it writable so we don't have to play $stdin games.
module Stdinput
def self.included(base)
base.class_eval do
remove_method :stdinput
attr_accessor :stdinput
end
base.alias_method_chain :initialize, :stdinput
end
def initialize_with_stdinput(type = nil, stdinput = $stdin)
@stdinput = stdinput
@stdinput.set_encoding(Encoding::BINARY) if @stdinput.respond_to?(:set_encoding)
initialize_without_stdinput(type || 'query')
end
end
end
end

View File

@@ -0,0 +1,77 @@
require 'action_controller/cgi_ext'
module ActionController #:nodoc:
class CGIHandler
module ProperStream
def each
while line = gets
yield line
end
end
def read(*args)
if args.empty?
super || ""
else
super
end
end
end
def self.dispatch_cgi(app, cgi, out = $stdout)
env = cgi.__send__(:env_table)
env.delete "HTTP_CONTENT_LENGTH"
cgi.stdinput.extend ProperStream
env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
env.update({
"rack.version" => [0,1],
"rack.input" => cgi.stdinput,
"rack.errors" => $stderr,
"rack.multithread" => false,
"rack.multiprocess" => true,
"rack.run_once" => false,
"rack.url_scheme" => ["yes", "on", "1"].include?(env["HTTPS"]) ? "https" : "http"
})
env["QUERY_STRING"] ||= ""
env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
env["REQUEST_PATH"] ||= "/"
env.delete "PATH_INFO" if env["PATH_INFO"] == ""
status, headers, body = app.call(env)
begin
out.binmode if out.respond_to?(:binmode)
out.sync = false if out.respond_to?(:sync=)
headers['Status'] = status.to_s
if headers.include?('Set-Cookie')
headers['cookie'] = headers.delete('Set-Cookie').split("\n")
end
out.write(cgi.header(headers))
body.each { |part|
out.write part
out.flush if out.respond_to?(:flush)
}
ensure
body.close if body.respond_to?(:close)
end
end
end
class CgiRequest #:nodoc:
DEFAULT_SESSION_OPTIONS = {
:database_manager => nil,
:prefix => "ruby_sess.",
:session_path => "/",
:session_key => "_session_id",
:cookie_only => true,
:session_http_only => true
}
end
end

View File

@@ -22,6 +22,11 @@ module ActionController
end
end
# DEPRECATE: Remove CGI support
def dispatch(cgi = nil, session_options = CgiRequest::DEFAULT_SESSION_OPTIONS, output = $stdout)
new(output).dispatch_cgi(cgi, session_options)
end
# Add a preparation callback. Preparation callbacks are run before every
# request in development mode, and before the first request in production
# mode.
@@ -37,7 +42,13 @@ module ActionController
end
def run_prepare_callbacks
new.send :run_callbacks, :prepare_dispatch
if defined?(Rails) && Rails.logger
logger = Rails.logger
else
logger = Logger.new($stderr)
end
new(logger).send :run_callbacks, :prepare_dispatch
end
def reload_application
@@ -64,8 +75,10 @@ module ActionController
include ActiveSupport::Callbacks
define_callbacks :prepare_dispatch, :before_dispatch, :after_dispatch
def initialize
build_middleware_stack
# DEPRECATE: Remove arguments, since they are only used by CGI
def initialize(output = $stdout, request = nil, response = nil)
@output = output
build_middleware_stack if @@cache_classes
end
def dispatch
@@ -83,11 +96,21 @@ module ActionController
end
end
# DEPRECATE: Remove CGI support
def dispatch_cgi(cgi, session_options)
CGIHandler.dispatch_cgi(self, cgi, @output)
end
def call(env)
if @@cache_classes
@app.call(env)
else
Reloader.run do
# When class reloading is turned on, we will want to rebuild the
# middleware stack every time we process a request. If we don't
# rebuild the middleware stack, then the stack may contain references
# to old classes metal classes, which will b0rk class reloading.
build_middleware_stack
@app.call(env)
end
end

View File

@@ -45,74 +45,37 @@ module ActionController #:nodoc:
end
def []=(k, v)
k = k.to_s
@flash[k] = v
@flash.discard(k)
v
end
def [](k)
@flash[k.to_s]
@flash[k]
end
end
class FlashHash < Hash
def self.from_session_value(value)
flash = case value
when FlashHash # Rails 2.3
value
when Hash # Rails 4.0
flashes = value['flashes'] || {}
flashes.stringify_keys!
discard = value['discard'] || []
discard = discard.map do |item|
item.kind_of?(Symbol) ? item.to_s : item
end
used = Hash[flashes.keys.map{|k| [k, discard.include?(k)] }]
new_from_values(flashes, used)
else
new
end
flash
end
def initialize #:nodoc:
super
@used = {}
end
def to_session_value
return nil if empty?
rails_3_discard_list = @used.map{|k,v| k if v}.compact
{'discard' => rails_3_discard_list, 'flashes' => Hash[to_a]}
end
def []=(k, v) #:nodoc:
k = k.to_s
keep(k)
super(k, v)
end
def [](k)
super(k.to_s)
end
def delete(k)
super(k.to_s)
super
end
def update(h) #:nodoc:
h.stringify_keys!
h.keys.each { |k| keep(k) }
super(h)
super
end
alias :merge! :update
def replace(h) #:nodoc:
@used = {}
super(h.stringify_keys)
super
end
# Sets a flash that will not be available to the next action, only to the current.
@@ -163,7 +126,8 @@ module ActionController #:nodoc:
end
def store(session, key = "flash")
session[key] = to_session_value
return if self.empty?
session[key] = self
end
private
@@ -174,20 +138,11 @@ module ActionController #:nodoc:
# use('msg', false) # marks the "msg" entry as unused (keeps it around for one more action)
def use(k=nil, v=true)
unless k.nil?
@used[k.to_s] = v
@used[k] = v
else
keys.each{ |key| use(key, v) }
end
end
def self.new_from_values(flashes, used)
new.tap do |flash_hash|
flashes.each do |k, v|
flash_hash[k] = v
end
flash_hash.instance_variable_set("@used", used)
end
end
end
module InstanceMethods #:nodoc:
@@ -213,11 +168,11 @@ module ActionController #:nodoc:
if notice = response_status_and_flash.delete(:notice)
flash[:notice] = notice
end
if other_flashes = response_status_and_flash.delete(:flash)
flash.update(other_flashes)
end
redirect_to_without_flash(options, response_status_and_flash)
end
@@ -226,19 +181,19 @@ module ActionController #:nodoc:
# to put a new one.
def flash #:doc:
if !defined?(@_flash)
@_flash = Flash::FlashHash.from_session_value(session["flash"])
@_flash = session["flash"] || FlashHash.new
@_flash.sweep
end
@_flash
end
# Convenience accessor for flash[:alert]
def alert
flash[:alert]
end
# Convenience accessor for flash[:alert]=
def alert=(message)
flash[:alert] = message
@@ -248,7 +203,7 @@ module ActionController #:nodoc:
def notice
flash[:notice]
end
# Convenience accessor for flash[:notice]=
def notice=(message)
flash[:notice] = message

View File

@@ -423,13 +423,13 @@ EOM
# Override Rack's GET method to support indifferent access
def GET
@env["action_controller.request.query_parameters"] ||= deep_munge(normalize_parameters(super) || {})
@env["action_controller.request.query_parameters"] ||= normalize_parameters(super)
end
alias_method :query_parameters, :GET
# Override Rack's POST method to support indifferent access
def POST
@env["action_controller.request.request_parameters"] ||= deep_munge(normalize_parameters(super) || {})
@env["action_controller.request.request_parameters"] ||= normalize_parameters(super)
end
alias_method :request_parameters, :POST
@@ -469,22 +469,6 @@ EOM
!(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host))
end
# Remove nils from the params hash
def deep_munge(hash)
hash.each do |k, v|
case v
when Array
v.grep(Hash) { |x| deep_munge(x) }
v.compact!
hash[k] = nil if v.empty?
when Hash
deep_munge(v)
end
end
hash
end
# Convert nested Hashs to HashWithIndifferentAccess and replace
# file upload hashs with UploadedFile objects
def normalize_parameters(value)

View File

@@ -106,7 +106,7 @@ module ActionController #:nodoc:
# Sets the token value for the current session. Pass a <tt>:secret</tt> option
# in +protect_from_forgery+ to add a custom salt to the hash.
def form_authenticity_token
session[:_csrf_token] ||= ActiveSupport::SecureRandom.base64(32)
session[:_csrf_token] ||= SecureRandom.base64(32)
end
def protect_against_forgery?

View File

@@ -2,7 +2,7 @@ require 'rack/utils'
module ActionController
module Session
class AbstractStore
class AbstractStore
ENV_SESSION_KEY = 'rack.session'.freeze
ENV_SESSION_OPTIONS_KEY = 'rack.session.options'.freeze
@@ -55,17 +55,17 @@ module ActionController
def [](key)
load_for_read!
super(key.to_s) || super(key)
super
end
def has_key?(key)
load_for_read!
super(key.to_s) || super(key)
super
end
def []=(key, value)
load_for_write!
super(key.to_s, value)
super
end
def clear
@@ -87,9 +87,7 @@ module ActionController
def delete(key)
load_for_write!
value = super(key)
string_value = super(key.to_s)
string_value || value
super
end
def data
@@ -121,7 +119,7 @@ module ActionController
end
private
def load_for_read!
load! if !loaded? && exists?
end
@@ -185,7 +183,7 @@ module ActionController
request = ActionController::Request.new(env)
return response if (options[:secure] && !request.ssl?)
session_data.send(:load!) if session_data.is_a?(AbstractStore::SessionHash) && !session_data.loaded?
sid = options[:id] || generate_sid
@@ -207,14 +205,14 @@ module ActionController
end
private
def prepare!(env)
env[ENV_SESSION_KEY] = SessionHash.new(self, env)
env[ENV_SESSION_OPTIONS_KEY] = OptionsHash.new(self, env, @default_options)
end
def generate_sid
ActiveSupport::SecureRandom.hex(16)
SecureRandom.hex(16)
end
def load_session(env)
@@ -224,7 +222,7 @@ module ActionController
[sid, session]
end
end
def extract_session_id(env)
stale_session_check! do
request = Rack::Request.new(env)
@@ -237,7 +235,7 @@ module ActionController
def current_session_id(env)
env[ENV_SESSION_OPTIONS_KEY][:id]
end
def exists?(env)
current_session_id(env).present?
end
@@ -249,11 +247,11 @@ module ActionController
def set_session(env, sid, session_data)
raise '#set_session needs to be implemented.'
end
def destroy(env)
raise '#destroy needs to be implemented.'
end
module SessionUtils
private
def stale_session_check!

View File

@@ -37,7 +37,7 @@ module ActionController
# Note that changing digest or secret invalidates all existing sessions!
class CookieStore
include AbstractStore::SessionUtils
# Cookies can typically store 4096 bytes.
MAX = 4096
SECRET_MIN_LENGTH = 30 # characters
@@ -86,8 +86,7 @@ module ActionController
@secret = options.delete(:secret).freeze
@digest = options.delete(:digest) || 'SHA1'
@serializer = options.delete(:serializer) || Marshal
@verifier = verifier_for(@secret, @digest, @serializer)
@verifier = verifier_for(@secret, @digest)
@default_options = DEFAULT_OPTIONS.merge(options).freeze
@@ -96,21 +95,14 @@ module ActionController
def call(env)
prepare!(env)
status, headers, body = @app.call(env)
session_data = env[ENV_SESSION_KEY]
options = env[ENV_SESSION_OPTIONS_KEY]
request = ActionController::Request.new(env)
if !(options[:secure] && !request.ssl?) && (!session_data.is_a?(AbstractStore::SessionHash) || session_data.loaded? || options[:expire_after])
# Backport standard Rack::Session::Cookie behavior
# Skip writing session if env['rack.session.options'][:skip] is set
if options[:skip]
return [status, headers, body]
end
session_data.send(:load!) if session_data.is_a?(AbstractStore::SessionHash) && !session_data.loaded?
persistent_session_id!(session_data)
@@ -130,7 +122,7 @@ module ActionController
end
private
def prepare!(env)
env[ENV_SESSION_KEY] = AbstractStore::SessionHash.new(self, env)
env[ENV_SESSION_OPTIONS_KEY] = AbstractStore::OptionsHash.new(self, env, @default_options)
@@ -139,13 +131,13 @@ module ActionController
def load_session(env)
data = unpacked_cookie_data(env)
data = persistent_session_id!(data)
[data["session_id"] || data[:session_id], data]
[data[:session_id], data]
end
def extract_session_id(env)
if data = unpacked_cookie_data(env)
persistent_session_id!(data) unless data.empty?
data["session_id"] || data[:session_id]
data[:session_id]
else
nil
end
@@ -209,19 +201,19 @@ module ActionController
if secret.length < SECRET_MIN_LENGTH
raise ArgumentError, "Secret should be something secure, " +
"like \"#{ActiveSupport::SecureRandom.hex(16)}\". The value you " +
"like \"#{SecureRandom.hex(16)}\". The value you " +
"provided, \"#{secret}\", is shorter than the minimum length " +
"of #{SECRET_MIN_LENGTH} characters"
end
end
def verifier_for(secret, digest, serializer)
def verifier_for(secret, digest)
key = secret.respond_to?(:call) ? secret.call : secret
ActiveSupport::MessageVerifier.new(key, digest: digest, serializer: serializer)
ActiveSupport::MessageVerifier.new(key, digest: digest)
end
def generate_sid
ActiveSupport::SecureRandom.hex(16)
SecureRandom.hex(16)
end
def destroy(env)
@@ -233,12 +225,12 @@ module ActionController
end
def inject_persistent_session_id(data)
requires_session_id?(data) ? { "session_id" => generate_sid } : {}
requires_session_id?(data) ? { :session_id => generate_sid } : {}
end
def requires_session_id?(data)
if data
data.respond_to?(:key?) && !(data.key?("session_id") || data.key?(:session_id))
data.respond_to?(:key?) && !data.key?(:session_id)
else
true
end

View File

@@ -1,5 +1,3 @@
require 'active_support/core_ext/string/bytesize'
module ActionController #:nodoc:
# Methods for sending arbitrary data and for streaming files to the browser,
# instead of rendering.

View File

@@ -219,7 +219,7 @@ module ActionController #:nodoc:
# A shortcut to the flash. Returns an empty hash if no session flash exists.
def flash
ActionController::Flash::FlashHash.from_session_value(session["flash"]) || {}
session['flash'] || {}
end
# Do we have a flash?

View File

@@ -22,12 +22,12 @@
#++
begin
require 'active_support'
require 'active_support/all'
rescue LoadError
activesupport_path = "#{File.dirname(__FILE__)}/../../activesupport/lib"
if File.directory?(activesupport_path)
$:.unshift activesupport_path
require 'active_support'
require 'active_support/all'
end
end

View File

@@ -768,11 +768,7 @@ module ActionView
options = options.stringify_keys
tag_value = options.delete("value")
name_and_id = options.dup
if name_and_id.has_key?("for")
name_and_id["id"] = name_and_id["for"]
else
name_and_id.delete("id")
end
name_and_id["id"] = name_and_id["for"]
add_default_name_and_id_for_value(tag_value, name_and_id)
options.delete("index")
options["for"] ||= name_and_id["id"]
@@ -932,15 +928,15 @@ module ActionView
def add_default_name_and_id(options)
if options.has_key?("index")
options["name"] = tag_name_with_index(options["index"]) unless options.has_key?("name")
options["id"] = tag_id_with_index(options["index"]) unless options.has_key?("id")
options["name"] ||= tag_name_with_index(options["index"])
options["id"] ||= tag_id_with_index(options["index"])
options.delete("index")
elsif defined?(@auto_index)
options["name"] = tag_name_with_index(@auto_index) unless options.has_key?("name")
options["id"] = tag_id_with_index(@auto_index) unless options.has_key?("id")
options["name"] ||= tag_name_with_index(@auto_index)
options["id"] ||= tag_id_with_index(@auto_index)
else
options["name"] = tag_name + (options.has_key?('multiple') ? '[]' : '') unless options.has_key?("name")
options["id"] = tag_id unless options.has_key?("id")
options["name"] ||= tag_name + (options.has_key?('multiple') ? '[]' : '')
options["id"] ||= tag_id
end
end

View File

@@ -73,8 +73,6 @@ module ActionView
def number_to_currency(number, options = {})
options.symbolize_keys!
options[:format] = ERB::Util.html_escape(options[:format]) if options[:format]
defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {}
currency = I18n.translate(:'number.currency.format', :locale => options[:locale], :raise => true) rescue {}
defaults = defaults.merge(currency)
@@ -87,13 +85,11 @@ module ActionView
separator = '' if precision == 0
begin
value = number_with_precision(number,
format.gsub(/%n/, number_with_precision(number,
:precision => precision,
:delimiter => delimiter,
:separator => separator)
value = ERB::Util.html_escape(value) if value
unit = ERB::Util.html_escape(unit)
format.gsub(/%n/, value).gsub(/%u/, unit).html_safe
).gsub(/%u/, unit).html_safe
rescue
number
end

View File

@@ -17,7 +17,7 @@ module ActionView
src << "@output_buffer.safe_append='"
src << "\n" * @newline_pending if @newline_pending > 0
src << escape_text(text)
src << "'.freeze;"
src << "';"
@newline_pending = 0
end
@@ -63,7 +63,7 @@ module ActionView
def flush_newline_if_pending(src)
if @newline_pending > 0
src << "@output_buffer.safe_append='#{"\n" * @newline_pending}'.freeze;"
src << "@output_buffer.safe_append='#{"\n" * @newline_pending}';"
@newline_pending = 0
end
end

View File

@@ -1,7 +1,9 @@
$:.unshift File.expand_path('../../lib', __FILE__)
$:.unshift File.expand_path('../../../activesupport/lib', __FILE__)
$:.unshift File.expand_path('../fixtures/helpers', __FILE__)
$:.unshift File.expand_path('../fixtures/alternate_helpers', __FILE__)
begin
old, $VERBOSE = $VERBOSE, nil
require File.expand_path('../../../load_paths', __FILE__)
ensure
$VERBOSE = old
end
require 'rubygems'
require 'yaml'

View File

@@ -622,19 +622,6 @@ class FragmentCachingTest < ActionController::TestCase
assert_equal 'generated till now -> fragment content', buffer
end
def test_fragment_for_bytesize
buffer = "\xC4\x8D"
buffer.force_encoding('ASCII-8BIT')
@controller.fragment_for(buffer, 'bytesize') do
buffer.force_encoding('UTF-8')
buffer << "abc"
end
assert_equal Encoding::UTF_8, buffer.encoding
assert_equal "abc", @store.read('views/bytesize')
end
def test_html_safety
assert_nil @store.read('views/name')
content = 'value'.html_safe

View File

@@ -45,7 +45,7 @@ class DispatcherTest < Test::Unit::TestCase
def test_rebuilds_middleware_stack_on_every_request_if_in_loading_mode
dispatcher = create_dispatcher(false)
dispatcher.instance_variable_set(:"@app", lambda { |env| })
dispatcher.expects(:build_middleware_stack).never
dispatcher.expects(:build_middleware_stack).twice
dispatcher.call(nil)
Reloader.default_lock.unlock
dispatcher.call(nil)

View File

@@ -5,6 +5,7 @@ class BaseRackTest < ActiveSupport::TestCase
@env = {
"HTTP_MAX_FORWARDS" => "10",
"SERVER_NAME" => "glu.ttono.us",
"FCGI_ROLE" => "RESPONDER",
"AUTH_TYPE" => "Basic",
"HTTP_X_FORWARDED_HOST" => "glu.ttono.us",
"HTTP_ACCEPT_CHARSET" => "UTF-8",

View File

@@ -79,7 +79,7 @@ module RequestForgeryProtectionTests
def setup
@token = "cf50faa3fe97702ca1ae"
ActiveSupport::SecureRandom.stubs(:base64).returns(@token)
SecureRandom.stubs(:base64).returns(@token)
ActionController::Base.request_forgery_protection_token = :authenticity_token
end
@@ -186,7 +186,7 @@ class RequestForgeryProtectionControllerTest < ActionController::TestCase
include RequestForgeryProtectionTests
test 'should emit a csrf-token meta tag' do
ActiveSupport::SecureRandom.stubs(:base64).returns(@token + '<=?')
SecureRandom.stubs(:base64).returns(@token + '<=?')
get :meta
assert_equal %(<meta name="csrf-param" content="authenticity_token"/>\n<meta name="csrf-token" content="cf50faa3fe97702ca1ae&lt;=?"/>), @response.body
end
@@ -208,7 +208,7 @@ class FreeCookieControllerTest < ActionController::TestCase
@response = ActionController::TestResponse.new
@token = "cf50faa3fe97702ca1ae"
ActiveSupport::SecureRandom.stubs(:base64).returns(@token)
SecureRandom.stubs(:base64).returns(@token)
end
def test_should_not_render_form_with_token_tag

View File

@@ -175,10 +175,6 @@ class FormHelperTest < ActionView::TestCase
I18n.locale = old_locale
end
def test_label_with_for_attribute_as_nil
assert_dom_equal('<label>Title</label>', label(:post, :title, nil, :for => nil))
end
def test_label_with_for_attribute_as_symbol
assert_dom_equal('<label for="my_for">Title</label>', label(:post, :title, nil, :for => "my_for"))
end
@@ -278,11 +274,6 @@ class FormHelperTest < ActionView::TestCase
hidden_field("post", "title", :value => "Something Else")
end
def test_text_field_with_id_as_nil
assert_dom_equal '<input name="post[title]" type="hidden" value="Hello World" />',
hidden_field("post", "title", :id => nil)
end
def test_check_box
assert_dom_equal(
'<input name="post[secret]" type="hidden" value="0" /><input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="1" />',

View File

@@ -3,12 +3,6 @@ require 'abstract_unit'
class NumberHelperTest < ActionView::TestCase
tests ActionView::Helpers::NumberHelper
def test_number_helpers_escape_delimiter_and_separator
assert_equal "$1&lt;script&gt;&lt;/script&gt;01", number_to_currency(1.01, :separator => "<script></script>")
assert_equal "$1&lt;script&gt;&lt;/script&gt;000.00", number_to_currency(1000, :delimiter => "<script></script>")
assert_equal "&lt;script&gt;1,000.00$&lt;/script&gt;", number_to_currency(1000, :format => "<script>%n%u</script>")
end
def test_number_to_phone
assert_equal("555-1234", number_to_phone(5551234))
assert_equal("800-555-1212", number_to_phone(8005551212))
@@ -30,10 +24,9 @@ class NumberHelperTest < ActionView::TestCase
assert_equal("$1,234,567,890.51", number_to_currency(1234567890.506))
assert_equal("$1,234,567,892", number_to_currency(1234567891.50, {:precision => 0}))
assert_equal("$1,234,567,890.5", number_to_currency(1234567890.50, {:precision => 1}))
assert_equal("&pound;1234567890,50", number_to_currency(1234567890.50, {:unit => raw("&pound;"), :separator => ",", :delimiter => ""}))
assert_equal("&amp;pound;1234567890,50", number_to_currency(1234567890.50, {:unit => "&pound;", :separator => ",", :delimiter => ""}))
assert_equal("&pound;1234567890,50", number_to_currency(1234567890.50, {:unit => "&pound;", :separator => ",", :delimiter => ""}))
assert_equal("$1,234,567,890.50", number_to_currency("1234567890.50"))
assert_equal("1,234,567,890.50 K&#269;", number_to_currency("1234567890.50", {:unit => raw("K&#269;"), :format => "%n %u"}))
assert_equal("1,234,567,890.50 K&#269;", number_to_currency("1234567890.50", {:unit => "K&#269;", :format => "%n %u"}))
#assert_equal("$x.", number_to_currency("x")) # fails due to API consolidation
assert_equal("$x", number_to_currency("x"))
assert_nil number_to_currency(nil)

View File

@@ -9,8 +9,8 @@ module RenderTestCases
# Reload and register danish language for testing
I18n.reload!
I18n.backend.store_translations 'da', 'da' => {}
I18n.backend.store_translations 'pt-BR', 'pt-BR' => {}
I18n.backend.store_translations 'da', {}
I18n.backend.store_translations 'pt-BR', {}
# Ensure original are still the same since we are reindexing view paths
assert_equal ORIGINAL_LOCALES, I18n.available_locales.map(&:to_s).sort

View File

@@ -8,7 +8,7 @@ require 'active_model'
require 'active_model/state_machine'
$:.unshift File.expand_path('../../../activesupport/lib', __FILE__)
require 'active_support'
require 'active_support/all'
require 'active_support/test_case'
class ActiveModel::TestCase < ActiveSupport::TestCase

View File

@@ -22,12 +22,12 @@
#++
begin
require 'active_support'
require 'active_support/all'
rescue LoadError
activesupport_path = "#{File.dirname(__FILE__)}/../../activesupport/lib"
if File.directory?(activesupport_path)
$:.unshift activesupport_path
require 'active_support'
require 'active_support/all'
end
end
@@ -56,6 +56,7 @@ module ActiveRecord
autoload :DynamicScopeMatch, 'active_record/dynamic_scope_match'
autoload :Migration, 'active_record/migration'
autoload :Migrator, 'active_record/migration'
autoload :ModelName, 'active_record/model_name'
autoload :NamedScope, 'active_record/named_scope'
autoload :NestedAttributes, 'active_record/nested_attributes'
autoload :Observing, 'active_record/observer'

View File

@@ -2487,6 +2487,12 @@ module ActiveRecord #:nodoc:
result
end
# Returns an ActiveRecord::ModelName object for module. It can be
# used to retrieve all kinds of naming-related information.
def model_name
@model_name ||= ::ActiveRecord::ModelName.new(name)
end
# A model instance's primary key is always available as model.id
# whether you name it the default 'id' or set it to something else.
def id

View File

@@ -195,9 +195,7 @@ module ActiveRecord
def log_info(sql, name, ms)
if @logger && @logger.debug?
name = '%s (%.1fms)' % [name || 'SQL', ms]
if sql.respond_to?(:force_encoding)
sql = sql.dup.force_encoding 'binary'
end
sql.force_encoding 'binary' if sql.respond_to?(:force_encoding)
@logger.debug(format_log_entry(name, sql.squeeze(' ')))
end
end
@@ -214,7 +212,13 @@ module ActiveRecord
log_info(sql, name, 0)
nil
end
rescue => e
rescue SystemExit, SignalException, NoMemoryError => e
# Don't re-wrap these exceptions. They are probably not being caused by invalid
# sql, but rather some external stimulus beyond the responsibilty of this code.
# Additionaly, wrapping these exceptions with StatementInvalid would lead to
# meaningful loss of data, such as losing SystemExit#status.
raise e
rescue Exception => e
# Log message and raise exception.
# Set last_verification to 0, so that connection gets verified
# upon reentering the request loop

View File

@@ -0,0 +1,25 @@
module ActiveRecord
class ModelName < String
alias_method :cache_key, :collection
def singular
@singular ||= ActiveSupport::Inflector.underscore(self).tr('/', '_').freeze
end
def plural
@plural ||= ActiveSupport::Inflector.pluralize(singular).freeze
end
def element
@element ||= ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(self)).freeze
end
def collection
@collection ||= ActiveSupport::Inflector.tableize(self).freeze
end
def partial_path
@partial_path ||= "#{collection}/#{element}".freeze
end
end
end

View File

@@ -1,5 +1,4 @@
require 'active_support/json'
require 'active_support/core_ext/module/model_naming'
module ActiveRecord #:nodoc:
module Serialization

View File

@@ -1,5 +1,9 @@
$:.unshift(File.dirname(__FILE__) + '/../../lib')
$:.unshift(File.dirname(__FILE__) + '/../../../activesupport/lib')
begin
old, $VERBOSE = $VERBOSE, nil
require File.expand_path('../../../load_paths', __FILE__)
ensure
$VERBOSE = old
end
require 'config'

View File

@@ -11,4 +11,7 @@ Gem::Specification.new do |s|
s.homepage = 'http://www.rubyonrails.org'
s.require_path = 'lib'
s.add_dependency('i18n', '~> 0.6', '>= 0.6.4')
s.add_dependency('multi_json', '~> 1.0')
end

View File

@@ -2,7 +2,7 @@
begin
$:.unshift(File.expand_path(File.dirname(__FILE__) + '/../lib'))
require 'active_support'
require 'active_support/all'
rescue IOError
end

View File

@@ -1,30 +0,0 @@
#!/usr/bin/env ruby
Dir.chdir(File.expand_path("..", __FILE__))
$: << File.expand_path("../lib", __FILE__)
require "active_support"
ActiveSupport::TimeZone.all
def flatten_constants(mod, ary = [])
ary << mod
mod.constants.each do |const|
flatten_constants(mod.const_get(const), ary)
end
ary
end
defns = flatten_constants(TZInfo::Definitions).select { |mod|
defined?(mod.get)
}.map { |tz|
tz.get
}
file = "lib/active_support/vendor/tzinfo-0.3.12/tzinfo/definitions.dump"
data = Marshal.dump(defns)
Marshal.load(data)
File.open(file, "wb") do |f|
require "pry"
pry binding
f.write(data)
end
puts "Wrote #{data.size} bytes to #{file}"

View File

@@ -1,5 +1,5 @@
#--
# Copyright (c) 2005 David Heinemeier Hansson
# Copyright (c) 2005-2011 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -21,40 +21,62 @@
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#++
module ActiveSupport
def self.load_all!
[Dependencies, Deprecation, Gzip, MessageVerifier, Multibyte, SecureRandom, TimeWithZone]
end
require 'securerandom'
autoload :BacktraceCleaner, 'active_support/backtrace_cleaner'
autoload :Base64, 'active_support/base64'
autoload :BasicObject, 'active_support/basic_object'
autoload :BufferedLogger, 'active_support/buffered_logger'
autoload :Cache, 'active_support/cache'
autoload :Callbacks, 'active_support/callbacks'
autoload :Deprecation, 'active_support/deprecation'
autoload :Duration, 'active_support/duration'
autoload :Gzip, 'active_support/gzip'
autoload :Inflector, 'active_support/inflector'
autoload :Memoizable, 'active_support/memoizable'
autoload :MessageEncryptor, 'active_support/message_encryptor'
autoload :MessageVerifier, 'active_support/message_verifier'
autoload :Multibyte, 'active_support/multibyte'
autoload :OptionMerger, 'active_support/option_merger'
autoload :OrderedHash, 'active_support/ordered_hash'
autoload :OrderedOptions, 'active_support/ordered_options'
autoload :Rescuable, 'active_support/rescuable'
autoload :SafeBuffer, 'active_support/core_ext/string/output_safety'
autoload :SecureRandom, 'active_support/secure_random'
autoload :StringInquirer, 'active_support/string_inquirer'
autoload :TimeWithZone, 'active_support/time_with_zone'
autoload :TimeZone, 'active_support/values/time_zone'
autoload :XmlMini, 'active_support/xml_mini'
module ActiveSupport
class << self
attr_accessor :load_all_hooks
def on_load_all(&hook) load_all_hooks << hook end
def load_all!; load_all_hooks.each { |hook| hook.call } end
end
self.load_all_hooks = []
on_load_all do
[Dependencies, Deprecation, Gzip, MessageVerifier, Multibyte]
end
end
require 'active_support/vendor'
require 'active_support/core_ext'
require 'active_support/dependencies'
require 'active_support/json'
require "active_support/dependencies/autoload"
require "active_support/version"
I18n.load_path << "#{File.dirname(__FILE__)}/active_support/locale/en.yml"
module ActiveSupport
extend ActiveSupport::Autoload
autoload :DescendantsTracker
autoload :FileUpdateChecker
autoload :LogSubscriber
autoload :Notifications
# TODO: Narrow this list down
eager_autoload do
autoload :BacktraceCleaner
autoload :Base64
autoload :BasicObject
autoload :Benchmarkable
autoload :BufferedLogger
autoload :Cache
autoload :Callbacks
autoload :Concern
autoload :Configurable
autoload :Deprecation
autoload :Gzip
autoload :Inflector
autoload :JSON
autoload :Memoizable
autoload :MessageEncryptor
autoload :MessageVerifier
autoload :Multibyte
autoload :OptionMerger
autoload :OrderedHash
autoload :OrderedOptions
autoload :Rescuable
autoload :StringInquirer
autoload :TaggedLogging
autoload :XmlMini
end
autoload :SafeBuffer, "active_support/core_ext/string/output_safety"
autoload :TestCase
end
autoload :I18n, "active_support/i18n"

View File

@@ -1,8 +1,3 @@
# For forward compatibility with Rails 3.
#
# require 'active_support' loads a very bare minumum in Rails 3.
# require 'active_support/all' loads the whole suite like Rails 2 did.
#
# To prepare for Rails 3, switch to require 'active_support/all' now.
require 'active_support'
require 'active_support/time'
require 'active_support/core_ext'

View File

@@ -1,27 +1,43 @@
module ActiveSupport
# Many backtraces include too much information that's not relevant for the context. This makes it hard to find the signal
# in the backtrace and adds debugging time. With a BacktraceCleaner, you can setup filters and silencers for your particular
# context, so only the relevant lines are included.
# Backtraces often include many lines that are not relevant for the context under review. This makes it hard to find the
# signal amongst the backtrace noise, and adds debugging time. With a BacktraceCleaner, filters and silencers are used to
# remove the noisy lines, so that only the most relevant lines remain.
#
# If you need to reconfigure an existing BacktraceCleaner, like the one in Rails, to show as much as possible, you can always
# call BacktraceCleaner#remove_silencers!
# Filters are used to modify lines of data, while silencers are used to remove lines entirely. The typical filter use case
# is to remove lengthy path information from the start of each line, and view file paths relevant to the app directory
# instead of the file system root. The typical silencer use case is to exclude the output of a noisy library from the
# backtrace, so that you can focus on the rest.
#
# Example:
# ==== Example:
#
# bc = BacktraceCleaner.new
# bc.add_filter { |line| line.gsub(Rails.root, '') }
# bc.add_filter { |line| line.gsub(Rails.root, '') }
# bc.add_silencer { |line| line =~ /mongrel|rubygems/ }
# bc.clean(exception.backtrace) # will strip the Rails.root prefix and skip any lines from mongrel or rubygems
#
# To reconfigure an existing BacktraceCleaner (like the default one in Rails) and show as much data as possible, you can
# always call <tt>BacktraceCleaner#remove_silencers!</tt>, which will restore the backtrace to a pristine state. If you
# need to reconfigure an existing BacktraceCleaner so that it does not filter or modify the paths of any lines of the
# backtrace, you can call BacktraceCleaner#remove_filters! These two methods will give you a completely untouched backtrace.
#
# Inspired by the Quiet Backtrace gem by Thoughtbot.
class BacktraceCleaner
def initialize
@filters, @silencers = [], []
end
# Returns the backtrace after all filters and silencers has been run against it. Filters run first, then silencers.
def clean(backtrace)
silence(filter(backtrace))
# Returns the backtrace after all filters and silencers have been run against it. Filters run first, then silencers.
def clean(backtrace, kind = :silent)
filtered = filter(backtrace)
case kind
when :silent
silence(filtered)
when :noise
noise(filtered)
else
filtered
end
end
# Adds a filter from the block provided. Each line in the backtrace will be mapped against this filter.
@@ -34,8 +50,8 @@ module ActiveSupport
@filters << block
end
# Adds a silencer from the block provided. If the silencer returns true for a given line, it'll be excluded from the
# clean backtrace.
# Adds a silencer from the block provided. If the silencer returns true for a given line, it will be excluded from
# the clean backtrace.
#
# Example:
#
@@ -46,26 +62,37 @@ module ActiveSupport
end
# Will remove all silencers, but leave in the filters. This is useful if your context of debugging suddenly expands as
# you suspect a bug in the libraries you use.
# you suspect a bug in one of the libraries you use.
def remove_silencers!
@silencers = []
end
def remove_filters!
@filters = []
end
private
def filter(backtrace)
@filters.each do |f|
backtrace = backtrace.map { |line| f.call(line) }
end
backtrace
end
def silence(backtrace)
@silencers.each do |s|
backtrace = backtrace.reject { |line| s.call(line) }
end
backtrace
end
def noise(backtrace)
@silencers.each do |s|
backtrace = backtrace.select { |line| s.call(line) }
end
backtrace
end
end

View File

@@ -1,33 +1,54 @@
require 'active_support/deprecation'
begin
require 'base64'
rescue LoadError
end
# The Base64 module isn't available in earlier versions of Ruby 1.9.
module Base64
# Encodes a string to its base 64 representation. Each 60 characters of
# output is separated by a newline character.
#
# ActiveSupport::Base64.encode64("Original unencoded string")
# # => "T3JpZ2luYWwgdW5lbmNvZGVkIHN0cmluZw==\n"
def self.encode64(data)
[data].pack("m")
end
module ActiveSupport
if defined? ::Base64
Base64 = ::Base64
else
# 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.
module Base64
# Encodes a string to its base 64 representation. Each 60 characters of
# output is separated by a newline character.
#
# ActiveSupport::Base64.encode64("Original unencoded string")
# # => "T3JpZ2luYWwgdW5lbmNvZGVkIHN0cmluZw==\n"
def self.encode64(data)
[data].pack("m")
end
# Decodes a base 64 encoded string to its original representation.
#
# ActiveSupport::Base64.decode64("T3JpZ2luYWwgdW5lbmNvZGVkIHN0cmluZw==")
# # => "Original unencoded string"
def self.decode64(data)
data.unpack("m").first
end
# Decodes a base 64 encoded string to its original representation.
#
# ActiveSupport::Base64.decode64("T3JpZ2luYWwgdW5lbmNvZGVkIHN0cmluZw==")
# # => "Original unencoded string"
def self.decode64(data)
data.unpack("m").first
end
end
end
unless Base64.respond_to?(:strict_encode64)
# Included in Ruby 1.9
def Base64.strict_encode64(value)
encode64(value).gsub(/\n/, '')
end
end
module ActiveSupport
module Base64
def self.encode64(value)
ActiveSupport::Deprecation.warn "ActiveSupport::Base64.encode64 " \
"is deprecated. Use Base64.encode64 instead", caller
::Base64.encode64(value)
end
def self.decode64(value)
ActiveSupport::Deprecation.warn "ActiveSupport::Base64.decode64 " \
"is deprecated. Use Base64.decode64 instead", caller
::Base64.decode64(value)
end
def self.encode64s(value)
ActiveSupport::Deprecation.warn "ActiveSupport::Base64.encode64s " \
"is deprecated. Use Base64.strict_encode64 instead", caller
::Base64.strict_encode64(value)
end
end
end

View File

@@ -0,0 +1,55 @@
require 'active_support/core_ext/benchmark'
require 'active_support/core_ext/hash/keys'
module ActiveSupport
module Benchmarkable
# Allows you to measure the execution time of a block in a template and records the result to
# the log. Wrap this block around expensive operations or possible bottlenecks to get a time
# reading for the operation. For example, let's say you thought your file processing method
# was taking too long; you could wrap it in a benchmark block.
#
# <% benchmark "Process data files" do %>
# <%= expensive_files_operation %>
# <% end %>
#
# That would add something like "Process data files (345.2ms)" to the log, which you can then
# use to compare timings when optimizing your code.
#
# You may give an optional logger level (:debug, :info, :warn, :error) as the :level option.
# The default logger level value is :info.
#
# <% benchmark "Low-level files", :level => :debug do %>
# <%= lowlevel_files_operation %>
# <% end %>
#
# Finally, you can pass true as the third argument to silence all log activity (other than the
# timing information) from inside the block. This is great for boiling down a noisy block to
# just a single statement that produces one log line:
#
# <% benchmark "Process data files", :level => :info, :silence => true do %>
# <%= expensive_and_chatty_files_operation %>
# <% end %>
def benchmark(message = "Benchmarking", options = {})
if logger
options.assert_valid_keys(:level, :silence)
options[:level] ||= :info
result = nil
ms = Benchmark.ms { result = options[:silence] ? silence { yield } : yield }
logger.send(options[:level], '%s (%.1fms)' % [ message, ms ])
result
else
yield
end
end
# Silence the logger during the execution of the block.
#
def silence
old_logger_level, logger.level = logger.level, ::Logger::ERROR if logger
yield
ensure
logger.level = old_logger_level if logger
end
end
end

View File

@@ -1,4 +1,9 @@
require 'thread'
require 'logger'
require 'active_support/core_ext/logger'
require 'active_support/core_ext/class/attribute_accessors'
require 'active_support/deprecation'
require 'fileutils'
module ActiveSupport
# Inspired by the buffered logger idea by Ezra
@@ -25,62 +30,69 @@ module ActiveSupport
def silence(temporary_level = ERROR)
if silencer
begin
old_logger_level, self.level = level, temporary_level
yield self
logger = self.class.new @log_dest.dup, temporary_level
yield logger
ensure
self.level = old_logger_level
logger.close
end
else
yield self
end
end
deprecate :silence
attr_accessor :level
attr_reader :auto_flushing
deprecate :auto_flushing
def initialize(log, level = DEBUG)
@level = level
@buffer = {}
@auto_flushing = 1
@guard = Mutex.new
@log_dest = log
if log.respond_to?(:write)
@log = log
elsif File.exist?(log)
@log = open(log, (File::WRONLY | File::APPEND))
@log.sync = true
else
FileUtils.mkdir_p(File.dirname(log))
@log = open(log, (File::WRONLY | File::APPEND | File::CREAT))
@log.sync = true
@log.write("# Logfile created on %s" % [Time.now.to_s])
unless log.respond_to?(:write)
unless File.exist?(File.dirname(log))
ActiveSupport::Deprecation.warn(<<-eowarn)
Automatic directory creation for '#{log}' is deprecated. Please make sure the directory for your log file exists before creating the logger.
eowarn
FileUtils.mkdir_p(File.dirname(log))
end
end
@log = open_logfile log
self.level = level
end
def open_log(log, mode)
open(log, mode).tap do |open_log|
open_log.set_encoding(Encoding::BINARY) if open_log.respond_to?(:set_encoding)
open_log.sync = true
end
end
deprecate :open_log
def level
@log.level
end
def level=(l)
@log.level = l
end
def add(severity, message = nil, progname = nil, &block)
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.
message = "#{message}\n" unless message[-1] == ?\n
if message.respond_to?(:force_encoding)
buffer << message.force_encoding(Encoding.default_external)
else
buffer << message
end
auto_flush
message
@log.add(severity, message, progname, &block)
end
for severity in Severity.constants
# Dynamically add methods such as:
# def info
# def warn
# def debug
Severity.constants.each do |severity|
class_eval <<-EOT, __FILE__, __LINE__ + 1
def #{severity.downcase}(message = nil, progname = nil, &block) # def debug(message = nil, progname = nil, &block)
add(#{severity}, message, progname, &block) # add(DEBUG, message, progname, &block)
end # end
#
def #{severity.downcase}? # def debug?
#{severity} >= @level # DEBUG >= @level
end # end
def #{severity.downcase}(message = nil, progname = nil, &block) # def debug(message = nil, progname = nil, &block)
add(#{severity}, message, progname, &block) # add(DEBUG, message, progname, &block)
end # end
def #{severity.downcase}? # def debug?
#{severity} >= level # DEBUG >= level
end # end
EOT
end
@@ -89,45 +101,25 @@ module ActiveSupport
# never auto-flush. If you turn auto-flushing off, be sure to regularly
# flush the log yourself -- it will eat up memory until you do.
def auto_flushing=(period)
@auto_flushing =
case period
when true; 1
when false, nil, 0; MAX_BUFFER_SIZE
when Integer; period
else raise ArgumentError, "Unrecognized auto_flushing period: #{period.inspect}"
end
end
deprecate :auto_flushing=
def flush
@guard.synchronize do
unless buffer.empty?
old_buffer = buffer
@log.write(old_buffer.join)
end
end
deprecate :flush
# 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
end
def respond_to?(method, include_private = false)
return false if method.to_s == "flush"
super
end
def close
flush
@log.close if @log.respond_to?(:close)
@log = nil
@log.close
end
protected
def auto_flush
flush if buffer.size >= @auto_flushing
end
def buffer
@buffer[Thread.current] ||= []
end
def clear_buffer
@buffer.delete(Thread.current)
end
private
def open_logfile(log)
Logger.new log
end
end
end

View File

@@ -0,0 +1,6 @@
begin
require 'builder'
rescue LoadError => e
$stderr.puts "You don't have builder installed in your application. Please add it to your Gemfile and run bundle install"
raise e
end

View File

@@ -1,76 +1,99 @@
require 'benchmark'
require 'zlib'
require 'active_support/core_ext/array/extract_options'
require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/benchmark'
require 'active_support/core_ext/exception'
require 'active_support/core_ext/class/attribute_accessors'
require 'active_support/core_ext/numeric/bytes'
require 'active_support/core_ext/numeric/time'
require 'active_support/core_ext/object/to_param'
require 'active_support/core_ext/string/inflections'
module ActiveSupport
# See ActiveSupport::Cache::Store for documentation.
module Cache
autoload :FileStore, 'active_support/cache/file_store'
autoload :MemoryStore, 'active_support/cache/memory_store'
autoload :SynchronizedMemoryStore, 'active_support/cache/synchronized_memory_store'
autoload :DRbStore, 'active_support/cache/drb_store'
autoload :MemCacheStore, 'active_support/cache/mem_cache_store'
autoload :CompressedMemCacheStore, 'active_support/cache/compressed_mem_cache_store'
autoload :NullStore, 'active_support/cache/null_store'
# These options mean something to all cache implementations. Individual cache
# implementations may support additional options.
UNIVERSAL_OPTIONS = [:namespace, :compress, :compress_threshold, :expires_in, :race_condition_ttl]
module Strategy
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(:drb_store)
# # => returns a new ActiveSupport::Cache::DRbStore 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 = *([ store_option ].flatten)
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 == :drb_store ? "DRbStore" : store.to_s.camelize)
store_class = ActiveSupport::Cache.const_get(store_class_name)
store_class.new(*parameters)
when nil
ActiveSupport::Cache::MemoryStore.new
else
store
end
end
def self.expand_cache_key(key, namespace = nil)
expanded_cache_key = namespace ? "#{namespace}/" : ""
if ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"]
expanded_cache_key << "#{ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"]}/"
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
expanded_cache_key << case
when key.respond_to?(:cache_key)
key.cache_key
when key.is_a?(Array)
key.collect { |element| expand_cache_key(element) }.to_param
when key
key.to_param
def expand_cache_key(key, namespace = nil)
expanded_cache_key = namespace ? "#{namespace}/" : ""
if prefix = ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"]
expanded_cache_key << "#{prefix}/"
end
expanded_cache_key << retrieve_cache_key(key)
expanded_cache_key
end
private
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
expanded_cache_key
end
end
# An abstract cache store class. There are multiple cache store
@@ -79,28 +102,64 @@ module ActiveSupport
# ActiveSupport::Cache::MemCacheStore. MemCacheStore is currently the most
# popular cache store for large production websites.
#
# ActiveSupport::Cache::Store is meant for caching strings. Some cache
# store implementations, like MemoryStore, are able to cache arbitrary
# Ruby objects, but don't count on every cache store to be able to do that.
# Some implementations may not support all methods beyond the basic cache
# methods of +fetch+, +write+, +read+, +exist?+, and +delete+.
#
# ActiveSupport::Cache::Store can store any serializable Ruby object.
#
# cache = ActiveSupport::Cache::MemoryStore.new
#
#
# cache.read("city") # => nil
# cache.write("city", "Duckburgh")
# cache.read("city") # => "Duckburgh"
#
# Keys are always translated into Strings and are case sensitive. When an
# object is specified as a key and has a +cache_key+ method defined, this
# method will be called to define the key. Otherwise, the +to_param+
# method will be called. Hashes and Arrays can also be used as keys. The
# elements will be delimited by slashes, and the elements within a Hash
# will be sorted by key so they are consistent.
#
# cache.read("city") == cache.read(:city) # => true
#
# Nil values can be cached.
#
# If your cache is on a shared infrastructure, you can define a namespace
# for your cache entries. If a namespace is defined, it will be prefixed on
# to every key. The namespace can be either a static value or a Proc. If it
# is a Proc, it will be invoked when each key is evaluated so that you can
# use application logic to invalidate keys.
#
# cache.namespace = lambda { @last_mod_time } # Set the namespace to a variable
# @last_mod_time = Time.now # Invalidate the entire cache by changing namespace
#
#
# Caches can also store values in a compressed format to save space and
# reduce time spent sending data. Since there is overhead, values must be
# large enough to warrant compression. To turn on compression either pass
# <tt>:compress => true</tt> in the initializer or as an option to +fetch+
# or +write+. To specify the threshold at which to compress values, set the
# <tt>:compress_threshold</tt> option. The default threshold is 16K.
class Store
cattr_accessor :logger
attr_reader :silence, :logger_off
cattr_accessor :logger, :instance_writer => true
attr_reader :silence, :options
alias :silence? :silence
# Create a new cache. The options will be passed to any write method calls except
# for :namespace which can be used to set the global namespace for the cache.
def initialize(options = nil)
@options = options ? options.dup : {}
end
# Silence the logger.
def silence!
@silence = true
self
end
alias silence? silence
alias logger_off? logger_off
# Silence the logger within a block.
def mute
previous_silence, @silence = defined?(@silence) && @silence, true
yield
@@ -108,18 +167,27 @@ module ActiveSupport
@silence = previous_silence
end
# Set to true if cache stores should be instrumented. Default is false.
def self.instrument=(boolean)
Thread.current[:instrument_cache_store] = boolean
end
def self.instrument
Thread.current[:instrument_cache_store] || false
end
# Fetches data from the cache, using the given key. If there is data in
# the cache with the given key, then that data is returned.
#
# If there is no such data in the cache (a cache miss occurred), then
# then nil will be returned. However, if a block has been passed, then
# that block will be run in the event of a cache miss. The return value
# of the block will be written to the cache under the given cache key,
# and that return value will be returned.
# If there is no such data in the cache (a cache miss), then nil will be
# returned. However, if a block has been passed, that block will be run
# in the event of a cache miss. The return value of the block will be
# written to the cache under the given cache key, and that return value
# will be returned.
#
# cache.write("today", "Monday")
# cache.fetch("today") # => "Monday"
#
#
# cache.fetch("city") # => nil
# cache.fetch("city") do
# "Duckburgh"
@@ -132,42 +200,107 @@ module ActiveSupport
# cache.write("today", "Monday")
# cache.fetch("today", :force => true) # => nil
#
# Setting <tt>:compress</tt> will store a large cache entry set by the call
# in a compressed format.
#
#
# Setting <tt>:expires_in</tt> will set an expiration time on the cache.
# All caches support auto-expiring content after a specified number of
# seconds. This value can be specified as an option to the constructor
# (in which case all entries will be affected), or it can be supplied to
# the +fetch+ or +write+ method to effect just one entry.
#
# cache = ActiveSupport::Cache::MemoryStore.new(:expires_in => 5.minutes)
# cache.write(key, value, :expires_in => 1.minute) # Set a lower value for one entry
#
# Setting <tt>:race_condition_ttl</tt> is very useful in situations where a cache entry
# is used very frequently and is under heavy load. If a cache expires and due to heavy load
# seven different processes will try to read data natively and then they all will try to
# write to cache. To avoid that case the first process to find an expired cache entry will
# bump the cache expiration time by the value set in <tt>:race_condition_ttl</tt>. Yes
# this process is extending the time for a stale value by another few seconds. Because
# of extended life of the previous cache, other processes will continue to use slightly
# stale data for a just a big longer. In the meantime that first process will go ahead
# and will write into cache the new value. After that all the processes will start
# getting new value. The key is to keep <tt>:race_condition_ttl</tt> small.
#
# If the process regenerating the entry errors out, the entry will be regenerated
# after the specified number of seconds. Also note that the life of stale cache is
# extended only if it expired recently. Otherwise a new value is generated and
# <tt>:race_condition_ttl</tt> does not play any role.
#
# # Set all values to expire after one minute.
# cache = ActiveSupport::Cache::MemoryStore.new(:expires_in => 1.minute)
#
# cache.write("foo", "original value")
# val_1 = nil
# val_2 = nil
# sleep 60
#
# Thread.new do
# val_1 = cache.fetch("foo", :race_condition_ttl => 10) do
# sleep 1
# "new value 1"
# end
# end
#
# Thread.new do
# val_2 = cache.fetch("foo", :race_condition_ttl => 10) do
# "new value 2"
# end
# end
#
# # val_1 => "new value 1"
# # val_2 => "original value"
# # sleep 10 # First thread extend the life of cache by another 10 seconds
# # cache.fetch("foo") => "new value 1"
#
# Other options will be handled by the specific cache store implementation.
# Internally, #fetch calls #read, and calls #write on a cache miss.
# Internally, #fetch calls #read_entry, and calls #write_entry on a cache miss.
# +options+ will be passed to the #read and #write calls.
#
# For example, MemCacheStore's #write method supports the +:expires_in+
# option, which tells the memcached server to automatically expire the
# cache item after a certain period. We can use this option with #fetch
# too:
# For example, MemCacheStore's #write method supports the +:raw+
# option, which tells the memcached server to store all values as strings.
# We can use this option with #fetch too:
#
# cache = ActiveSupport::Cache::MemCacheStore.new
# cache.fetch("foo", :force => true, :expires_in => 5.seconds) do
# "bar"
# cache.fetch("foo", :force => true, :raw => true) do
# :bar
# end
# cache.fetch("foo") # => "bar"
# sleep(6)
# cache.fetch("foo") # => nil
def fetch(key, options = {})
@logger_off = true
if !options[:force] && value = read(key, options)
@logger_off = false
log("hit", key, options)
value
elsif block_given?
@logger_off = false
log("miss", key, options)
def fetch(name, options = nil)
if block_given?
options = merged_options(options)
key = namespaced_key(name, options)
unless options[:force]
entry = instrument(:read, name, options) do |payload|
payload[:super_operation] = :fetch if payload
read_entry(key, options)
end
end
if entry && entry.expired?
race_ttl = options[:race_condition_ttl].to_f
if race_ttl and Time.now.to_f - entry.expires_at <= race_ttl
entry.expires_at = Time.now + race_ttl
write_entry(key, entry, :expires_in => race_ttl * 2)
else
delete_entry(key, options)
end
entry = nil
end
value = nil
ms = Benchmark.ms { value = yield }
@logger_off = true
write(key, value, options)
@logger_off = false
log('write (will save %.2fms)' % ms, key, nil)
value
if entry
instrument(:fetch_hit, name, options) { |payload| }
entry.value
else
result = instrument(:generate, name, options) do |payload|
yield
end
write(name, result, options)
result
end
else
read(name, options)
end
end
@@ -175,73 +308,330 @@ module ActiveSupport
# the cache with the given key, then that data is returned. Otherwise,
# nil is returned.
#
# You may also specify additional options via the +options+ argument.
# The specific cache store implementation will decide what to do with
# +options+.
def read(key, options = nil)
log("read", key, options)
end
# Writes the given value to the cache, with the given key.
#
# You may also specify additional options via the +options+ argument.
# The specific cache store implementation will decide what to do with
# +options+.
#
# For example, MemCacheStore supports the +:expires_in+ option, which
# tells the memcached server to automatically expire the cache item after
# a certain period:
#
# cache = ActiveSupport::Cache::MemCacheStore.new
# cache.write("foo", "bar", :expires_in => 5.seconds)
# cache.read("foo") # => "bar"
# sleep(6)
# cache.read("foo") # => nil
def write(key, value, options = nil)
log("write", key, options)
end
def delete(key, options = nil)
log("delete", key, options)
end
def delete_matched(matcher, options = nil)
log("delete matched", matcher.inspect, options)
end
def exist?(key, options = nil)
log("exist?", key, options)
end
def increment(key, amount = 1)
log("incrementing", key, amount)
if num = read(key)
write(key, num + amount)
else
nil
# Options are passed to the underlying cache implementation.
def read(name, options = nil)
options = merged_options(options)
key = namespaced_key(name, options)
instrument(:read, name, options) do |payload|
entry = read_entry(key, options)
if entry
if entry.expired?
delete_entry(key, options)
payload[:hit] = false if payload
nil
else
payload[:hit] = true if payload
entry.value
end
else
payload[:hit] = false if payload
nil
end
end
end
def decrement(key, amount = 1)
log("decrementing", key, amount)
if num = read(key)
write(key, num - amount)
# Read multiple values at once from the cache. Options can be passed
# in the last argument.
#
# Some cache implementation may optimize this method.
#
# Returns a hash mapping the names provided to the values found.
def read_multi(*names)
options = names.extract_options!
options = merged_options(options)
results = {}
names.each do |name|
key = namespaced_key(name, options)
entry = read_entry(key, options)
if entry
if entry.expired?
delete_entry(key, options)
else
results[name] = entry.value
end
end
end
results
end
# Writes the value to the cache, with the key.
#
# Options are passed to the underlying cache implementation.
def write(name, value, options = nil)
options = merged_options(options)
instrument(:write, name, options) do |payload|
entry = Entry.new(value, options)
write_entry(namespaced_key(name, options), entry, options)
end
end
# Deletes an entry in the cache. Returns +true+ if an entry is deleted.
#
# Options are passed to the underlying cache implementation.
def delete(name, options = nil)
options = merged_options(options)
instrument(:delete, name) do |payload|
delete_entry(namespaced_key(name, options), options)
end
end
# Return true if the cache contains an entry for the given key.
#
# Options are passed to the underlying cache implementation.
def exist?(name, options = nil)
options = merged_options(options)
instrument(:exist?, name) do |payload|
entry = read_entry(namespaced_key(name, options), options)
if entry && !entry.expired?
true
else
false
end
end
end
# Delete all entries with keys matching the pattern.
#
# Options are passed to the underlying cache implementation.
#
# All implementations may not support this method.
def delete_matched(matcher, options = nil)
raise NotImplementedError.new("#{self.class.name} does not support delete_matched")
end
# Increment an integer value in the cache.
#
# Options are passed to the underlying cache implementation.
#
# All implementations may not support this method.
def increment(name, amount = 1, options = nil)
raise NotImplementedError.new("#{self.class.name} does not support increment")
end
# Increment an integer value in the cache.
#
# Options are passed to the underlying cache implementation.
#
# All implementations may not support this method.
def decrement(name, amount = 1, options = nil)
raise NotImplementedError.new("#{self.class.name} does not support decrement")
end
# Cleanup the cache by removing expired entries.
#
# Options are passed to the underlying cache implementation.
#
# All implementations may not support this method.
def cleanup(options = nil)
raise NotImplementedError.new("#{self.class.name} does not support cleanup")
end
# 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.
#
# All implementations may not support this method.
def clear(options = nil)
raise NotImplementedError.new("#{self.class.name} does not support clear")
end
protected
# Add the namespace defined in the options to a pattern designed to match keys.
# Implementations that support delete_matched should call this method to translate
# a pattern that matches names into one that matches namespaced keys.
def key_matcher(pattern, options)
prefix = options[:namespace].is_a?(Proc) ? options[:namespace].call : options[:namespace]
if prefix
source = pattern.source
if source.start_with?('^')
source = source[1, source.length]
else
source = ".*#{source[0, source.length]}"
end
Regexp.new("^#{Regexp.escape(prefix)}:#{source}", pattern.options)
else
pattern
end
end
# Read an entry from the cache implementation. Subclasses must implement this method.
def read_entry(key, options) # :nodoc:
raise NotImplementedError.new
end
# Write an entry to the cache implementation. Subclasses must implement this method.
def write_entry(key, entry, options) # :nodoc:
raise NotImplementedError.new
end
# Delete an entry from the cache implementation. Subclasses must implement this method.
def delete_entry(key, options) # :nodoc:
raise NotImplementedError.new
end
private
# Merge the default options with ones specific to a method call.
def merged_options(call_options) # :nodoc:
if call_options
options.merge(call_options)
else
options.dup
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
# called. If the key is a Hash, then keys will be sorted alphabetically.
def expanded_key(key) # :nodoc:
return key.cache_key.to_s if key.respond_to?(:cache_key)
case key
when Array
if key.size > 1
key = key.collect{|element| expanded_key(element)}
else
key = key.first
end
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.
def namespaced_key(key, options)
key = expanded_key(key)
namespace = options[:namespace] if options
prefix = namespace.is_a?(Proc) ? namespace.call : namespace
key = "#{prefix}:#{key}" if prefix
key
end
def instrument(operation, key, options = nil)
log(operation, key, options)
if self.class.instrument
payload = { :key => key }
payload.merge!(options) if options.is_a?(Hash)
ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload){ yield(payload) }
else
yield(nil)
end
end
def log(operation, key, options = nil)
return unless logger && logger.debug? && !silence?
logger.debug("Cache #{operation}: #{key}#{options.blank? ? "" : " (#{options.inspect})"}")
end
end
# Entry that is put into caches. It supports expiration time on entries and can compress values
# to save space in the cache.
class Entry
attr_reader :created_at, :expires_in
DEFAULT_COMPRESS_LIMIT = 16.kilobytes
class << self
# Create an entry with internal attributes set. This method is intended to be
# used by implementations that store cache entries in a native format instead
# of as serialized Ruby objects.
def create(raw_value, created_at, options = {})
entry = new(nil)
entry.instance_variable_set(:@value, raw_value)
entry.instance_variable_set(:@created_at, created_at.to_f)
entry.instance_variable_set(:@compressed, options[:compressed])
entry.instance_variable_set(:@expires_in, options[:expires_in])
entry
end
end
# Create a new cache entry for the specified value. Options supported are
# +:compress+, +:compress_threshold+, and +:expires_in+.
def initialize(value, options = {})
@compressed = false
@expires_in = options[:expires_in]
@expires_in = @expires_in.to_f if @expires_in
@created_at = Time.now.to_f
if value.nil?
@value = nil
else
nil
@value = Marshal.dump(value)
if should_compress?(@value, options)
@value = Zlib::Deflate.deflate(@value)
@compressed = true
end
end
end
# Get the raw value. This value may be serialized and compressed.
def raw_value
@value
end
# Get the value stored in the cache.
def value
# If the original value was exactly false @value is still true because
# it is marshalled and eventually compressed. Both operations yield
# strings.
if @value
# In rails 3.1 and earlier values in entries did not marshaled without
# options[:compress] and if it's Numeric.
# But after commit a263f377978fc07515b42808ebc1f7894fafaa3a
# all values in entries are marshalled. And after that code below expects
# that all values in entries will be marshaled (and will be strings).
# So here we need a check for old ones.
begin
Marshal.load(compressed? ? Zlib::Inflate.inflate(@value) : @value)
rescue TypeError
compressed? ? Zlib::Inflate.inflate(@value) : @value
end
end
end
def compressed?
@compressed
end
# Check if the entry is expired. The +expires_in+ parameter can override the
# value set when the entry was created.
def expired?
@expires_in && @created_at + @expires_in <= Time.now.to_f
end
# Set a new time when the entry will expire.
def expires_at=(time)
if time
@expires_in = time.to_f - @created_at
else
@expires_in = nil
end
end
# Seconds since the epoch when the entry will expire.
def expires_at
@expires_in ? @created_at + @expires_in : nil
end
# Returns the size of the cached value. This could be less than value.size
# if the data is compressed.
def size
if @value.nil?
0
else
@value.bytesize
end
end
private
def expires_in(options)
expires_in = options && options[:expires_in]
raise ":expires_in must be a number" if expires_in && !expires_in.is_a?(Numeric)
expires_in || 0
end
def log(operation, key, options)
logger.debug("Cache #{operation}: #{key}#{options ? " (#{options.inspect})" : ""}") if logger && !silence? && !logger_off?
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
end
end

View File

@@ -1,20 +0,0 @@
module ActiveSupport
module Cache
class CompressedMemCacheStore < MemCacheStore
def read(name, options = nil)
if value = super(name, (options || {}).merge(:raw => true))
if raw?(options)
value
else
Marshal.load(ActiveSupport::Gzip.decompress(value))
end
end
end
def write(name, value, options = nil)
value = ActiveSupport::Gzip.compress(Marshal.dump(value)) unless raw?(options)
super(name, value, (options || {}).merge(:raw => true))
end
end
end
end

View File

@@ -1,14 +0,0 @@
module ActiveSupport
module Cache
class DRbStore < MemoryStore #:nodoc:
attr_reader :address
def initialize(address = 'druby://localhost:9192')
require 'drb' unless defined?(DRbObject)
super()
@address = address
@data = DRbObject.new(nil, address)
end
end
end
end

View File

@@ -1,64 +1,170 @@
require 'active_support/core_ext/file/atomic'
require 'active_support/core_ext/string/conversions'
require 'active_support/core_ext/object/inclusion'
require 'rack/utils'
module ActiveSupport
module Cache
# 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.
class FileStore < Store
attr_reader :cache_path
def initialize(cache_path)
@cache_path = 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)
@cache_path = cache_path.to_s
extend Strategy::LocalCache
end
def read(name, options = nil)
super
File.open(real_file_path(name), 'rb') { |f| Marshal.load(f) } rescue nil
def clear(options = nil)
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
def write(name, value, options = nil)
super
ensure_cache_path(File.dirname(real_file_path(name)))
File.atomic_write(real_file_path(name), cache_path) { |f| Marshal.dump(value, f) }
value
rescue => e
logger.error "Couldn't create cache directory: #{name} (#{e.message})" if logger
def cleanup(options = nil)
options = merged_options(options)
each_key(options) do |key|
entry = read_entry(key, options)
delete_entry(key, options) if entry && entry.expired?
end
end
def delete(name, options = nil)
super
File.delete(real_file_path(name))
rescue SystemCallError => e
# If there's no cache, then there's nothing to complain about
end
def delete_matched(matcher, options = nil)
super
search_dir(@cache_path) do |f|
if f =~ matcher
begin
File.delete(f)
rescue SystemCallError => e
# If there's no cache, then there's nothing to complain about
end
def increment(name, amount = 1, options = nil)
file_name = key_file_path(namespaced_key(name, options))
lock_file(file_name) do
options = merged_options(options)
if num = read(name, options)
num = num.to_i + amount
write(name, num, options)
num
else
nil
end
end
end
def exist?(name, options = nil)
super
File.exist?(real_file_path(name))
def decrement(name, amount = 1, options = nil)
file_name = key_file_path(namespaced_key(name, options))
lock_file(file_name) do
options = merged_options(options)
if num = read(name, options)
num = num.to_i - amount
write(name, num, options)
num
else
nil
end
end
end
private
def real_file_path(name)
'%s/%s.cache' % [@cache_path, name.gsub('?', '.').gsub(':', '.')]
def delete_matched(matcher, options = nil)
options = merged_options(options)
instrument(:delete_matched, matcher.inspect) do
matcher = key_matcher(matcher, options)
search_dir(cache_path) do |path|
key = file_path_key(path)
delete_entry(key, options) if key.match(matcher)
end
end
end
protected
def read_entry(key, options)
file_name = key_file_path(key)
if File.exist?(file_name)
File.open(file_name) { |f| Marshal.load(f) }
end
rescue
nil
end
def write_entry(key, entry, options)
file_name = key_file_path(key)
ensure_cache_path(File.dirname(file_name))
File.atomic_write(file_name, cache_path) {|f| Marshal.dump(entry, f)}
true
end
def delete_entry(key, options)
file_name = key_file_path(key)
if File.exist?(file_name)
begin
File.delete(file_name)
delete_empty_directories(File.dirname(file_name))
true
rescue => e
# Just in case the error was caused by another process deleting the file first.
raise e if File.exist?(file_name)
false
end
end
end
private
# Lock a file for a block so only one process can modify it at a time.
def lock_file(file_name, &block) # :nodoc:
if File.exist?(file_name)
File.open(file_name, 'r+') do |f|
begin
f.flock File::LOCK_EX
yield
ensure
f.flock File::LOCK_UN
end
end
else
yield
end
end
# Translate a key into a file path.
def key_file_path(key)
fname = Rack::Utils.escape(key)
hash = Zlib.adler32(fname)
hash, dir_1 = hash.divmod(0x1000)
dir_2 = hash.modulo(0x1000)
fname_paths = []
# Make sure file name doesn't exceed file system limits.
begin
fname_paths << fname[0, FILENAME_MAX_SIZE]
fname = fname[FILENAME_MAX_SIZE..-1]
end until fname.blank?
File.join(cache_path, DIR_FORMATTER % dir_1, DIR_FORMATTER % dir_2, *fname_paths)
end
# Translate a file path into a key.
def file_path_key(path)
fname = path[cache_path.size, path.size].split(File::SEPARATOR, 4).last
Rack::Utils.unescape(fname)
end
# Delete empty directories in the cache.
def delete_empty_directories(dir)
return if dir == cache_path
if Dir.entries(dir).reject{|f| f.in?(EXCLUDED_DIRS)}.empty?
File.delete(dir) rescue nil
delete_empty_directories(File.dirname(dir))
end
end
# Make sure a file path's directories exist.
def ensure_cache_path(path)
FileUtils.makedirs(path) unless File.exist?(path)
end
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)

View File

@@ -1,19 +1,27 @@
require 'memcache'
begin
require 'memcache'
rescue LoadError => e
$stderr.puts "You don't have memcache-client installed in your application. Please add it to your Gemfile and run bundle install"
raise e
end
require 'digest/md5'
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.
#
# Special features:
# - Clustering and load balancing. One can specify multiple memcached servers,
# and MemCacheStore will load balance between all available servers. If a
# server goes down, then MemCacheStore will ignore it until it goes back
# online.
# - Time-based expiry support. See #write and the +:expires_in+ option.
# - Per-request in memory cache for all communication with the MemCache server(s).
# 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.
class MemCacheStore < Store
module Response # :nodoc:
STORED = "STORED\r\n"
@@ -23,10 +31,12 @@ module ActiveSupport
DELETED = "DELETED\r\n"
end
ESCAPE_KEY_CHARS = /[\x00-\x20%\x7F-\xFF]/n
def self.build_mem_cache(*addresses)
addresses = addresses.flatten
options = addresses.extract_options!
addresses = ["localhost"] if addresses.empty?
addresses = ["localhost:11211"] if addresses.empty?
MemCache.new(addresses, options)
end
@@ -44,100 +54,153 @@ module ActiveSupport
# require 'memcached' # gem install memcached; uses C bindings to libmemcached
# ActiveSupport::Cache::MemCacheStore.new(Memcached::Rails.new("localhost:11211"))
def initialize(*addresses)
addresses = addresses.flatten
options = addresses.extract_options!
super(options)
if addresses.first.respond_to?(:get)
@data = addresses.first
else
@data = self.class.build_mem_cache(*addresses)
mem_cache_options = options.dup
UNIVERSAL_OPTIONS.each{|name| mem_cache_options.delete(name)}
@data = self.class.build_mem_cache(*(addresses + [mem_cache_options]))
end
extend Strategy::LocalCache
extend LocalCacheWithRaw
end
# Reads multiple keys from the cache.
def read_multi(*keys)
@data.get_multi keys
# Reads multiple values from the cache using a single call to the
# servers for all keys. Options can be passed in the last argument.
def read_multi(*names)
options = names.extract_options!
options = merged_options(options)
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|
entry = deserialize_entry(value)
values[keys_to_names[key]] = entry.value unless entry.expired?
end
values
end
def read(key, options = nil) # :nodoc:
super
@data.get(key, raw?(options))
rescue MemCache::MemCacheError => e
logger.error("MemCacheError (#{e}): #{e.message}")
nil
end
# Writes a value to the cache.
#
# Possible options:
# - +:unless_exist+ - set to true if you don't want to update the cache
# if the key is already set.
# - +:expires_in+ - the number of seconds that this value may stay in
# the cache. See ActiveSupport::Cache::Store#write for an example.
def write(key, value, options = nil)
super
method = options && options[:unless_exist] ? :add : :set
# memcache-client will break the connection if you send it an integer
# in raw mode, so we convert it to a string to be sure it continues working.
value = value.to_s if raw?(options)
response = @data.send(method, key, value, expires_in(options), raw?(options))
response == Response::STORED
rescue MemCache::MemCacheError => e
logger.error("MemCacheError (#{e}): #{e.message}")
false
end
def delete(key, options = nil) # :nodoc:
super
response = @data.delete(key, expires_in(options))
response == Response::DELETED
rescue MemCache::MemCacheError => e
logger.error("MemCacheError (#{e}): #{e.message}")
false
end
def exist?(key, options = nil) # :nodoc:
# Doesn't call super, cause exist? in memcache is in fact a read
# But who cares? Reading is very fast anyway
# Local cache is checked first, if it doesn't know then memcache itself is read from
!read(key, options).nil?
end
def increment(key, amount = 1) # :nodoc:
log("incrementing", key, amount)
response = @data.incr(key, amount)
response == Response::NOT_FOUND ? nil : response
# Increment a cached value. This method uses the memcached incr atomic
# operator and can only be used on values written with the :raw option.
# Calling it on a value not stored with :raw will initialize that value
# to zero.
def increment(name, amount = 1, options = nil) # :nodoc:
options = merged_options(options)
response = instrument(:increment, name, :amount => amount) do
@data.incr(escape_key(namespaced_key(name, options)), amount)
end
response == Response::NOT_FOUND ? nil : response.to_i
rescue MemCache::MemCacheError
nil
end
def decrement(key, amount = 1) # :nodoc:
log("decrement", key, amount)
response = @data.decr(key, amount)
response == Response::NOT_FOUND ? nil : response
# Decrement a cached value. This method uses the memcached decr atomic
# operator and can only be used on values written with the :raw option.
# Calling it on a value not stored with :raw will initialize that value
# to zero.
def decrement(name, amount = 1, options = nil) # :nodoc:
options = merged_options(options)
response = instrument(:decrement, name, :amount => amount) do
@data.decr(escape_key(namespaced_key(name, options)), amount)
end
response == Response::NOT_FOUND ? nil : response.to_i
rescue MemCache::MemCacheError
nil
end
def delete_matched(matcher, options = nil) # :nodoc:
# don't do any local caching at present, just pass
# through and let the error happen
super
raise "Not supported by Memcache"
end
def clear
# Clear the entire cache on all memcached servers. This method should
# be used with care when shared cache is being used.
def clear(options = nil)
@data.flush_all
end
# Get the statistics from the memcached servers.
def stats
@data.stats
end
private
def raw?(options)
options && options[:raw]
protected
# Read an entry from the cache.
def read_entry(key, options) # :nodoc:
deserialize_entry(@data.get(escape_key(key), true))
rescue MemCache::MemCacheError => e
logger.error("MemCacheError (#{e}): #{e.message}") if logger
nil
end
# Write an entry to the cache.
def write_entry(key, entry, options) # :nodoc:
method = options && options[:unless_exist] ? :add : :set
value = options[:raw] ? entry.value.to_s : entry
expires_in = options[:expires_in].to_i
if expires_in > 0 && !options[:raw]
# Set the memcache expire a few minutes in the future to support race condition ttls on read
expires_in += 5.minutes
end
response = @data.send(method, escape_key(key), value, expires_in, options[:raw])
response == Response::STORED
rescue MemCache::MemCacheError => e
logger.error("MemCacheError (#{e}): #{e.message}") if logger
false
end
# Delete an entry from the cache.
def delete_entry(key, options) # :nodoc:
response = @data.delete(escape_key(key))
response == Response::DELETED
rescue MemCache::MemCacheError => e
logger.error("MemCacheError (#{e}): #{e.message}") if logger
false
end
private
# Memcache keys are binaries. So we need to force their encoding to binary
# before applying the regular expression to ensure we are escaping all
# characters properly.
def escape_key(key)
key = key.to_s.dup
key = key.force_encoding("BINARY") if key.encoding_aware?
key = key.gsub(ESCAPE_KEY_CHARS){ |match| "%#{match.getbyte(0).to_s(16).upcase}" }
key = "#{key[0, 213]}:md5:#{Digest::MD5.hexdigest(key)}" if key.size > 250
key
end
def deserialize_entry(raw_value)
if raw_value
entry = Marshal.load(raw_value) rescue raw_value
entry.is_a?(Entry) ? entry : Entry.new(entry)
else
nil
end
end
# Provide support for raw values in the local cache strategy.
module LocalCacheWithRaw # :nodoc:
protected
def read_entry(key, options)
entry = super
if options[:raw] && local_cache && entry
entry = deserialize_entry(entry.value)
end
entry
end
def write_entry(key, entry, options) # :nodoc:
retval = super
if options[:raw] && local_cache && retval
raw_entry = Entry.new(entry.value.to_s)
raw_entry.expires_at = entry.expires_at
local_cache.write_entry(key, raw_entry, options)
end
retval
end
end
end
end
end

View File

@@ -1,58 +1,159 @@
require 'monitor'
module ActiveSupport
module Cache
# A cache store implementation which stores everything into memory in the
# same process. If you're running multiple Ruby on Rails server processes
# (which is the case if you're using mongrel_cluster or Phusion Passenger),
# then this means that your Rails server process instances won't be able
# to share cache data with each other. If your application never performs
# manual cache item expiry (e.g. when you're using generational cache keys),
# then using MemoryStore is ok. Otherwise, consider carefully whether you
# should be using this cache store.
# then this means that Rails server process instances won't be able
# to share cache data with each other and this may not be the most
# appropriate cache in that scenario.
#
# MemoryStore is not only able to store strings, but also arbitrary Ruby
# objects.
# This cache has a bounded size specified by the :size options to the
# initializer (default is 32Mb). When the cache exceeds the allotted size,
# a cleanup will occur which tries to prune the cache down to three quarters
# of the maximum size by removing the least recently used entries.
#
# MemoryStore is not thread-safe. Use SynchronizedMemoryStore instead
# if you need thread-safety.
# MemoryStore is thread-safe.
class MemoryStore < Store
def initialize
def initialize(options = nil)
options ||= {}
super(options)
@data = {}
@key_access = {}
@max_size = options[:size] || 32.megabytes
@max_prune_time = options[:max_prune_time] || 2
@cache_size = 0
@monitor = Monitor.new
@pruning = false
end
def read_multi(*names)
results = {}
names.each { |n| results[n] = read(n) }
results
def clear(options = nil)
synchronize do
@data.clear
@key_access.clear
@cache_size = 0
end
end
def read(name, options = nil)
super
@data[name]
def cleanup(options = nil)
options = merged_options(options)
instrument(:cleanup, :size => @data.size) do
keys = synchronize{ @data.keys }
keys.each do |key|
entry = @data[key]
delete_entry(key, options) if entry && entry.expired?
end
end
end
def write(name, value, options = nil)
super
@data[name] = value.freeze
# To ensure entries fit within the specified memory prune the cache by removing the least
# recently accessed entries.
def prune(target_size, max_time = nil)
return if pruning?
@pruning = true
begin
start_time = Time.now
cleanup
instrument(:prune, target_size, :from => @cache_size) do
keys = synchronize{ @key_access.keys.sort{|a,b| @key_access[a].to_f <=> @key_access[b].to_f} }
keys.each do |key|
delete_entry(key, options)
return if @cache_size <= target_size || (max_time && Time.now - start_time > max_time)
end
end
ensure
@pruning = false
end
end
def delete(name, options = nil)
super
@data.delete(name)
# Returns true if the cache is currently being pruned.
def pruning?
@pruning
end
# Increment an integer value in the cache.
def increment(name, amount = 1, options = nil)
synchronize do
options = merged_options(options)
if num = read(name, options)
num = num.to_i + amount
write(name, num, options)
num
else
nil
end
end
end
# Decrement an integer value in the cache.
def decrement(name, amount = 1, options = nil)
synchronize do
options = merged_options(options)
if num = read(name, options)
num = num.to_i - amount
write(name, num, options)
num
else
nil
end
end
end
def delete_matched(matcher, options = nil)
super
@data.delete_if { |k,v| k =~ matcher }
options = merged_options(options)
instrument(:delete_matched, matcher.inspect) do
matcher = key_matcher(matcher, options)
keys = synchronize { @data.keys }
keys.each do |key|
delete_entry(key, options) if key.match(matcher)
end
end
end
def exist?(name, options = nil)
super
@data.has_key?(name)
def inspect # :nodoc:
"<##{self.class.name} entries=#{@data.size}, size=#{@cache_size}, options=#{@options.inspect}>"
end
def clear
@data.clear
# Synchronize calls to the cache. This should be called wherever the underlying cache implementation
# is not thread safe.
def synchronize(&block) # :nodoc:
@monitor.synchronize(&block)
end
protected
def read_entry(key, options) # :nodoc:
entry = @data[key]
synchronize do
if entry
@key_access[key] = Time.now.to_f
else
@key_access.delete(key)
end
end
entry
end
def write_entry(key, entry, options) # :nodoc:
synchronize do
old_entry = @data[key]
@cache_size -= old_entry.size if old_entry
@cache_size += entry.size
@key_access[key] = Time.now.to_f
@data[key] = entry
prune(@max_size * 0.75, @max_prune_time) if @cache_size > @max_size
true
end
end
def delete_entry(key, options) # :nodoc:
synchronize do
@key_access.delete(key)
entry = @data.delete(key)
@cache_size -= entry.size if entry
!!entry
end
end
end
end
end

View File

@@ -0,0 +1,44 @@
module ActiveSupport
module Cache
# A cache store implementation which doesn't actually store anything. Useful in
# development and test environments where you don't want caching turned on but
# need to go through the caching interface.
#
# This cache does implement the local cache strategy, so values will actually
# be cached inside blocks that utilize this strategy. See
# ActiveSupport::Cache::Strategy::LocalCache for more details.
class NullStore < Store
def initialize(options = nil)
super(options)
extend Strategy::LocalCache
end
def clear(options = nil)
end
def cleanup(options = nil)
end
def increment(name, amount = 1, options = nil)
end
def decrement(name, amount = 1, options = nil)
end
def delete_matched(matcher, options = nil)
end
protected
def read_entry(key, options) # :nodoc:
end
def write_entry(key, entry, options) # :nodoc:
true
end
def delete_entry(key, options) # :nodoc:
false
end
end
end
end

View File

@@ -1,103 +1,168 @@
require 'active_support/core_ext/object/duplicable'
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
# duration of a block. Repeated calls to the cache for the same key will hit the
# in-memory cache for faster access.
module LocalCache
# this allows caching of the fact that there is nothing in the remote cache
NULL = 'remote_cache_store:null'
def with_local_cache
Thread.current[thread_local_key] = MemoryStore.new
yield
ensure
Thread.current[thread_local_key] = nil
end
def middleware
@middleware ||= begin
klass = Class.new
klass.class_eval(<<-EOS, __FILE__, __LINE__ + 1)
def initialize(app)
@app = app
end
def call(env)
Thread.current[:#{thread_local_key}] = MemoryStore.new
@app.call(env)
ensure
Thread.current[:#{thread_local_key}] = nil
end
EOS
klass
# Simple memory backed cache. This cache is not thread safe and is intended only
# for serving as a temporary memory cache for a single thread.
class LocalStore < Store
def initialize
super
@data = {}
end
end
def read(key, options = nil)
value = local_cache && local_cache.read(key)
if value == NULL
nil
elsif value.nil?
value = super
local_cache.mute { local_cache.write(key, value || NULL) } if local_cache
value.duplicable? ? value.dup : value
else
# forcing the value to be immutable
value.duplicable? ? value.dup : value
# Don't allow synchronizing since it isn't thread safe,
def synchronize # :nodoc:
yield
end
end
def write(key, value, options = nil)
value = value.to_s if respond_to?(:raw?) && raw?(options)
local_cache.mute { local_cache.write(key, value || NULL) } if local_cache
super
end
def clear(options = nil)
@data.clear
end
def delete(key, options = nil)
local_cache.mute { local_cache.write(key, NULL) } if local_cache
super
end
def read_entry(key, options)
@data[key]
end
def exist(key, options = nil)
value = local_cache.read(key) if local_cache
if value == NULL
false
elsif value
def write_entry(key, value, options)
@data[key] = value
true
else
end
def delete_entry(key, options)
!!@data.delete(key)
end
end
# Use a local cache for the duration of block.
def with_local_cache
save_val = Thread.current[thread_local_key]
begin
Thread.current[thread_local_key] = LocalStore.new
yield
ensure
Thread.current[thread_local_key] = save_val
end
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(name, thread_local_key)
@name = name
@thread_local_key = thread_local_key
@app = nil
end
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
end
def cleanup(options = nil) # :nodoc:
local_cache.clear(options) if local_cache
super
end
def increment(name, amount = 1, options = nil) # :nodoc:
value = bypass_local_cache{super}
if local_cache
local_cache.mute do
if value
local_cache.write(name, value, options)
else
local_cache.delete(name, options)
end
end
end
value
end
def decrement(name, amount = 1, options = nil) # :nodoc:
value = bypass_local_cache{super}
if local_cache
local_cache.mute do
if value
local_cache.write(name, value, options)
else
local_cache.delete(name, options)
end
end
end
value
end
protected
def read_entry(key, options) # :nodoc:
if local_cache
entry = local_cache.read_entry(key, options)
unless entry
entry = super
local_cache.write_entry(key, entry, options)
end
entry
else
super
end
end
def write_entry(key, entry, options) # :nodoc:
local_cache.write_entry(key, entry, options) if local_cache
super
end
end
def increment(key, amount = 1)
if value = super
local_cache.mute { local_cache.write(key, value.to_s) } if local_cache
value
else
nil
def delete_entry(key, options) # :nodoc:
local_cache.delete_entry(key, options) if local_cache
super
end
end
def decrement(key, amount = 1)
if value = super
local_cache.mute { local_cache.write(key, value.to_s) } if local_cache
value
else
nil
end
end
def clear
local_cache.clear if local_cache
super
end
private
def thread_local_key
@thread_local_key ||= "#{self.class.name.underscore}_local_cache".gsub("/", "_").to_sym
@thread_local_key ||= "#{self.class.name.underscore}_local_cache_#{object_id}".gsub(/[\/-]/, '_').to_sym
end
def local_cache
Thread.current[thread_local_key]
end
def bypass_local_cache
save_cache = Thread.current[thread_local_key]
begin
Thread.current[thread_local_key] = nil
yield
ensure
Thread.current[thread_local_key] = save_cache
end
end
end
end
end

View File

@@ -1,47 +0,0 @@
module ActiveSupport
module Cache
# Like MemoryStore, but thread-safe.
class SynchronizedMemoryStore < MemoryStore
def initialize
super
@guard = Monitor.new
end
def fetch(key, options = {})
@guard.synchronize { super }
end
def read(name, options = nil)
@guard.synchronize { super }
end
def write(name, value, options = nil)
@guard.synchronize { super }
end
def delete(name, options = nil)
@guard.synchronize { super }
end
def delete_matched(matcher, options = nil)
@guard.synchronize { super }
end
def exist?(name,options = nil)
@guard.synchronize { super }
end
def increment(key, amount = 1)
@guard.synchronize { super }
end
def decrement(key, amount = 1)
@guard.synchronize { super }
end
def clear
@guard.synchronize { super }
end
end
end
end

View File

@@ -1,279 +1,626 @@
require 'active_support/concern'
require 'active_support/descendants_tracker'
require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/kernel/reporting'
require 'active_support/core_ext/kernel/singleton_class'
require 'active_support/core_ext/object/inclusion'
module ActiveSupport
# Callbacks are 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
# Three kinds of callbacks are supported: before callbacks, run before a certain event;
# after callbacks, run after the event; and around callbacks, blocks that surround the
# event, triggering it when they yield. Callback code can be contained in instance
# methods, procs or lambdas, or callback objects that respond to certain predetermined
# methods. See +ClassMethods.set_callback+ for details.
#
# ==== Example
#
# class Record
# include ActiveSupport::Callbacks
# define_callbacks :save
#
# define_callbacks :before_save, :after_save
# def save
# run_callbacks :save do
# puts "- save"
# end
# end
# end
#
# class ConfigStorage < Storage
# before_save :saving_message
# class PersonRecord < Record
# set_callback :save, :before, :saving_message
# def saving_message
# puts "saving..."
# end
#
# after_save do |object|
# set_callback :save, :after do |object|
# puts "saved"
# end
#
# def save
# run_callbacks(:before_save)
# puts "- save"
# run_callbacks(:after_save)
# end
# end
#
# config = ConfigStorage.new
# config.save
# person = PersonRecord.new
# person.save
#
# Output:
# saving...
# - save
# saved
#
# Callbacks from parent classes are inherited.
#
# Example:
# class Storage
# include ActiveSupport::Callbacks
#
# define_callbacks :before_save, :after_save
#
# before_save :prepare
# def prepare
# puts "preparing save"
# end
# end
#
# class ConfigStorage < Storage
# before_save :saving_message
# def saving_message
# puts "saving..."
# end
#
# after_save do |object|
# puts "saved"
# end
#
# def save
# run_callbacks(:before_save)
# puts "- save"
# run_callbacks(:after_save)
# end
# end
#
# config = ConfigStorage.new
# config.save
#
# Output:
# preparing save
# saving...
# - save
# saved
module Callbacks
class CallbackChain < Array
def self.build(kind, *methods, &block)
methods, options = extract_options(*methods, &block)
methods.map! { |method| Callback.new(kind, method, options) }
new(methods)
end
extend Concern
def run(object, options = {}, &terminator)
enumerator = options[:enumerator] || :each
unless block_given?
send(enumerator) { |callback| callback.call(object) }
else
send(enumerator) do |callback|
result = callback.call(object)
break result if terminator.call(result, object)
end
end
end
# TODO: Decompose into more Array like behavior
def replace_or_append!(chain)
if index = index(chain)
self[index] = chain
else
self << chain
end
self
end
def find(callback, &block)
select { |c| c == callback && (!block_given? || yield(c)) }.first
end
def delete(callback)
super(callback.is_a?(Callback) ? callback : find(callback))
end
private
def self.extract_options(*methods, &block)
methods.flatten!
options = methods.extract_options!
methods << block if block_given?
return methods, options
end
def extract_options(*methods, &block)
self.class.extract_options(*methods, &block)
end
included do
extend ActiveSupport::DescendantsTracker
end
class Callback
attr_reader :kind, :method, :identifier, :options
# 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
def initialize(kind, method, options = {})
@kind = kind
@method = method
@identifier = options[:identifier]
@options = options
private
# A hook invoked everytime a before callback is halted.
# This can be overriden in AS::Callback implementors in order
# to provide better debugging/logging.
def halted_callback_hook(filter)
end
class Callback #:nodoc:#
@@_callback_sequence = 0
attr_accessor :chain, :filter, :kind, :options, :per_key, :klass, :raw_filter
def initialize(chain, filter, kind, options, klass)
@chain, @kind, @klass = chain, kind, klass
normalize_options!(options)
@per_key = options.delete(:per_key)
@raw_filter, @options = filter, options
@filter = _compile_filter(filter)
@compiled_options = _compile_options(options)
@callback_id = next_id
_compile_per_key_options
end
def ==(other)
case other
when Callback
(self.identifier && self.identifier == other.identifier) || self.method == other.method
else
(self.identifier && self.identifier == other) || self.method == other
end
def clone(chain, klass)
obj = super()
obj.chain = chain
obj.klass = klass
obj.per_key = @per_key.dup
obj.options = @options.dup
obj.per_key[:if] = @per_key[:if].dup
obj.per_key[:unless] = @per_key[:unless].dup
obj.options[:if] = @options[:if].dup
obj.options[:unless] = @options[:unless].dup
obj
end
def eql?(other)
self == other
def normalize_options!(options)
options[:if] = Array.wrap(options[:if])
options[:unless] = Array.wrap(options[:unless])
options[:per_key] ||= {}
options[:per_key][:if] = Array.wrap(options[:per_key][:if])
options[:per_key][:unless] = Array.wrap(options[:per_key][:unless])
end
def dup
self.class.new(@kind, @method, @options.dup)
def name
chain.name
end
def hash
if @identifier
@identifier.hash
else
@method.hash
end
def next_id
@@_callback_sequence += 1
end
def call(*args, &block)
evaluate_method(method, *args, &block) if should_run_callback?(*args)
rescue LocalJumpError
raise ArgumentError,
"Cannot yield from a Proc type filter. The Proc must take two " +
"arguments and execute #call on the second argument."
def matches?(_kind, _filter)
@kind == _kind && @filter == _filter
end
private
def evaluate_method(method, *args, &block)
case method
when Symbol
object = args.shift
object.send(method, *args, &block)
when String
eval(method, args.first.instance_eval { binding })
when Proc, Method
method.call(*args, &block)
else
if method.respond_to?(kind)
method.send(kind, *args, &block)
else
raise ArgumentError,
"Callbacks must be a symbol denoting the method to call, a string to be evaluated, " +
"a block to be invoked, or an object responding to the callback method."
def _update_filter(filter_options, new_options)
filter_options[:if].push(new_options[:unless]) if new_options.key?(:unless)
filter_options[:unless].push(new_options[:if]) if new_options.key?(:if)
end
def recompile!(_options, _per_key)
_update_filter(self.options, _options)
_update_filter(self.per_key, _per_key)
@callback_id = next_id
@filter = _compile_filter(@raw_filter)
@compiled_options = _compile_options(@options)
_compile_per_key_options
end
def _compile_per_key_options
key_options = _compile_options(@per_key)
@klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
def _one_time_conditions_valid_#{@callback_id}?
true if #{key_options}
end
RUBY_EVAL
end
# This will supply contents for before and around filters, and no
# contents for after filters (for the forward pass).
def start(key=nil, object=nil)
return if key && !object.send("_one_time_conditions_valid_#{@callback_id}?")
# options[0] is the compiled form of supplied conditions
# options[1] is the "end" for the conditional
#
case @kind
when :before
# if condition # before_save :filter_name, :if => :condition
# filter_name
# end
<<-RUBY_EVAL
if !halted && #{@compiled_options}
# This double assignment is to prevent warnings in 1.9.3 as
# the `result` variable is not always used except if the
# terminator code refers to it.
result = result = #{@filter}
halted = (#{chain.config[:terminator]})
if halted
halted_callback_hook(#{@raw_filter.inspect.inspect})
end
end
RUBY_EVAL
when :around
# Compile around filters with conditions into proxy methods
# that contain the conditions.
#
# For `around_save :filter_name, :if => :condition':
#
# def _conditional_callback_save_17
# if condition
# filter_name do
# yield self
# end
# else
# yield self
# end
# end
#
name = "_conditional_callback_#{@kind}_#{next_id}"
@klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
def #{name}(halted)
if #{@compiled_options} && !halted
#{@filter} do
yield self
end
else
yield self
end
end
RUBY_EVAL
"#{name}(halted) do"
end
end
# This will supply contents for around and after filters, but not
# before filters (for the backward pass).
def end(key=nil, object=nil)
return if key && !object.send("_one_time_conditions_valid_#{@callback_id}?")
case @kind
when :after
# after_save :filter_name, :if => :condition
<<-RUBY_EVAL
if #{@compiled_options}
#{@filter}
end
RUBY_EVAL
when :around
<<-RUBY_EVAL
value
end
RUBY_EVAL
end
end
private
# Options support the same options as filters themselves (and support
# symbols, string, procs, and objects), so compile a conditional
# expression based on the options
def _compile_options(options)
conditions = ["true"]
unless options[:if].empty?
conditions << Array.wrap(_compile_filter(options[:if]))
end
unless options[:unless].empty?
conditions << Array.wrap(_compile_filter(options[:unless])).map {|f| "!#{f}"}
end
conditions.flatten.join(" && ")
end
# Filters support:
#
# Arrays:: Used in conditions. This is used to specify
# multiple conditions. Used internally to
# merge conditions from skip_* filters
# Symbols:: A method to call
# Strings:: Some content to evaluate
# Procs:: A proc to call with the object
# Objects:: An object with a before_foo method on it to call
#
# All of these objects are compiled into methods and handled
# the same after this point:
#
# Arrays:: Merged together into a single filter
# Symbols:: Already methods
# Strings:: class_eval'ed into methods
# Procs:: define_method'ed into methods
# Objects::
# a method is created that calls the before_foo method
# on the object.
#
def _compile_filter(filter)
method_name = "_callback_#{@kind}_#{next_id}"
case filter
when Array
filter.map {|f| _compile_filter(f)}
when Symbol
filter
when String
"(#{filter})"
when Proc
@klass.send(:define_method, method_name, &filter)
return method_name if filter.arity <= 0
method_name << (filter.arity == 1 ? "(self)" : " self, Proc.new ")
else
@klass.send(:define_method, "#{method_name}_object") { filter }
_normalize_legacy_filter(kind, filter)
scopes = Array.wrap(chain.config[:scope])
method_to_call = scopes.map{ |s| s.is_a?(Symbol) ? send(s) : s }.join("_")
@klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
def #{method_name}(&blk)
#{method_name}_object.send(:#{method_to_call}, self, &blk)
end
RUBY_EVAL
method_name
end
end
def _normalize_legacy_filter(kind, filter)
if !filter.respond_to?(kind) && filter.respond_to?(:filter)
filter.singleton_class.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
def #{kind}(context, &block) filter(context, &block) end
RUBY_EVAL
elsif filter.respond_to?(:before) && filter.respond_to?(:after) && kind == :around
def filter.around(context)
should_continue = before(context)
yield if should_continue
after(context)
end
end
def should_run_callback?(*args)
[options[:if]].flatten.compact.all? { |a| evaluate_method(a, *args) } &&
![options[:unless]].flatten.compact.any? { |a| evaluate_method(a, *args) }
end
end
end
def self.included(base)
base.extend ClassMethods
# An Array with a compile method
class CallbackChain < Array #:nodoc:#
attr_reader :name, :config
def initialize(name, config)
@name = name
@config = {
:terminator => "false",
:rescuable => false,
:scope => [ :kind ]
}.merge(config)
end
def compile(key=nil, object=nil)
method = []
method << "value = nil"
method << "halted = false"
each do |callback|
method << callback.start(key, object)
end
if config[:rescuable]
method << "rescued_error = nil"
method << "begin"
end
method << "value = yield if block_given? && !halted"
if config[:rescuable]
method << "rescue Exception => e"
method << "rescued_error = e"
method << "end"
end
reverse_each do |callback|
method << callback.end(key, object)
end
method << "raise rescued_error if rescued_error" if config[:rescuable]
method << "halted ? false : (block_given? ? value : true)"
method.compact.join("\n")
end
end
module ClassMethods
# Generate the internal runner method called by +run_callbacks+.
def __define_runner(symbol) #:nodoc:
runner_method = "_run_#{symbol}_callbacks"
unless private_method_defined?(runner_method)
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
def #{runner_method}(key = nil, &blk)
self.class.__run_callback(key, :#{symbol}, self, &blk)
end
private :#{runner_method}
RUBY_EVAL
end
end
# This method calls the callback method for the given key.
# If this called first time it creates a new callback method for the key,
# calculating which callbacks can be omitted because of per_key conditions.
#
def __run_callback(key, kind, object, &blk) #:nodoc:
name = __callback_runner_name(key, kind)
unless object.respond_to?(name, true)
str = object.send("_#{kind}_callbacks").compile(key, object)
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
def #{name}() #{str} end
protected :#{name}
RUBY_EVAL
end
object.send(name, &blk)
end
def __reset_runner(symbol)
name = __callback_runner_name(nil, symbol)
undef_method(name) if method_defined?(name)
end
def __callback_runner_name(key, kind)
"_run__#{self.name.hash.abs}__#{kind}__#{key.hash.abs}__callbacks"
end
# This is used internally to append, prepend and skip callbacks to the
# CallbackChain.
#
def __update_callbacks(name, filters = [], block = nil) #:nodoc:
type = filters.first.in?([:before, :after, :around]) ? filters.shift : :before
options = filters.last.is_a?(Hash) ? filters.pop : {}
filters.unshift(block) if block
([self] + ActiveSupport::DescendantsTracker.descendants(self)).reverse.each do |target|
chain = target.send("_#{name}_callbacks")
yield target, chain.dup, type, filters, options
target.__reset_runner(name)
end
end
# Install a callback for the given event.
#
# set_callback :save, :before, :before_meth
# set_callback :save, :after, :after_meth, :if => :condition
# set_callback :save, :around, lambda { |r| stuff; result = yield; stuff }
#
# 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+.
#
# 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,
# we convert :only and :except conditions into per-key conditions.
#
# before_filter :authenticate, :except => "index"
#
# becomes
#
# set_callback :process_action, :before, :authenticate, :per_key => {:unless => proc {|c| c.action_name == "index"}}
#
# Per-key conditions are evaluated only once per use of a given key.
# In the case of the above example, you would do:
#
# run_callbacks(:process_action, action_name) { ... dispatch stuff ... }
#
# In that case, each action_name would get its own compiled callback
# method that took into consideration the per_key conditions. This
# is a speed improvement for ActionPack.
#
def set_callback(name, *filter_list, &block)
mapped = nil
__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) }
end
options[:prepend] ? chain.unshift(*(mapped.reverse)) : chain.push(*mapped)
target.send("_#{name}_callbacks=", chain)
end
end
# Skip a previously set callback. Like +set_callback+, <tt>:if</tt> or <tt>:unless</tt>
# options may be passed in order to control when the callback is skipped.
#
# 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 |target, chain, type, filters, options|
filters.each do |filter|
filter = chain.find {|c| c.matches?(type, filter) }
if filter && options.any?
new_filter = filter.clone(chain, self)
chain.insert(chain.index(filter), new_filter)
new_filter.recompile!(options, options[:per_key] || {})
end
chain.delete(filter)
end
target.send("_#{name}_callbacks=", chain)
end
end
# 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").dup
callbacks.each { |c| chain.delete(c) }
target.send("_#{symbol}_callbacks=", chain)
target.__reset_runner(symbol)
end
self.send("_#{symbol}_callbacks=", callbacks.dup.clear)
__reset_runner(symbol)
end
# Define sets of events in the object lifecycle that support callbacks.
#
# define_callbacks :validate
# define_callbacks :initialize, :save, :destroy
#
# ===== Options
#
# * <tt>:terminator</tt> - Determines when a before filter will halt the callback
# chain, preventing following callbacks from being called and the event from being
# triggered. This is a string to be eval'ed. The result of the callback is available
# in the <tt>result</tt> variable.
#
# define_callbacks :validate, :terminator => "result == false"
#
# In this example, if any before validate callbacks returns +false+,
# other callbacks are not executed. Defaults to "false", meaning no value
# halts the chain.
#
# * <tt>:rescuable</tt> - By default, after filters are not executed if
# the given block or a before filter raises an error. By setting this option
# to <tt>true</tt> exception raised by given block is stored and after
# executing all the after callbacks the stored exception is raised.
#
# * <tt>: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
#
# def before_save(caller)
# puts 'Audit: before_save is called'
# end
# end
#
# class Account
# include ActiveSupport::Callbacks
#
# define_callbacks :save
# set_callback :save, :before, Audit.new
#
# 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
#
# 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.
#
# A declaration like
#
# define_callbacks :save, :scope => [:name]
#
# would call <tt>Audit#save</tt>.
#
def define_callbacks(*callbacks)
config = callbacks.last.is_a?(Hash) ? callbacks.pop : {}
callbacks.each do |callback|
class_eval <<-"end_eval"
def self.#{callback}(*methods, &block) # def self.before_save(*methods, &block)
callbacks = CallbackChain.build(:#{callback}, *methods, &block) # callbacks = CallbackChain.build(:before_save, *methods, &block)
@#{callback}_callbacks ||= CallbackChain.new # @before_save_callbacks ||= CallbackChain.new
@#{callback}_callbacks.concat callbacks # @before_save_callbacks.concat callbacks
end # end
#
def self.#{callback}_callback_chain # def self.before_save_callback_chain
@#{callback}_callbacks ||= CallbackChain.new # @before_save_callbacks ||= CallbackChain.new
#
if superclass.respond_to?(:#{callback}_callback_chain) # if superclass.respond_to?(:before_save_callback_chain)
CallbackChain.new( # CallbackChain.new(
superclass.#{callback}_callback_chain + # superclass.before_save_callback_chain +
@#{callback}_callbacks # @before_save_callbacks
) # )
else # else
@#{callback}_callbacks # @before_save_callbacks
end # end
end # end
end_eval
class_attribute "_#{callback}_callbacks"
send("_#{callback}_callbacks=", CallbackChain.new(callback, config))
__define_runner(callback)
end
end
end
# Runs all the callbacks defined for the given options.
#
# If a block is given it will be called after each callback receiving as arguments:
#
# * the result from the callback
# * the object which has the callback
#
# If the result from the block evaluates to false, the callback chain is stopped.
#
# Example:
# class Storage
# include ActiveSupport::Callbacks
#
# define_callbacks :before_save, :after_save
# end
#
# class ConfigStorage < Storage
# before_save :pass
# before_save :pass
# before_save :stop
# before_save :pass
#
# def pass
# puts "pass"
# end
#
# def stop
# puts "stop"
# return false
# end
#
# def save
# result = run_callbacks(:before_save) { |result, object| result == false }
# puts "- save" if result
# end
# end
#
# config = ConfigStorage.new
# config.save
#
# Output:
# pass
# pass
# stop
def run_callbacks(kind, options = {}, &block)
self.class.send("#{kind}_callback_chain").run(self, options, &block)
end
end
end

View File

@@ -1,3 +1,5 @@
require 'active_support/deprecation'
module ActiveSupport
# A typical module looks like this:
#
@@ -5,7 +7,7 @@ module ActiveSupport
# def self.included(base)
# base.extend ClassMethods
# base.class_eval do
# scope :disabled, -> { where(disabled: true) }
# scope :disabled, where(:disabled => true)
# end
# end
#
@@ -14,8 +16,7 @@ module ActiveSupport
# end
# end
#
# By using <tt>ActiveSupport::Concern</tt> the above module could instead be
# written as:
# By using <tt>ActiveSupport::Concern</tt> the above module could instead be written as:
#
# require 'active_support/concern'
#
@@ -23,7 +24,7 @@ module ActiveSupport
# extend ActiveSupport::Concern
#
# included do
# scope :disabled, -> { where(disabled: true) }
# scope :disabled, where(:disabled => true)
# end
#
# module ClassMethods
@@ -31,9 +32,8 @@ module ActiveSupport
# end
# end
#
# Moreover, it gracefully handles module dependencies. Given a +Foo+ module
# and a +Bar+ module which depends on the former, we would typically write the
# following:
# Moreover, it gracefully handles module dependencies. Given a +Foo+ module and a +Bar+
# module which depends on the former, we would typically write the following:
#
# module Foo
# def self.included(base)
@@ -56,11 +56,11 @@ module ActiveSupport
# include Bar # Bar is the module that Host really needs
# end
#
# But why should +Host+ care about +Bar+'s dependencies, namely +Foo+? We
# could try to hide these from +Host+ directly including +Foo+ in +Bar+:
# But why should +Host+ care about +Bar+'s dependencies, namely +Foo+? We could try to hide
# these from +Host+ directly including +Foo+ in +Bar+:
#
# module Bar
# include Foo
# include Foo
# def self.included(base)
# base.method_injected_by_foo
# end
@@ -70,17 +70,18 @@ module ActiveSupport
# include Bar
# end
#
# Unfortunately this won't work, since when +Foo+ is included, its <tt>base</tt>
# is the +Bar+ module, not the +Host+ class. With <tt>ActiveSupport::Concern</tt>,
# module dependencies are properly resolved:
# Unfortunately this won't work, since when +Foo+ is included, its <tt>base</tt> is the +Bar+ module,
# not the +Host+ class. With <tt>ActiveSupport::Concern</tt>, module dependencies are properly resolved:
#
# require 'active_support/concern'
#
# module Foo
# extend ActiveSupport::Concern
# included do
# def self.method_injected_by_foo
# ...
# class_eval do
# def self.method_injected_by_foo
# ...
# end
# end
# end
# end
@@ -97,38 +98,36 @@ module ActiveSupport
# class Host
# include Bar # works, Bar takes care now of its dependencies
# end
#
module Concern
class MultipleIncludedBlocks < StandardError #:nodoc:
def initialize
super "Cannot define multiple 'included' blocks for a Concern"
end
end
def self.extended(base) #:nodoc:
base.instance_variable_set(:@_dependencies, [])
def self.extended(base)
base.instance_variable_set("@_dependencies", [])
end
def append_features(base)
if base.instance_variable_defined?(:@_dependencies)
base.instance_variable_get(:@_dependencies) << self
if base.instance_variable_defined?("@_dependencies")
base.instance_variable_get("@_dependencies") << self
return false
else
return false if base < self
@_dependencies.each { |dep| base.send(:include, dep) }
super
base.extend const_get(:ClassMethods) if const_defined?(:ClassMethods)
base.class_eval(&@_included_block) if instance_variable_defined?(:@_included_block)
base.extend const_get("ClassMethods") if const_defined?("ClassMethods")
if const_defined?("InstanceMethods")
base.send :include, const_get("InstanceMethods")
ActiveSupport::Deprecation.warn "The InstanceMethods module inside ActiveSupport::Concern will be " \
"no longer included automatically. Please define instance methods directly in #{self} instead.", caller
end
base.class_eval(&@_included_block) if instance_variable_defined?("@_included_block")
end
end
def included(base = nil, &block)
if base.nil?
raise MultipleIncludedBlocks if instance_variable_defined?(:@_included_block)
@_included_block = block
else
super
end
end
end
end
end

View File

@@ -0,0 +1,90 @@
require 'active_support/concern'
require 'active_support/ordered_options'
require 'active_support/core_ext/kernel/singleton_class'
require 'active_support/core_ext/module/delegation'
require 'active_support/core_ext/array/extract_options'
module ActiveSupport
# 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)
end
# compiles reader methods so we don't have to go through method_missing
def self.compile_methods!(keys)
keys.reject { |m| method_defined?(m) }.each do |key|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{key}; _get(#{key.inspect}); end
RUBY
end
end
end
module ClassMethods
def 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)
options = names.extract_options!
names.each do |name|
reader, line = "def #{name}; config.#{name}; end", __LINE__
writer, line = "def #{name}=(value); config.#{name} = value; end", __LINE__
singleton_class.class_eval reader, __FILE__, line
singleton_class.class_eval writer, __FILE__, line
class_eval reader, __FILE__, line unless options[:instance_reader] == false
class_eval writer, __FILE__, line unless options[:instance_writer] == false
end
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 ||= self.class.config.inheritable_copy
end
end
end

View File

@@ -1,8 +1,3 @@
filenames = Dir["#{File.dirname(__FILE__)}/core_ext/*.rb"].sort.map do |path|
File.basename(path, '.rb')
Dir["#{File.dirname(__FILE__)}/core_ext/*.rb"].sort.each do |path|
require "active_support/core_ext/#{File.basename(path, '.rb')}"
end
# deprecated
filenames -= %w(blank)
filenames.each { |filename| require "active_support/core_ext/#{filename}" }

View File

@@ -1,15 +1,8 @@
require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/array/access'
require 'active_support/core_ext/array/uniq_by'
require 'active_support/core_ext/array/conversions'
require 'active_support/core_ext/array/extract_options'
require 'active_support/core_ext/array/grouping'
require 'active_support/core_ext/array/random_access'
require 'active_support/core_ext/array/wrapper'
class Array #:nodoc:
include ActiveSupport::CoreExtensions::Array::Access
include ActiveSupport::CoreExtensions::Array::Conversions
include ActiveSupport::CoreExtensions::Array::ExtractOptions
include ActiveSupport::CoreExtensions::Array::Grouping
include ActiveSupport::CoreExtensions::Array::RandomAccess
extend ActiveSupport::CoreExtensions::Array::Wrapper
end
require 'active_support/core_ext/array/prepend_and_append'

View File

@@ -1,53 +1,46 @@
module ActiveSupport #:nodoc:
module CoreExtensions #:nodoc:
module Array #:nodoc:
# Makes it easier to access parts of an array.
module Access
# Returns the tail of the array from +position+.
#
# %w( a b c d ).from(0) # => %w( a b c d )
# %w( a b c d ).from(2) # => %w( c d )
# %w( a b c d ).from(10) # => nil
# %w().from(0) # => nil
def from(position)
self[position..-1]
end
# Returns the beginning of the array up to +position+.
#
# %w( a b c d ).to(0) # => %w( a )
# %w( a b c d ).to(2) # => %w( a b c )
# %w( a b c d ).to(10) # => %w( a b c d )
# %w().to(0) # => %w()
def to(position)
self[0..position]
end
class Array
# Returns the tail of the array from +position+.
#
# %w( a b c d ).from(0) # => %w( a b c d )
# %w( a b c d ).from(2) # => %w( c d )
# %w( a b c d ).from(10) # => %w()
# %w().from(0) # => %w()
def from(position)
self[position, length] || []
end
# Equal to <tt>self[1]</tt>.
def second
self[1]
end
# Returns the beginning of the array up to +position+.
#
# %w( a b c d ).to(0) # => %w( a )
# %w( a b c d ).to(2) # => %w( a b c )
# %w( a b c d ).to(10) # => %w( a b c d )
# %w().to(0) # => %w()
def to(position)
self.first position + 1
end
# Equal to <tt>self[2]</tt>.
def third
self[2]
end
# Equal to <tt>self[1]</tt>.
def second
self[1]
end
# Equal to <tt>self[3]</tt>.
def fourth
self[3]
end
# Equal to <tt>self[2]</tt>.
def third
self[2]
end
# Equal to <tt>self[4]</tt>.
def fifth
self[4]
end
# Equal to <tt>self[3]</tt>.
def fourth
self[3]
end
# Equal to <tt>self[41]</tt>. Also known as accessing "the reddit".
def forty_two
self[41]
end
end
end
# Equal to <tt>self[4]</tt>.
def fifth
self[4]
end
# Equal to <tt>self[41]</tt>. Also known as accessing "the reddit".
def forty_two
self[41]
end
end

View File

@@ -1,197 +1,164 @@
module ActiveSupport #:nodoc:
module CoreExtensions #:nodoc:
module Array #:nodoc:
module Conversions
# Converts the array to a comma-separated sentence where the last element is joined by the connector word. Options:
# * <tt>:words_connector</tt> - The sign or word used to join the elements in arrays with two or more elements (default: ", ")
# * <tt>:two_words_connector</tt> - The sign or word used to join the elements in arrays with two elements (default: " and ")
# * <tt>:last_word_connector</tt> - The sign or word used to join the last element in arrays with three or more elements (default: ", and ")
def to_sentence(options = {})
default_words_connector = I18n.translate(:'support.array.words_connector', :locale => options[:locale])
default_two_words_connector = I18n.translate(:'support.array.two_words_connector', :locale => options[:locale])
default_last_word_connector = I18n.translate(:'support.array.last_word_connector', :locale => options[:locale])
require 'active_support/xml_mini'
require 'active_support/core_ext/hash/keys'
require 'active_support/core_ext/hash/reverse_merge'
require 'active_support/core_ext/string/inflections'
# Try to emulate to_senteces previous to 2.3
if options.has_key?(:connector) || options.has_key?(:skip_last_comma)
::ActiveSupport::Deprecation.warn(":connector has been deprecated. Use :words_connector instead", caller) if options.has_key? :connector
::ActiveSupport::Deprecation.warn(":skip_last_comma has been deprecated. Use :last_word_connector instead", caller) if options.has_key? :skip_last_comma
class Array
# Converts the array to a comma-separated sentence where the last element is joined by the connector word. Options:
# * <tt>:words_connector</tt> - The sign or word used to join the elements in arrays with two or more elements (default: ", ")
# * <tt>:two_words_connector</tt> - The sign or word used to join the elements in arrays with two elements (default: " and ")
# * <tt>:last_word_connector</tt> - The sign or word used to join the last element in arrays with three or more elements (default: ", and ")
def to_sentence(options = {})
if defined?(I18n)
default_words_connector = I18n.translate(:'support.array.words_connector', :locale => options[:locale])
default_two_words_connector = I18n.translate(:'support.array.two_words_connector', :locale => options[:locale])
default_last_word_connector = I18n.translate(:'support.array.last_word_connector', :locale => options[:locale])
else
default_words_connector = ", "
default_two_words_connector = " and "
default_last_word_connector = ", and "
end
skip_last_comma = options.delete :skip_last_comma
if connector = options.delete(:connector)
options[:last_word_connector] ||= skip_last_comma ? connector : ", #{connector}"
else
options[:last_word_connector] ||= skip_last_comma ? default_two_words_connector : default_last_word_connector
end
end
options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale)
options.reverse_merge! :words_connector => default_words_connector, :two_words_connector => default_two_words_connector, :last_word_connector => default_last_word_connector
case length
when 0
""
when 1
self[0].to_s
when 2
"#{self[0]}#{options[:two_words_connector]}#{self[1]}"
else
"#{self[0...-1].join(options[:words_connector])}#{options[:last_word_connector]}#{self[-1]}"
end
end
options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale)
options.reverse_merge! :words_connector => default_words_connector, :two_words_connector => default_two_words_connector, :last_word_connector => default_last_word_connector
# Calls <tt>to_param</tt> on all its elements and joins the result with
# slashes. This is used by <tt>url_for</tt> in Action Pack.
def to_param
collect { |e| e.to_param }.join '/'
end
# 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%5B%5D=Rails&hobbies%5B%5D=coding"
def to_query(key)
prefix = "#{key}[]"
collect { |value| value.to_query(prefix) }.join '&'
end
def self.included(base) #:nodoc:
base.class_eval do
alias_method :to_default_s, :to_s
alias_method :to_s, :to_formatted_s
end
end
# Converts a collection of elements into a formatted string by calling
# <tt>to_s</tt> on all elements and joining them:
#
# Blog.find(:all).to_formatted_s # => "First PostSecond PostThird Post"
#
# Adding in the <tt>:db</tt> argument as the format yields a prettier
# output:
#
# Blog.find(:all).to_formatted_s(:db) # => "First Post,Second Post,Third Post"
def to_formatted_s(format = :default)
case format
when :db
if respond_to?(:empty?) && self.empty?
"null"
else
collect { |element| element.id }.join(",")
end
else
to_default_s
end
end
# Returns a string that represents this array in XML by sending +to_xml+
# to each element. Active Record collections delegate their representation
# in XML to this method.
#
# All elements are expected to respond to +to_xml+, if any of them does
# not an exception is raised.
#
# The root node reflects the class name of the first element in plural
# if all elements belong to the same type and that's not Hash:
#
# customer.projects.to_xml
#
# <?xml version="1.0" encoding="UTF-8"?>
# <projects type="array">
# <project>
# <amount type="decimal">20000.0</amount>
# <customer-id type="integer">1567</customer-id>
# <deal-date type="date">2008-04-09</deal-date>
# ...
# </project>
# <project>
# <amount type="decimal">57230.0</amount>
# <customer-id type="integer">1567</customer-id>
# <deal-date type="date">2008-04-15</deal-date>
# ...
# </project>
# </projects>
#
# Otherwise the root element is "records":
#
# [{:foo => 1, :bar => 2}, {:baz => 3}].to_xml
#
# <?xml version="1.0" encoding="UTF-8"?>
# <records type="array">
# <record>
# <bar type="integer">2</bar>
# <foo type="integer">1</foo>
# </record>
# <record>
# <baz type="integer">3</baz>
# </record>
# </records>
#
# If the collection is empty the root element is "nil-classes" by default:
#
# [].to_xml
#
# <?xml version="1.0" encoding="UTF-8"?>
# <nil-classes type="array"/>
#
# To ensure a meaningful root element use the <tt>:root</tt> option:
#
# customer_with_no_projects.projects.to_xml(:root => "projects")
#
# <?xml version="1.0" encoding="UTF-8"?>
# <projects type="array"/>
#
# By default root children have as node name the one of the root
# singularized. You can change it with the <tt>:children</tt> option.
#
# The +options+ hash is passed downwards:
#
# Message.all.to_xml(:skip_types => true)
#
# <?xml version="1.0" encoding="UTF-8"?>
# <messages>
# <message>
# <created-at>2008-03-07T09:58:18+01:00</created-at>
# <id>1</id>
# <name>1</name>
# <updated-at>2008-03-07T09:58:18+01:00</updated-at>
# <user-id>1</user-id>
# </message>
# </messages>
#
def to_xml(options = {})
raise "Not all elements respond to to_xml" unless all? { |e| e.respond_to? :to_xml }
require 'builder' unless defined?(Builder)
options = options.dup
options[:root] ||= all? { |e| e.is_a?(first.class) && first.class.to_s != "Hash" } ? first.class.to_s.underscore.pluralize.tr('/', '-') : "records"
options[:children] ||= options[:root].singularize
options[:indent] ||= 2
options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
root = options.delete(:root).to_s
children = options.delete(:children)
if !options.has_key?(:dasherize) || options[:dasherize]
root = root.dasherize
end
options[:builder].instruct! unless options.delete(:skip_instruct)
opts = options.merge({ :root => children })
xml = options[:builder]
if empty?
xml.tag!(root, options[:skip_types] ? {} : {:type => "array"})
else
xml.tag!(root, options[:skip_types] ? {} : {:type => "array"}) {
yield xml if block_given?
each { |e| e.to_xml(opts.merge({ :skip_instruct => true })) }
}
end
end
end
case length
when 0
""
when 1
self[0].to_s.dup
when 2
"#{self[0]}#{options[:two_words_connector]}#{self[1]}"
else
"#{self[0...-1].join(options[:words_connector])}#{options[:last_word_connector]}#{self[-1]}"
end
end
# Converts a collection of elements into a formatted string by calling
# <tt>to_s</tt> on all elements and joining them:
#
# Blog.all.to_formatted_s # => "First PostSecond PostThird Post"
#
# Adding in the <tt>:db</tt> argument as the format yields a comma separated
# id list:
#
# Blog.all.to_formatted_s(:db) # => "1,2,3"
def to_formatted_s(format = :default)
case format
when :db
if respond_to?(:empty?) && self.empty?
"null"
else
collect { |element| element.id }.join(",")
end
else
to_default_s
end
end
alias_method :to_default_s, :to_s
alias_method :to_s, :to_formatted_s
# Returns a string that represents the array in XML by invoking +to_xml+
# on each element. Active Record collections delegate their representation
# in XML to this method.
#
# All elements are expected to respond to +to_xml+, if any of them does
# not then an exception is raised.
#
# The root node reflects the class name of the first element in plural
# if all elements belong to the same type and that's not Hash:
#
# customer.projects.to_xml
#
# <?xml version="1.0" encoding="UTF-8"?>
# <projects type="array">
# <project>
# <amount type="decimal">20000.0</amount>
# <customer-id type="integer">1567</customer-id>
# <deal-date type="date">2008-04-09</deal-date>
# ...
# </project>
# <project>
# <amount type="decimal">57230.0</amount>
# <customer-id type="integer">1567</customer-id>
# <deal-date type="date">2008-04-15</deal-date>
# ...
# </project>
# </projects>
#
# Otherwise the root element is "records":
#
# [{:foo => 1, :bar => 2}, {:baz => 3}].to_xml
#
# <?xml version="1.0" encoding="UTF-8"?>
# <records type="array">
# <record>
# <bar type="integer">2</bar>
# <foo type="integer">1</foo>
# </record>
# <record>
# <baz type="integer">3</baz>
# </record>
# </records>
#
# If the collection is empty the root element is "nil-classes" by default:
#
# [].to_xml
#
# <?xml version="1.0" encoding="UTF-8"?>
# <nil-classes type="array"/>
#
# To ensure a meaningful root element use the <tt>:root</tt> option:
#
# customer_with_no_projects.projects.to_xml(:root => "projects")
#
# <?xml version="1.0" encoding="UTF-8"?>
# <projects type="array"/>
#
# By default name of the node for the children of root is <tt>root.singularize</tt>.
# You can change it with the <tt>:children</tt> option.
#
# The +options+ hash is passed downwards:
#
# Message.all.to_xml(:skip_types => true)
#
# <?xml version="1.0" encoding="UTF-8"?>
# <messages>
# <message>
# <created-at>2008-03-07T09:58:18+01:00</created-at>
# <id>1</id>
# <name>1</name>
# <updated-at>2008-03-07T09:58:18+01:00</updated-at>
# <user-id>1</user-id>
# </message>
# </messages>
#
def to_xml(options = {})
require 'active_support/builder' unless defined?(Builder)
options = options.dup
options[:indent] ||= 2
options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
options[:root] ||= if first.class.to_s != "Hash" && all? { |e| e.is_a?(first.class) }
underscored = ActiveSupport::Inflector.underscore(first.class.name)
ActiveSupport::Inflector.pluralize(underscored).tr('/', '_')
else
"objects"
end
builder = options[:builder]
builder.instruct! unless options.delete(:skip_instruct)
root = ActiveSupport::XmlMini.rename_key(options[:root].to_s, options)
children = options.delete(:children) || root.singularize
attributes = options[:skip_types] ? {} : {:type => "array"}
return builder.tag!(root, attributes) if empty?
builder.__send__(:method_missing, root, attributes) do
each { |value| ActiveSupport::XmlMini.to_tag(children, value, options) }
yield builder if block_given?
end
end
end

View File

@@ -1,20 +1,29 @@
module ActiveSupport #:nodoc:
module CoreExtensions #:nodoc:
module Array #:nodoc:
module ExtractOptions
# Extracts options from a set of arguments. Removes and returns the last
# element in the array if it's a hash, otherwise returns a blank hash.
#
# def options(*args)
# args.extract_options!
# end
#
# options(1, 2) # => {}
# options(1, 2, :a => :b) # => {:a=>:b}
def extract_options!
last.is_a?(::Hash) ? pop : {}
end
end
class Hash
# By default, only instances of Hash itself are extractable.
# Subclasses of Hash may implement this method and return
# true to declare themselves as extractable. If a Hash
# is extractable, Array#extract_options! pops it from
# the Array when it is the last element of the Array.
def extractable_options?
instance_of?(Hash)
end
end
class Array
# Extracts options from a set of arguments. Removes and returns the last
# element in the array if it's a hash, otherwise returns a blank hash.
#
# def options(*args)
# args.extract_options!
# end
#
# options(1, 2) # => {}
# options(1, 2, :a => :b) # => {:a=>:b}
def extract_options!
if last.is_a?(Hash) && last.extractable_options?
pop
else
{}
end
end
end

View File

@@ -1,106 +1,100 @@
require 'enumerator'
module ActiveSupport #:nodoc:
module CoreExtensions #:nodoc:
module Array #:nodoc:
module Grouping
# Splits or iterates over the array in groups of size +number+,
# padding any remaining slots with +fill_with+ unless it is +false+.
#
# %w(1 2 3 4 5 6 7).in_groups_of(3) {|group| p group}
# ["1", "2", "3"]
# ["4", "5", "6"]
# ["7", nil, nil]
#
# %w(1 2 3).in_groups_of(2, '&nbsp;') {|group| p group}
# ["1", "2"]
# ["3", "&nbsp;"]
#
# %w(1 2 3).in_groups_of(2, false) {|group| p group}
# ["1", "2"]
# ["3"]
def in_groups_of(number, fill_with = nil)
if fill_with == false
collection = self
else
# size % number gives how many extra we have;
# subtracting from number gives how many to add;
# modulo number ensures we don't add group of just fill.
padding = (number - size % number) % number
collection = dup.concat([fill_with] * padding)
end
class Array
# Splits or iterates over the array in groups of size +number+,
# padding any remaining slots with +fill_with+ unless it is +false+.
#
# %w(1 2 3 4 5 6 7).in_groups_of(3) {|group| p group}
# ["1", "2", "3"]
# ["4", "5", "6"]
# ["7", nil, nil]
#
# %w(1 2 3).in_groups_of(2, '&nbsp;') {|group| p group}
# ["1", "2"]
# ["3", "&nbsp;"]
#
# %w(1 2 3).in_groups_of(2, false) {|group| p group}
# ["1", "2"]
# ["3"]
def in_groups_of(number, fill_with = nil)
if fill_with == false
collection = self
else
# size % number gives how many extra we have;
# subtracting from number gives how many to add;
# modulo number ensures we don't add group of just fill.
padding = (number - size % number) % number
collection = dup.concat([fill_with] * padding)
end
if block_given?
collection.each_slice(number) { |slice| yield(slice) }
else
[].tap do |groups|
collection.each_slice(number) { |group| groups << group }
end
end
end
if block_given?
collection.each_slice(number) { |slice| yield(slice) }
else
groups = []
collection.each_slice(number) { |group| groups << group }
groups
end
end
# Splits or iterates over the array in +number+ of groups, padding any
# remaining slots with +fill_with+ unless it is +false+.
#
# %w(1 2 3 4 5 6 7 8 9 10).in_groups(3) {|group| p group}
# ["1", "2", "3", "4"]
# ["5", "6", "7", nil]
# ["8", "9", "10", nil]
#
# %w(1 2 3 4 5 6 7).in_groups(3, '&nbsp;') {|group| p group}
# ["1", "2", "3"]
# ["4", "5", "&nbsp;"]
# ["6", "7", "&nbsp;"]
#
# %w(1 2 3 4 5 6 7).in_groups(3, false) {|group| p group}
# ["1", "2", "3"]
# ["4", "5"]
# ["6", "7"]
def in_groups(number, fill_with = nil)
# size / number gives minor group size;
# size % number gives how many objects need extra accomodation;
# each group hold either division or division + 1 items.
division = size / number
modulo = size % number
# Splits or iterates over the array in +number+ of groups, padding any
# remaining slots with +fill_with+ unless it is +false+.
#
# %w(1 2 3 4 5 6 7 8 9 10).in_groups(3) {|group| p group}
# ["1", "2", "3", "4"]
# ["5", "6", "7", nil]
# ["8", "9", "10", nil]
#
# %w(1 2 3 4 5 6 7).in_groups(3, '&nbsp;') {|group| p group}
# ["1", "2", "3"]
# ["4", "5", "&nbsp;"]
# ["6", "7", "&nbsp;"]
#
# %w(1 2 3 4 5 6 7).in_groups(3, false) {|group| p group}
# ["1", "2", "3"]
# ["4", "5"]
# ["6", "7"]
def in_groups(number, fill_with = nil)
# size / number gives minor group size;
# size % number gives how many objects need extra accommodation;
# each group hold either division or division + 1 items.
division = size / number
modulo = size % number
# create a new array avoiding dup
groups = []
start = 0
# create a new array avoiding dup
groups = []
start = 0
number.times do |index|
length = division + (modulo > 0 && modulo > index ? 1 : 0)
padding = fill_with != false &&
modulo > 0 && length == division ? 1 : 0
groups << slice(start, length).concat([fill_with] * padding)
start += length
end
number.times do |index|
length = division + (modulo > 0 && modulo > index ? 1 : 0)
padding = fill_with != false &&
modulo > 0 && length == division ? 1 : 0
groups << slice(start, length).concat([fill_with] * padding)
start += length
end
if block_given?
groups.each{|g| yield(g) }
else
groups
end
end
if block_given?
groups.each { |g| yield(g) }
else
groups
end
end
# Divides the array into one or more subarrays based on a delimiting +value+
# or the result of an optional block.
#
# [1, 2, 3, 4, 5].split(3) # => [[1, 2], [4, 5]]
# (1..10).to_a.split { |i| i % 3 == 0 } # => [[1, 2], [4, 5], [7, 8], [10]]
def split(value = nil)
using_block = block_given?
# Divides the array into one or more subarrays based on a delimiting +value+
# or the result of an optional block.
#
# [1, 2, 3, 4, 5].split(3) # => [[1, 2], [4, 5]]
# (1..10).to_a.split { |i| i % 3 == 0 } # => [[1, 2], [4, 5], [7, 8], [10]]
def split(value = nil)
using_block = block_given?
inject([[]]) do |results, element|
if (using_block && yield(element)) || (value == element)
results << []
else
results.last << element
end
results
end
end
inject([[]]) do |results, element|
if (using_block && yield(element)) || (value == element)
results << []
else
results.last << element
end
results
end
end
end

View File

@@ -0,0 +1,7 @@
class Array
# The human way of thinking about adding stuff to the end of a list is with append
alias_method :append, :<<
# The human way of thinking about adding stuff to the beginning of a list is with prepend
alias_method :prepend, :unshift
end

View File

@@ -1,42 +1,30 @@
module ActiveSupport #:nodoc:
module CoreExtensions #:nodoc:
module Array #:nodoc:
module RandomAccess
# This method is deprecated because it masks Kernel#rand within the Array class itself,
# which may be used by a 3rd party library extending Array in turn. See
#
# https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/4555
#
def rand # :nodoc:
ActiveSupport::Deprecation.warn 'Array#rand is deprecated and will be removed in Rails 3. Use Array#sample instead', caller
sample
end
# Returns a random element from the array.
def random_element # :nodoc:
ActiveSupport::Deprecation.warn 'Array#random_element is deprecated and will be removed in Rails 3. Use Array#sample instead', caller
sample
end
# Backport of Array#sample based on Marc-Andre Lafortune's http://github.com/marcandre/backports/
def sample(n=nil)
return self[Kernel.rand(size)] if n.nil?
n = n.to_int
rescue Exception => e
raise TypeError, "Coercion error: #{n.inspect}.to_int => Integer failed:\n(#{e.message})"
else
raise TypeError, "Coercion error: #{n}.to_int did NOT return an Integer (was #{n.class})" unless n.kind_of? ::Integer
raise ArgumentError, "negative array size" if n < 0
n = size if n > size
result = ::Array.new(self)
n.times do |i|
r = i + Kernel.rand(size - i)
result[i], result[r] = result[r], result[i]
end
result[n..size] = []
result
end unless method_defined? :sample
end
class Array
# Backport of Array#sample based on Marc-Andre Lafortune's https://github.com/marcandre/backports/
# Returns a random element or +n+ random elements from the array.
# If the array is empty and +n+ is nil, returns <tt>nil</tt>.
# If +n+ is passed and its value is less than 0, it raises an +ArgumentError+ exception.
# If the value of +n+ is equal or greater than 0 it returns <tt>[]</tt>.
#
# [1,2,3,4,5,6].sample # => 4
# [1,2,3,4,5,6].sample(3) # => [2, 4, 5]
# [1,2,3,4,5,6].sample(-3) # => ArgumentError: negative array size
# [].sample # => nil
# [].sample(3) # => []
def sample(n=nil)
return self[Kernel.rand(size)] if n.nil?
n = n.to_int
rescue Exception => e
raise TypeError, "Coercion error: #{n.inspect}.to_int => Integer failed:\n(#{e.message})"
else
raise TypeError, "Coercion error: obj.to_int did NOT return an Integer (was #{n.class})" unless n.kind_of? Integer
raise ArgumentError, "negative array size" if n < 0
n = size if n > size
result = Array.new(self)
n.times do |i|
r = i + Kernel.rand(size - i)
result[i], result[r] = result[r], result[i]
end
end
result[n..size] = []
result
end unless method_defined? :sample
end

View File

@@ -0,0 +1,16 @@
class Array
# Returns an unique array based on the criteria given as a +Proc+.
#
# [1, 2, 3, 4].uniq_by { |i| i.odd? } # => [1, 2]
#
def uniq_by
hash, array = {}, []
each { |i| hash[yield(i)] ||= (array << i) }
array
end
# Same as uniq_by, but modifies self.
def uniq_by!
replace(uniq_by{ |i| yield(i) })
end
end

View File

@@ -0,0 +1,48 @@
class Array
# Wraps its argument in an array unless it is already an array (or array-like).
#
# Specifically:
#
# * If the argument is +nil+ an empty list is returned.
# * Otherwise, if the argument responds to +to_ary+ it is invoked, and its result returned.
# * Otherwise, returns an array with the argument as its single element.
#
# Array.wrap(nil) # => []
# Array.wrap([1, 2, 3]) # => [1, 2, 3]
# Array.wrap(0) # => [0]
#
# This method is similar in purpose to <tt>Kernel#Array</tt>, but there are some differences:
#
# * If the argument responds to +to_ary+ the method is invoked. <tt>Kernel#Array</tt>
# moves on to try +to_a+ if the returned value is +nil+, but <tt>Array.wrap</tt> returns
# such a +nil+ right away.
# * If the returned value from +to_ary+ is neither +nil+ nor an +Array+ object, <tt>Kernel#Array</tt>
# raises an exception, while <tt>Array.wrap</tt> does not, it just returns the value.
# * It does not call +to_a+ on the argument, though special-cases +nil+ to return an empty array.
#
# The last point is particularly worth comparing for some enumerables:
#
# Array(:foo => :bar) # => [[:foo, :bar]]
# Array.wrap(:foo => :bar) # => [{:foo => :bar}]
#
# Array("foo\nbar") # => ["foo\n", "bar"], in Ruby 1.8
# Array.wrap("foo\nbar") # => ["foo\nbar"]
#
# There's also a related idiom that uses the splat operator:
#
# [*object]
#
# which returns <tt>[nil]</tt> for +nil+, and calls to <tt>Array(object)</tt> otherwise.
#
# Thus, in this case the behavior is different for +nil+, and the differences with
# <tt>Kernel#Array</tt> explained above apply to the rest of +object+s.
def self.wrap(object)
if object.nil?
[]
elsif object.respond_to?(:to_ary)
object.to_ary || [object]
else
[object]
end
end
end

View File

@@ -1,24 +0,0 @@
module ActiveSupport #:nodoc:
module CoreExtensions #:nodoc:
module Array #:nodoc:
module Wrapper
# Wraps the object in an Array unless it's an Array. Converts the
# object to an Array using #to_ary if it implements that.
def wrap(object)
case object
when nil
[]
when self
object
else
if object.respond_to?(:to_ary)
object.to_ary
else
[object]
end
end
end
end
end
end
end

View File

@@ -1,4 +0,0 @@
require 'active_support/base64'
require 'active_support/core_ext/base64/encoding'
ActiveSupport::Base64.extend ActiveSupport::CoreExtensions::Base64::Encoding

View File

@@ -1,16 +0,0 @@
module ActiveSupport #:nodoc:
module CoreExtensions #:nodoc:
module Base64 #:nodoc:
module Encoding
# Encodes the value as base64 without the newline breaks. This makes the base64 encoding readily usable as URL parameters
# or memcache keys without further processing.
#
# ActiveSupport::Base64.encode64s("Original unencoded string")
# # => "T3JpZ2luYWwgdW5lbmNvZGVkIHN0cmluZw=="
def encode64s(value)
encode64(value).gsub(/\n/, '')
end
end
end
end
end

View File

@@ -1,18 +1,6 @@
require 'benchmark'
class << Benchmark
# Earlier Ruby had a slower implementation.
if RUBY_VERSION < '1.8.7'
remove_method :realtime
def realtime
r0 = Time.now
yield
r1 = Time.now
r1.to_f - r0.to_f
end
end
def ms
1000 * realtime { yield }
end

View File

@@ -0,0 +1 @@
require 'active_support/core_ext/big_decimal/conversions'

View File

@@ -0,0 +1,45 @@
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' }
# 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
# Backport this method if it doesn't exist
unless method_defined?(:to_d)
def to_d
self
end
end
DEFAULT_STRING_FORMAT = 'F'
def to_formatted_s(format = DEFAULT_STRING_FORMAT)
_original_to_s(format)
end
alias_method :_original_to_s, :to_s
alias_method :to_s, :to_formatted_s
end

View File

@@ -1,6 +0,0 @@
require 'bigdecimal'
require 'active_support/core_ext/bigdecimal/conversions'
class BigDecimal#:nodoc:
include ActiveSupport::CoreExtensions::BigDecimal::Conversions
end

View File

@@ -1,37 +0,0 @@
require 'yaml'
module ActiveSupport #:nodoc:
module CoreExtensions #:nodoc:
module BigDecimal #:nodoc:
module Conversions
DEFAULT_STRING_FORMAT = 'F'.freeze
YAML_TAG = 'tag:yaml.org,2002:float'.freeze
YAML_MAPPING = { 'Infinity' => '.Inf', '-Infinity' => '-.Inf', 'NaN' => '.NaN' }
def self.included(base) #:nodoc:
base.class_eval do
alias_method :_original_to_s, :to_s
alias_method :to_s, :to_formatted_s
yaml_as YAML_TAG
end
end
def to_formatted_s(format = DEFAULT_STRING_FORMAT)
_original_to_s(format)
end
# 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 = {})
YAML.quick_emit(nil, opts) do |out|
string = to_s
out.scalar(YAML_TAG, YAML_MAPPING[string] || string, :plain)
end
end
end
end
end
end

View File

@@ -1,2 +0,0 @@
require 'active_support/core_ext/object/blank'
ActiveSupport::Deprecation.warn 'require "active_support/core_ext/blank" is deprecated and will be removed in Rails 3. Use require "active_support/core_ext/object/blank" instead.'

View File

@@ -1,5 +0,0 @@
require 'active_support/core_ext/cgi/escape_skipping_slashes'
class CGI #:nodoc:
extend ActiveSupport::CoreExtensions::CGI::EscapeSkippingSlashes
end

View File

@@ -1,23 +0,0 @@
module ActiveSupport #:nodoc:
module CoreExtensions #:nodoc:
module CGI #:nodoc:
module EscapeSkippingSlashes #:nodoc:
if RUBY_VERSION >= '1.9'
def 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 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
end
end
end

View File

@@ -1,5 +1,5 @@
require 'active_support/core_ext/class/attribute_accessors'
require 'active_support/core_ext/class/inheritable_attributes'
require 'active_support/core_ext/class/removal'
require 'active_support/core_ext/class/delegating_attributes'
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/class/attribute_accessors'
require 'active_support/core_ext/class/delegating_attributes'
require 'active_support/core_ext/class/inheritable_attributes'
require 'active_support/core_ext/class/subclasses'

View File

@@ -1,9 +1,10 @@
require 'active_support/core_ext/kernel/singleton_class'
require 'active_support/core_ext/module/remove_method'
require 'active_support/core_ext/array/extract_options'
class Class
# Declare a class-level attribute whose value is inheritable and
# overwritable by subclasses:
# Declare a class-level attribute whose value is inheritable by subclasses.
# Subclasses can change their own value and it will not impact parent class.
#
# class Base
# class_attribute :setting
@@ -18,12 +19,34 @@ class Class
# Subclass.setting # => false
# Base.setting # => true
#
# In the above case as long as Subclass does not assign a value to setting
# by performing <tt>Subclass.setting = _something_ </tt>, <tt>Subclass.setting</tt>
# would read value assigned to parent class. Once Subclass assigns a value then
# the value assigned by Subclass would be returned.
#
# This matches normal Ruby method inheritance: think of writing an attribute
# on a subclass as overriding the reader method.
# on a subclass as overriding the reader method. However, you need to be aware
# when using +class_attribute+ with mutable structures as +Array+ or +Hash+.
# In such cases, you don't want to do changes in places but use setters:
#
# Base.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]
#
# # Use setters to not propagate changes:
# Base.setting = []
# Subclass.setting += [:foo]
# Base.setting # => []
# Subclass.setting # => [:foo]
#
# For convenience, a query method is defined as well:
#
# Subclass.setting? # => false
# Subclass.setting? # => false
#
# Instances may overwrite the class value in the same way:
#
@@ -34,11 +57,18 @@ class Class
# object.setting # => false
# Base.setting # => true
#
# To opt out of the instance reader method, pass :instance_reader => false.
#
# object.setting # => NoMethodError
# object.setting? # => NoMethodError
#
# To opt out of the instance writer method, pass :instance_writer => false.
#
# object.setting = false # => NoMethodError
def class_attribute(*attrs)
instance_writer = !attrs.last.is_a?(Hash) || attrs.pop[:instance_writer]
options = attrs.extract_options!
instance_reader = options.fetch(:instance_reader, true)
instance_writer = options.fetch(:instance_writer, true)
attrs.each do |name|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
@@ -50,18 +80,36 @@ 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
def #{name}
defined?(@#{name}) ? @#{name} : singleton_class.#{name}
end
if instance_reader
remove_possible_method :#{name}
def #{name}
defined?(@#{name}) ? @#{name} : self.class.#{name}
end
def #{name}?
!!#{name}
def #{name}?
!!#{name}
end
end
RUBY
attr_writer name if instance_writer
end
end
private
def singleton_class?
ancestors.first != self
end
end

View File

@@ -1,16 +1,37 @@
require 'active_support/core_ext/array/extract_options'
# Extends the class object with class and instance accessors for class attributes,
# just like the native attr* accessors for instance attributes.
#
# class Person
# cattr_accessor :hair_colors
# end
#
# Person.hair_colors = [:brown, :black, :blonde, :red]
class Class
# Defines a class attribute if it's not defined and creates a reader method that
# returns the attribute value.
#
# class Person
# cattr_reader :hair_colors
# end
#
# Person.class_variable_set("@@hair_colors", [:brown, :black])
# Person.hair_colors # => [:brown, :black]
# Person.new.hair_colors # => [:brown, :black]
#
# The attribute name must be a valid method name in Ruby.
#
# class Person
# cattr_reader :"1_Badname "
# end
# # => NameError: invalid attribute name
#
# If you want to opt out the instance reader method, you can pass <tt>:instance_reader => false</tt>
# or <tt>:instance_accessor => false</tt>.
#
# class Person
# cattr_reader :hair_colors, :instance_reader => false
# end
#
# Person.new.hair_colors # => NoMethodError
def cattr_reader(*syms)
options = syms.extract_options!
syms.each do |sym|
next if sym.is_a?(Hash)
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
unless defined? @@#{sym}
@@#{sym} = nil
@@ -21,7 +42,7 @@ class Class
end
EOS
unless options[:instance_reader] == false
unless options[:instance_reader] == false || options[:instance_accessor] == false
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
def #{sym}
@@#{sym}
@@ -31,6 +52,43 @@ class Class
end
end
# Defines a class attribute if it's not defined and creates a writer method to allow
# assignment to the attribute.
#
# class Person
# cattr_writer :hair_colors
# end
#
# Person.hair_colors = [:brown, :black]
# Person.class_variable_get("@@hair_colors") # => [:brown, :black]
# Person.new.hair_colors = [:blonde, :red]
# Person.class_variable_get("@@hair_colors") # => [:blonde, :red]
#
# The attribute name must be a valid method name in Ruby.
#
# class Person
# cattr_writer :"1_Badname "
# end
# # => NameError: invalid attribute name
#
# If you want to opt out the instance writer method, pass <tt>:instance_writer => false</tt>
# or <tt>:instance_accessor => false</tt>.
#
# class Person
# cattr_writer :hair_colors, :instance_writer => false
# end
#
# Person.new.hair_colors = [:blonde, :red] # => NoMethodError
#
# Also, you can pass a block to set up the attribute with a default value.
#
# class Person
# cattr_writer :hair_colors do
# [:brown, :black, :blonde, :red]
# end
# end
#
# Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red]
def cattr_writer(*syms)
options = syms.extract_options!
syms.each do |sym|
@@ -44,18 +102,67 @@ class Class
end
EOS
unless options[:instance_writer] == false
unless options[:instance_writer] == false || options[:instance_accessor] == false
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
def #{sym}=(obj)
@@#{sym} = obj
end
EOS
end
self.send("#{sym}=", yield) if block_given?
end
end
def cattr_accessor(*syms)
# Defines both class and instance accessors for class attributes.
#
# class Person
# cattr_accessor :hair_colors
# end
#
# Person.hair_colors = [:brown, :black, :blonde, :red]
# Person.hair_colors # => [:brown, :black, :blonde, :red]
# Person.new.hair_colors # => [:brown, :black, :blonde, :red]
#
# If a subclass changes the value then that would also change the value for
# parent class. Similarly if parent class changes the value then that would
# change the value of subclasses too.
#
# class Male < Person
# end
#
# Male.hair_colors << :blue
# Person.hair_colors # => [:brown, :black, :blonde, :red, :blue]
#
# To opt out of the instance writer method, pass <tt>:instance_writer => false</tt>.
# To opt out of the instance reader method, pass <tt>:instance_reader => false</tt>.
#
# class Person
# cattr_accessor :hair_colors, :instance_writer => false, :instance_reader => false
# end
#
# Person.new.hair_colors = [:brown] # => NoMethodError
# Person.new.hair_colors # => NoMethodError
#
# Or pass <tt>:instance_accessor => false</tt>, to opt out both instance methods.
#
# class Person
# cattr_accessor :hair_colors, :instance_accessor => false
# end
#
# Person.new.hair_colors = [:brown] # => NoMethodError
# Person.new.hair_colors # => NoMethodError
#
# Also you can pass a block to set up the attribute with a default value.
#
# class Person
# cattr_accessor :hair_colors do
# [:brown, :black, :blonde, :red]
# end
# end
#
# Person.class_variable_get("@@hair_colors") #=> [:brown, :black, :blonde, :red]
def cattr_accessor(*syms, &blk)
cattr_reader(*syms)
cattr_writer(*syms)
cattr_writer(*syms, &blk)
end
end

View File

@@ -1,47 +1,44 @@
# These class attributes behave something like the class
# inheritable accessors. But instead of copying the hash over at
# the time the subclass is first defined, the accessors simply
# delegate to their superclass unless they have been given a
# specific value. This stops the strange situation where values
# set after class definition don't get applied to subclasses.
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/array/extract_options'
require 'active_support/core_ext/kernel/singleton_class'
require 'active_support/core_ext/module/remove_method'
class Class
def superclass_delegating_reader(*names)
class_name_to_stop_searching_on = self.superclass.name.blank? ? "Object" : self.superclass.name
names.each do |name|
class_eval <<-EOS
def self.#{name} # def self.only_reader
if defined?(@#{name}) # if defined?(@only_reader)
@#{name} # @only_reader
elsif superclass < #{class_name_to_stop_searching_on} && # elsif superclass < Object &&
superclass.respond_to?(:#{name}) # superclass.respond_to?(:only_reader)
superclass.#{name} # superclass.only_reader
end # end
end # end
def #{name} # def only_reader
self.class.#{name} # self.class.only_reader
end # end
def self.#{name}? # def self.only_reader?
!!#{name} # !!only_reader
end # end
def #{name}? # def only_reader?
!!#{name} # !!only_reader
end # end
EOS
end
def superclass_delegating_accessor(name, options = {})
# Create private _name and _name= methods that can still be used if the public
# methods are overridden. This allows
_superclass_delegating_accessor("_#{name}")
# Generate the public methods name, name=, and name?
# These methods dispatch to the private _name, and _name= methods, making them
# overridable
singleton_class.send(:define_method, name) { send("_#{name}") }
singleton_class.send(:define_method, "#{name}?") { !!send("_#{name}") }
singleton_class.send(:define_method, "#{name}=") { |value| send("_#{name}=", value) }
# If an instance_reader is needed, generate methods for name and name= on the
# class itself, so instances will be able to see them
define_method(name) { send("_#{name}") } if options[:instance_reader] != false
define_method("#{name}?") { !!send("#{name}") } if options[:instance_reader] != false
end
def superclass_delegating_writer(*names)
names.each do |name|
class_eval <<-EOS
def self.#{name}=(value) # def self.only_writer=(value)
@#{name} = value # @only_writer = value
end # end
EOS
end
private
# Take the object being set and store it in a method. This gives us automatic
# inheritance behavior, without having to store the object in an instance
# variable and look up the superclass chain manually.
def _stash_object_in_method(object, method, instance_reader = true)
singleton_class.remove_possible_method(method)
singleton_class.send(:define_method, method) { object }
remove_possible_method(method)
define_method(method) { object } if instance_reader
end
def superclass_delegating_accessor(*names)
superclass_delegating_reader(*names)
superclass_delegating_writer(*names)
def _superclass_delegating_accessor(name, options = {})
singleton_class.send(:define_method, "#{name}=") do |value|
_stash_object_in_method(value, name, options[:instance_reader] != false)
end
send("#{name}=", nil)
end
end

View File

@@ -1,7 +1,3 @@
# Retain for backward compatibility. Methods are now included in Class.
module ClassInheritableAttributes # :nodoc:
end
# Allows attributes to be shared within an inheritance hierarchy, but where 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
# to, for example, an array without those additions being shared with either their parent, siblings, or

View File

@@ -1,50 +0,0 @@
class Class #:nodoc:
# Unassociates the class with its subclasses and removes the subclasses
# themselves.
#
# Integer.remove_subclasses # => [Bignum, Fixnum]
# Fixnum # => NameError: uninitialized constant Fixnum
def remove_subclasses
Object.remove_subclasses_of(self)
end
# Returns an array with the names of the subclasses of +self+ as strings.
#
# Integer.subclasses # => ["Bignum", "Fixnum"]
def subclasses
Object.subclasses_of(self).map { |o| o.to_s }
end
# Removes the classes in +klasses+ from their parent module.
#
# Ordinary classes belong to some module via a constant. This method computes
# that constant name from the class name and removes it from the module it
# belongs to.
#
# Object.remove_class(Integer) # => [Integer]
# Integer # => NameError: uninitialized constant Integer
#
# Take into account that in general the class object could be still stored
# somewhere else.
#
# i = Integer # => Integer
# Object.remove_class(Integer) # => [Integer]
# Integer # => NameError: uninitialized constant Integer
# i.subclasses # => ["Bignum", "Fixnum"]
# Fixnum.superclass # => Integer
def remove_class(*klasses)
klasses.flatten.each do |klass|
# Skip this class if there is nothing bound to this name
next unless defined?(klass.name)
basename = klass.to_s.split("::").last
parent = klass.parent
# Skip this class if it does not match the current one bound to this name
next unless parent.const_defined?(basename) && klass = parent.const_get(basename)
parent.instance_eval { remove_const basename } unless parent == klass
end
end
end

View File

@@ -0,0 +1,36 @@
require 'active_support/core_ext/module/anonymous'
require 'active_support/core_ext/module/reachable'
class Class #:nodoc:
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
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
end
end

View File

@@ -1,10 +0,0 @@
require 'date'
require 'active_support/core_ext/date/behavior'
require 'active_support/core_ext/date/calculations'
require 'active_support/core_ext/date/conversions'
class Date#:nodoc:
include ActiveSupport::CoreExtensions::Date::Behavior
include ActiveSupport::CoreExtensions::Date::Calculations
include ActiveSupport::CoreExtensions::Date::Conversions
end

View File

@@ -0,0 +1,8 @@
require 'active_support/core_ext/object/acts_like'
class Date
# Duck-types as a Date-like class. See Object#acts_like?.
def acts_like_date?
true
end
end

View File

@@ -1,42 +0,0 @@
require 'date'
module ActiveSupport #:nodoc:
module CoreExtensions #:nodoc:
module Date #:nodoc:
module Behavior
# Enable more predictable duck-typing on Date-like classes. See
# Object#acts_like?.
def acts_like_date?
true
end
# Date memoizes some instance methods using metaprogramming to wrap
# the methods with one that caches the result in an instance variable.
#
# If a Date is frozen but the memoized method hasn't been called, the
# first call will result in a frozen object error since the memo
# instance variable is uninitialized.
#
# Work around by eagerly memoizing before freezing.
#
# Ruby 1.9 uses a preinitialized instance variable so it's unaffected.
# This hack is as close as we can get to feature detection:
begin
::Date.today.freeze.jd
rescue => frozen_object_error
if frozen_object_error.message =~ /frozen/
def freeze #:nodoc:
self.class.private_instance_methods(false).each do |m|
if m.to_s =~ /\A__\d+__\Z/
instance_variable_set(:"@#{m}", [send(m)])
end
end
super
end
end
end
end
end
end
end

View File

@@ -1,241 +1,276 @@
module ActiveSupport #:nodoc:
module CoreExtensions #:nodoc:
module Date #:nodoc:
# Enables the use of time calculations within Date itself
module Calculations
def self.included(base) #:nodoc:
base.extend ClassMethods
require 'date'
require 'active_support/duration'
require 'active_support/core_ext/object/acts_like'
require 'active_support/core_ext/date/zones'
require 'active_support/core_ext/time/zones'
base.instance_eval do
alias_method :plus_without_duration, :+
alias_method :+, :plus_with_duration
class Date
DAYS_INTO_WEEK = { :monday => 0, :tuesday => 1, :wednesday => 2, :thursday => 3, :friday => 4, :saturday => 5, :sunday => 6 }
alias_method :minus_without_duration, :-
alias_method :-, :minus_with_duration
end
end
if RUBY_VERSION < '1.9'
undef :>>
module ClassMethods
# Returns a new Date representing the date 1 day ago (i.e. yesterday's date).
def yesterday
::Date.today.yesterday
end
# Returns a new Date representing the date 1 day after today (i.e. tomorrow's date).
def tomorrow
::Date.today.tomorrow
end
# Returns Time.zone.today when config.time_zone is set, otherwise just returns Date.today.
def current
::Time.zone_default ? ::Time.zone.today : ::Date.today
end
end
# Tells whether the Date object's date lies in the past
def past?
self < ::Date.current
end
# Tells whether the Date object's date is today
def today?
self.to_date == ::Date.current # we need the to_date because of DateTime
end
# Tells whether the Date object's date lies in the future
def future?
self > ::Date.current
end
# Converts Date to a Time (or DateTime if necessary) with the time portion set to the beginning of the day (0:00)
# and then subtracts the specified number of seconds
def ago(seconds)
to_time.since(-seconds)
end
# Converts Date to a Time (or DateTime if necessary) with the time portion set to the beginning of the day (0:00)
# and then adds the specified number of seconds
def since(seconds)
to_time.since(seconds)
end
alias :in :since
# Converts Date to a Time (or DateTime if necessary) with the time portion set to the beginning of the day (0:00)
def beginning_of_day
to_time
end
alias :midnight :beginning_of_day
alias :at_midnight :beginning_of_day
alias :at_beginning_of_day :beginning_of_day
# Converts Date to a Time (or DateTime if necessary) with the time portion set to the end of the day (23:59:59)
def end_of_day
to_time.end_of_day
end
def plus_with_duration(other) #:nodoc:
if ActiveSupport::Duration === other
other.since(self)
else
plus_without_duration(other)
end
end
def minus_with_duration(other) #:nodoc:
if ActiveSupport::Duration === other
plus_with_duration(-other)
else
minus_without_duration(other)
end
end
# Provides precise Date calculations for years, months, and days. The +options+ parameter takes a hash with
# any of these keys: <tt>:years</tt>, <tt>:months</tt>, <tt>:weeks</tt>, <tt>:days</tt>.
def advance(options)
options = options.dup
d = self
d = d >> options.delete(:years) * 12 if options[:years]
d = d >> options.delete(:months) if options[:months]
d = d + options.delete(:weeks) * 7 if options[:weeks]
d = d + options.delete(:days) if options[:days]
d
end
# Returns a new Date where one or more of the elements have been changed according to the +options+ parameter.
#
# Examples:
#
# Date.new(2007, 5, 12).change(:day => 1) # => Date.new(2007, 5, 1)
# Date.new(2007, 5, 12).change(:year => 2005, :month => 1) # => Date.new(2005, 1, 12)
def change(options)
::Date.new(
options[:year] || self.year,
options[:month] || self.month,
options[:day] || self.day
)
end
# Returns a new Date/DateTime representing the time a number of specified months ago
def months_ago(months)
advance(:months => -months)
end
# Returns a new Date/DateTime representing the time a number of specified months in the future
def months_since(months)
advance(:months => months)
end
# Returns a new Date/DateTime representing the time a number of specified years ago
def years_ago(years)
advance(:years => -years)
end
# Returns a new Date/DateTime representing the time a number of specified years in the future
def years_since(years)
advance(:years => years)
end
def last_year # :nodoc:
ActiveSupport::Deprecation.warn("Date#last_year is deprecated and has been removed in Rails 3, please use Date#prev_year instead", caller)
prev_year
end
# Short-hand for years_ago(1)
def prev_year
years_ago(1)
end unless method_defined?(:prev_year)
# Short-hand for years_since(1)
def next_year
years_since(1)
end
def last_month # :nodoc:
ActiveSupport::Deprecation.warn("Date#last_month is deprecated and has been removed in Rails 3, please use Date#prev_month instead", caller)
prev_month
end
# Short-hand for months_ago(1)
def prev_month
months_ago(1)
end unless method_defined?(:prev_month)
# Short-hand for months_since(1)
def next_month
months_since(1)
end
# Returns a new Date/DateTime representing the "start" of this week (i.e, Monday; DateTime objects will have time set to 0:00)
def beginning_of_week
days_to_monday = self.wday!=0 ? self.wday-1 : 6
result = self - days_to_monday
self.acts_like?(:time) ? result.midnight : result
end
alias :monday :beginning_of_week
alias :at_beginning_of_week :beginning_of_week
# Returns a new Date/DateTime representing the end of this week (Sunday, DateTime objects will have time set to 23:59:59)
def end_of_week
days_to_sunday = self.wday!=0 ? 7-self.wday : 0
result = self + days_to_sunday.days
self.acts_like?(:time) ? result.end_of_day : result
end
alias :at_end_of_week :end_of_week
# Returns a new Date/DateTime representing the 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]
self.acts_like?(:time) ? result.change(:hour => 0) : result
end
# Returns a new ; DateTime objects will have time set to 0:00DateTime representing the start of the month (1st of the month; DateTime objects will have time set to 0:00)
def beginning_of_month
self.acts_like?(:time) ? change(:day => 1,:hour => 0, :min => 0, :sec => 0) : change(:day => 1)
end
alias :at_beginning_of_month :beginning_of_month
# Returns a new Date/DateTime representing the end of the month (last day of the month; DateTime objects will have time set to 0:00)
def end_of_month
last_day = ::Time.days_in_month( self.month, self.year )
self.acts_like?(:time) ? change(:day => last_day, :hour => 23, :min => 59, :sec => 59) : change(:day => last_day)
end
alias :at_end_of_month :end_of_month
# Returns a new Date/DateTime representing the start of the quarter (1st of january, april, july, october; DateTime objects will have time set to 0:00)
def beginning_of_quarter
beginning_of_month.change(:month => [10, 7, 4, 1].detect { |m| m <= self.month })
end
alias :at_beginning_of_quarter :beginning_of_quarter
# Returns a new Date/DateTime representing the end of the quarter (last day of march, june, september, december; DateTime objects will have time set to 23:59:59)
def end_of_quarter
beginning_of_month.change(:month => [3, 6, 9, 12].detect { |m| m >= self.month }).end_of_month
end
alias :at_end_of_quarter :end_of_quarter
# Returns a new Date/DateTime representing the start of the year (1st of january; DateTime objects will have time set to 0:00)
def beginning_of_year
self.acts_like?(:time) ? change(:month => 1, :day => 1, :hour => 0, :min => 0, :sec => 0) : change(:month => 1, :day => 1)
end
alias :at_beginning_of_year :beginning_of_year
# Returns a new Time representing the end of the year (31st of december; DateTime objects will have time set to 23:59:59)
def end_of_year
self.acts_like?(:time) ? change(:month => 12,:day => 31,:hour => 23, :min => 59, :sec => 59) : change(:month => 12, :day => 31)
end
alias :at_end_of_year :end_of_year
# Convenience method which returns a new Date/DateTime representing the time 1 day ago
def yesterday
self - 1
end
# Convenience method which returns a new Date/DateTime representing the time 1 day since the instance time
def tomorrow
self + 1
end
# Backported from 1.9. The one in 1.8 leads to incorrect next_month and
# friends for dates where the calendar reform is involved. It additionally
# prevents an infinite loop fixed in r27013.
def >>(n)
y, m = (year * 12 + (mon - 1) + n).divmod(12)
m, = (m + 1) .divmod(1)
d = mday
until jd2 = self.class.valid_civil?(y, m, d, start)
d -= 1
raise ArgumentError, 'invalid date' unless d > 0
end
self + (jd2 - jd)
end
end
class << self
# Returns a new Date representing the date 1 day ago (i.e. yesterday's date).
def yesterday
::Date.current.yesterday
end
# Returns a new Date representing the date 1 day after today (i.e. tomorrow's date).
def tomorrow
::Date.current.tomorrow
end
# 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 ? ::Time.zone.today : ::Date.today
end
end
# Returns true if the Date object's date lies in the past. Otherwise returns false.
def past?
self < ::Date.current
end
# Returns true if the Date object's date is today.
def today?
self.to_date == ::Date.current # we need the to_date because of DateTime
end
# Returns true if the Date object's date lies in the future.
def future?
self > ::Date.current
end
# Converts Date to a Time (or DateTime if necessary) with the time portion set to the beginning of the day (0:00)
# and then subtracts the specified number of seconds.
def ago(seconds)
to_time_in_current_zone.since(-seconds)
end
# Converts Date to a Time (or DateTime if necessary) with the time portion set to the beginning of the day (0:00)
# and then adds the specified number of seconds
def since(seconds)
to_time_in_current_zone.since(seconds)
end
alias :in :since
# Converts Date to a Time (or DateTime if necessary) with the time portion set to the beginning of the day (0:00)
def beginning_of_day
to_time_in_current_zone
end
alias :midnight :beginning_of_day
alias :at_midnight :beginning_of_day
alias :at_beginning_of_day :beginning_of_day
# Converts Date to a Time (or DateTime if necessary) with the time portion set to the end of the day (23:59:59)
def end_of_day
to_time_in_current_zone.end_of_day
end
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
# Provides precise Date calculations for years, months, and days. The +options+ parameter takes a hash with
# any of these keys: <tt>:years</tt>, <tt>:months</tt>, <tt>:weeks</tt>, <tt>:days</tt>.
def advance(options)
options = options.dup
d = self
d = d >> options.delete(:years) * 12 if options[:years]
d = d >> options.delete(:months) if options[:months]
d = d + options.delete(:weeks) * 7 if options[:weeks]
d = d + options.delete(:days) if options[:days]
d
end
# Returns a new Date where one or more of the elements have been changed according to the +options+ parameter.
#
# Examples:
#
# Date.new(2007, 5, 12).change(:day => 1) # => Date.new(2007, 5, 1)
# Date.new(2007, 5, 12).change(:year => 2005, :month => 1) # => Date.new(2005, 1, 12)
def change(options)
::Date.new(
options[:year] || self.year,
options[:month] || self.month,
options[:day] || self.day
)
end
# Returns a new Date/DateTime representing the time a number of specified weeks ago.
def weeks_ago(weeks)
advance(:weeks => -weeks)
end
# Returns a new Date/DateTime representing the time a number of specified months ago.
def months_ago(months)
advance(:months => -months)
end
# Returns a new Date/DateTime representing the time a number of specified months in the future.
def months_since(months)
advance(:months => months)
end
# Returns a new Date/DateTime representing the time a number of specified years ago.
def years_ago(years)
advance(:years => -years)
end
# Returns a new Date/DateTime representing the time a number of specified years in the future.
def years_since(years)
advance(:years => years)
end
# Shorthand for years_ago(1)
def prev_year
years_ago(1)
end unless method_defined?(:prev_year)
# Shorthand for years_since(1)
def next_year
years_since(1)
end unless method_defined?(:next_year)
# Shorthand for months_ago(1)
def prev_month
months_ago(1)
end unless method_defined?(:prev_month)
# Shorthand for months_since(1)
def next_month
months_since(1)
end unless method_defined?(:next_month)
# Returns number of days to start of this week. Week is assumed to start on
# +start_day+, default is +:monday+.
def days_to_week_start(start_day = :monday)
start_day_number = DAYS_INTO_WEEK[start_day]
current_day_number = wday != 0 ? wday - 1 : 6
(current_day_number - start_day_number) % 7
end
# Returns a new +Date+/+DateTime+ representing the start of this week. Week is
# assumed to start on +start_day+, default is +:monday+. +DateTime+ objects
# have their time set to 0:00.
def beginning_of_week(start_day = :monday)
days_to_start = days_to_week_start(start_day)
result = self - days_to_start
acts_like?(:time) ? result.midnight : result
end
alias :at_beginning_of_week :beginning_of_week
# Returns a new +Date+/+DateTime+ representing the start of this week. Week is
# assumed to start on a Monday. +DateTime+ objects have their time set to 0:00.
def monday
beginning_of_week
end
# Returns a new +Date+/+DateTime+ representing the end of this week. Week is
# assumed to start on +start_day+, default is +:monday+. +DateTime+ objects
# have their time set to 23:59:59.
def end_of_week(start_day = :monday)
days_to_end = 6 - days_to_week_start(start_day)
result = self + days_to_end.days
self.acts_like?(:time) ? result.end_of_day : result
end
alias :at_end_of_week :end_of_week
# Returns a new +Date+/+DateTime+ representing the end of this week. Week is
# assumed to start on a Monday. +DateTime+ objects have their time set to 23:59:59.
def sunday
end_of_week
end
# Returns a new +Date+/+DateTime+ representing the given +day+ in the previous
# week. Default is +:monday+. +DateTime+ objects have their time set to 0:00.
def prev_week(day = :monday)
result = (self - 7).beginning_of_week + DAYS_INTO_WEEK[day]
self.acts_like?(:time) ? result.change(:hour => 0) : result
end
# Returns a new Date/DateTime representing the start of the given day in next week (default is :monday).
def next_week(day = :monday)
result = (self + 7).beginning_of_week + DAYS_INTO_WEEK[day]
self.acts_like?(:time) ? result.change(:hour => 0) : result
end
# Returns a new ; DateTime objects will have time set to 0:00DateTime representing the start of the month (1st of the month; DateTime objects will have time set to 0:00)
def beginning_of_month
self.acts_like?(:time) ? change(:day => 1, :hour => 0) : change(:day => 1)
end
alias :at_beginning_of_month :beginning_of_month
# Returns a new Date/DateTime representing the end of the month (last day of the month; DateTime objects will have time set to 0:00)
def end_of_month
last_day = ::Time.days_in_month( self.month, self.year )
self.acts_like?(:time) ? change(:day => last_day, :hour => 23, :min => 59, :sec => 59) : change(:day => last_day)
end
alias :at_end_of_month :end_of_month
# Returns a new Date/DateTime representing the start of the quarter (1st of january, april, july, october; DateTime objects will have time set to 0:00)
def beginning_of_quarter
beginning_of_month.change(:month => [10, 7, 4, 1].detect { |m| m <= self.month })
end
alias :at_beginning_of_quarter :beginning_of_quarter
# Returns a new Date/DateTime representing the end of the quarter (last day of march, june, september, december; DateTime objects will have time set to 23:59:59)
def end_of_quarter
beginning_of_month.change(:month => [3, 6, 9, 12].detect { |m| m >= self.month }).end_of_month
end
alias :at_end_of_quarter :end_of_quarter
# Returns a new Date/DateTime representing the start of the year (1st of january; DateTime objects will have time set to 0:00)
def beginning_of_year
self.acts_like?(:time) ? change(:month => 1, :day => 1, :hour => 0) : change(:month => 1, :day => 1)
end
alias :at_beginning_of_year :beginning_of_year
# Returns a new Time representing the end of the year (31st of december; DateTime objects will have time set to 23:59:59)
def end_of_year
self.acts_like?(:time) ? change(:month => 12, :day => 31, :hour => 23, :min => 59, :sec => 59) : change(:month => 12, :day => 31)
end
alias :at_end_of_year :end_of_year
# Convenience method which returns a new Date/DateTime representing the time 1 day ago
def yesterday
self - 1
end
# Convenience method which returns a new Date/DateTime representing the time 1 day since the instance time
def tomorrow
self + 1
end
end

View File

@@ -1,107 +1,106 @@
module ActiveSupport #:nodoc:
module CoreExtensions #:nodoc:
module Date #:nodoc:
# Converting dates to formatted strings, times, and datetimes.
module Conversions
DATE_FORMATS = {
:short => "%e %b",
:long => "%B %e, %Y",
:db => "%Y-%m-%d",
:number => "%Y%m%d",
:long_ordinal => lambda { |date| date.strftime("%B #{date.day.ordinalize}, %Y") }, # => "April 25th, 2007"
:rfc822 => "%e %b %Y"
}
require 'date'
require 'active_support/inflector/methods'
require 'active_support/core_ext/date/zones'
require 'active_support/core_ext/module/remove_method'
def self.included(base) #:nodoc:
base.instance_eval do
alias_method :to_default_s, :to_s
alias_method :to_s, :to_formatted_s
alias_method :default_inspect, :inspect
alias_method :inspect, :readable_inspect
class Date
DATE_FORMATS = {
:short => "%e %b",
:long => "%B %e, %Y",
:db => "%Y-%m-%d",
:number => "%Y%m%d",
:long_ordinal => lambda { |date| date.strftime("%B #{ActiveSupport::Inflector.ordinalize(date.day)}, %Y") }, # => "April 25th, 2007"
:rfc822 => "%e %b %Y"
}
# Ruby 1.9 has Date#to_time which converts to localtime only.
remove_method :to_time if base.instance_methods.include?(:to_time)
# Ruby 1.9 has Date#to_time which converts to localtime only.
remove_possible_method :to_time
# Ruby 1.9 has Date#xmlschema which converts to a string without the time component.
remove_method :xmlschema if base.instance_methods.include?(:xmlschema)
end
end
# Ruby 1.9 has Date#xmlschema which converts to a string without the time component.
remove_possible_method :xmlschema
# Convert to a formatted string. See DATE_FORMATS for predefined formats.
#
# This method is aliased to <tt>to_s</tt>.
#
# ==== Examples
# date = Date.new(2007, 11, 10) # => Sat, 10 Nov 2007
#
# date.to_formatted_s(:db) # => "2007-11-10"
# date.to_s(:db) # => "2007-11-10"
#
# date.to_formatted_s(:short) # => "10 Nov"
# date.to_formatted_s(:long) # => "November 10, 2007"
# date.to_formatted_s(:long_ordinal) # => "November 10th, 2007"
# date.to_formatted_s(:rfc822) # => "10 Nov 2007"
#
# == Adding your own time formats to to_formatted_s
# You can add your own formats to the Date::DATE_FORMATS hash.
# Use the format name as the hash key and either a strftime string
# or Proc instance that takes a date argument as the value.
#
# # config/initializers/time_formats.rb
# Date::DATE_FORMATS[:month_and_year] = "%B %Y"
# Date::DATE_FORMATS[:short_ordinal] = lambda { |date| date.strftime("%B #{date.day.ordinalize}") }
def to_formatted_s(format = :default)
if formatter = DATE_FORMATS[format]
if formatter.respond_to?(:call)
formatter.call(self).to_s
else
strftime(formatter)
end
else
to_default_s
end
end
# Overrides the default inspect method with a human readable one, e.g., "Mon, 21 Feb 2005"
def readable_inspect
strftime("%a, %d %b %Y")
end
# A method to keep Time, Date and DateTime instances interchangeable on conversions.
# In this case, it simply returns +self+.
def to_date
self
end if RUBY_VERSION < '1.9'
# Converts a Date instance to a Time, where the time is set to the beginning of the day.
# The timezone can be either :local or :utc (default :local).
#
# ==== Examples
# date = Date.new(2007, 11, 10) # => Sat, 10 Nov 2007
#
# date.to_time # => Sat Nov 10 00:00:00 0800 2007
# date.to_time(:local) # => Sat Nov 10 00:00:00 0800 2007
#
# date.to_time(:utc) # => Sat Nov 10 00:00:00 UTC 2007
def to_time(form = :local)
::Time.send("#{form}_time", year, month, day)
end
# Converts a Date instance to a DateTime, where the time is set to the beginning of the day
# and UTC offset is set to 0.
#
# ==== Examples
# date = Date.new(2007, 11, 10) # => Sat, 10 Nov 2007
#
# date.to_datetime # => Sat, 10 Nov 2007 00:00:00 0000
def to_datetime
::DateTime.civil(year, month, day, 0, 0, 0, 0)
end if RUBY_VERSION < '1.9'
def xmlschema
to_time.xmlschema
end
# Convert to a formatted string. See DATE_FORMATS for predefined formats.
#
# This method is aliased to <tt>to_s</tt>.
#
# ==== Examples
# date = Date.new(2007, 11, 10) # => Sat, 10 Nov 2007
#
# date.to_formatted_s(:db) # => "2007-11-10"
# date.to_s(:db) # => "2007-11-10"
#
# date.to_formatted_s(:short) # => "10 Nov"
# date.to_formatted_s(:long) # => "November 10, 2007"
# date.to_formatted_s(:long_ordinal) # => "November 10th, 2007"
# date.to_formatted_s(:rfc822) # => "10 Nov 2007"
#
# == Adding your own time formats to to_formatted_s
# You can add your own formats to the Date::DATE_FORMATS hash.
# Use the format name as the hash key and either a strftime string
# or Proc instance that takes a date argument as the value.
#
# # config/initializers/time_formats.rb
# Date::DATE_FORMATS[:month_and_year] = "%B %Y"
# Date::DATE_FORMATS[:short_ordinal] = lambda { |date| date.strftime("%B #{date.day.ordinalize}") }
def to_formatted_s(format = :default)
if formatter = DATE_FORMATS[format]
if formatter.respond_to?(:call)
formatter.call(self).to_s
else
strftime(formatter)
end
else
to_default_s
end
end
alias_method :to_default_s, :to_s
alias_method :to_s, :to_formatted_s
# Overrides the default inspect method with a human readable one, e.g., "Mon, 21 Feb 2005"
def readable_inspect
strftime("%a, %d %b %Y")
end
alias_method :default_inspect, :inspect
alias_method :inspect, :readable_inspect
# A method to keep Time, Date and DateTime instances interchangeable on conversions.
# In this case, it simply returns +self+.
def to_date
self
end if RUBY_VERSION < '1.9'
# Converts a Date instance to a Time, where the time is set to the beginning of the day.
# The timezone can be either :local or :utc (default :local).
#
# ==== Examples
# date = Date.new(2007, 11, 10) # => Sat, 10 Nov 2007
#
# date.to_time # => Sat Nov 10 00:00:00 0800 2007
# date.to_time(:local) # => Sat Nov 10 00:00:00 0800 2007
#
# date.to_time(:utc) # => Sat Nov 10 00:00:00 UTC 2007
def to_time(form = :local)
::Time.send("#{form}_time", year, month, day)
end
# Converts a Date instance to a DateTime, where the time is set to the beginning of the day
# and UTC offset is set to 0.
#
# ==== Examples
# date = Date.new(2007, 11, 10) # => Sat, 10 Nov 2007
#
# date.to_datetime # => Sat, 10 Nov 2007 00:00:00 0000
def to_datetime
::DateTime.civil(year, month, day, 0, 0, 0, 0)
end if RUBY_VERSION < '1.9'
def iso8601
strftime('%F')
end if RUBY_VERSION < '1.9'
alias_method :rfc3339, :iso8601 if RUBY_VERSION < '1.9'
def xmlschema
to_time_in_current_zone.xmlschema
end
end

View File

@@ -0,0 +1,33 @@
# Date memoizes some instance methods using metaprogramming to wrap
# the methods with one that caches the result in an instance variable.
#
# If a Date is frozen but the memoized method hasn't been called, the
# first call will result in a frozen object error since the memo
# instance variable is uninitialized.
#
# Work around by eagerly memoizing before the first freeze.
#
# Ruby 1.9 uses a preinitialized instance variable so it's unaffected.
# This hack is as close as we can get to feature detection:
if RUBY_VERSION < '1.9'
require 'date'
begin
::Date.today.freeze.jd
rescue => frozen_object_error
if frozen_object_error.message =~ /frozen/
class Date #:nodoc:
def freeze
unless frozen?
self.class.private_instance_methods(false).each do |m|
if m.to_s =~ /\A__\d+__\Z/
instance_variable_set(:"@#{m}", [send(m)])
end
end
end
super
end
end
end
end
end

View File

@@ -0,0 +1,14 @@
require 'date'
require 'active_support/core_ext/time/zones'
class Date
# Converts Date to a TimeWithZone in the current zone if Time.zone or Time.zone_default
# is set, otherwise converts Date to a Time via Date#to_time
def to_time_in_current_zone
if ::Time.zone
::Time.zone.local(year, month, day)
else
to_time
end
end
end

View File

@@ -1,12 +0,0 @@
require 'date'
require 'active_support/core_ext/time/behavior'
require 'active_support/core_ext/time/zones'
require 'active_support/core_ext/date_time/calculations'
require 'active_support/core_ext/date_time/conversions'
class DateTime
include ActiveSupport::CoreExtensions::Time::Behavior
include ActiveSupport::CoreExtensions::Time::Zones
include ActiveSupport::CoreExtensions::DateTime::Calculations
include ActiveSupport::CoreExtensions::DateTime::Conversions
end

View File

@@ -0,0 +1,13 @@
require 'active_support/core_ext/object/acts_like'
class DateTime
# Duck-types as a Date-like class. See Object#acts_like?.
def acts_like_date?
true
end
# Duck-types as a Time-like class. See Object#acts_like?.
def acts_like_time?
true
end
end

View File

@@ -1,126 +1,143 @@
require 'rational'
require 'rational' unless RUBY_VERSION >= '1.9.2'
module ActiveSupport #:nodoc:
module CoreExtensions #:nodoc:
module DateTime #:nodoc:
# Enables the use of time calculations within DateTime itself
module Calculations
def self.included(base) #:nodoc:
base.extend ClassMethods
class DateTime
class << self
# DateTimes aren't aware of DST rules, so use a consistent non-DST offset when creating a DateTime with an offset in the local zone
def local_offset
::Time.local(2012).utc_offset.to_r / 86400
end
base.class_eval do
alias_method :compare_without_coercion, :<=>
alias_method :<=>, :compare_with_coercion
end
end
module ClassMethods
# DateTimes aren't aware of DST rules, so use a consistent non-DST offset when creating a DateTime with an offset in the local zone
def local_offset
::Time.local(2007).utc_offset.to_r / 86400
end
def current
::Time.zone_default ? ::Time.zone.now.to_datetime : ::Time.now.to_datetime
end
end
# Tells whether the DateTime object's datetime lies in the past
def past?
self < ::DateTime.current
end
# Tells whether the DateTime object's datetime lies in the future
def future?
self > ::DateTime.current
end
# Seconds since midnight: DateTime.now.seconds_since_midnight
def seconds_since_midnight
self.sec + (self.min * 60) + (self.hour * 3600)
end
# Returns a new DateTime where one or more of the elements have been changed according to the +options+ parameter. The time options
# (hour, minute, sec) reset cascadingly, so if only the hour is passed, then minute and sec is set to 0. If the hour and
# minute is passed, then sec is set to 0.
def change(options)
::DateTime.civil(
options[:year] || self.year,
options[:month] || self.month,
options[:day] || self.day,
options[:hour] || self.hour,
options[:min] || (options[:hour] ? 0 : self.min),
options[:sec] || ((options[:hour] || options[:min]) ? 0 : self.sec),
options[:offset] || self.offset,
options[:start] || self.start
)
end
# Uses Date to provide precise Time calculations for years, months, and days.
# The +options+ parameter takes a hash with any of these keys: <tt>:years</tt>,
# <tt>:months</tt>, <tt>:weeks</tt>, <tt>:days</tt>, <tt>:hours</tt>,
# <tt>:minutes</tt>, <tt>:seconds</tt>.
def advance(options)
d = to_date.advance(options)
datetime_advanced_by_date = change(:year => d.year, :month => d.month, :day => d.day)
seconds_to_advance = (options[:seconds] || 0) + (options[:minutes] || 0) * 60 + (options[:hours] || 0) * 3600
seconds_to_advance == 0 ? datetime_advanced_by_date : datetime_advanced_by_date.since(seconds_to_advance)
end
# Returns a new DateTime representing the time a number of seconds ago
# Do not use this method in combination with x.months, use months_ago instead!
def ago(seconds)
self.since(-seconds)
end
# Returns a new DateTime representing the time a number of seconds since the instance time
# Do not use this method in combination with x.months, use months_since instead!
def since(seconds)
self + Rational(seconds.round, 86400)
end
alias :in :since
# Returns a new DateTime representing the start of the day (0:00)
def beginning_of_day
change(:hour => 0)
end
alias :midnight :beginning_of_day
alias :at_midnight :beginning_of_day
alias :at_beginning_of_day :beginning_of_day
# Returns a new DateTime representing the end of the day (23:59:59)
def end_of_day
change(:hour => 23, :min => 59, :sec => 59)
end
# Adjusts DateTime to UTC by adding its offset value; offset is set to 0
#
# Example:
#
# DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)) # => Mon, 21 Feb 2005 10:11:12 -0600
# DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)).utc # => Mon, 21 Feb 2005 16:11:12 +0000
def utc
new_offset(0)
end
alias_method :getutc, :utc
# Returns true if offset == 0
def utc?
offset == 0
end
# Returns the offset value in seconds
def utc_offset
(offset * 86400).to_i
end
# Layers additional behavior on DateTime#<=> so that Time and ActiveSupport::TimeWithZone instances can be compared with a DateTime
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)
end
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 ? ::Time.zone.now.to_datetime : ::Time.now.to_datetime
end
end
# Tells whether the DateTime object's datetime lies in the past
def past?
self < ::DateTime.current
end
# Tells whether the DateTime object's datetime lies in the future
def future?
self > ::DateTime.current
end
# Seconds since midnight: DateTime.now.seconds_since_midnight
def seconds_since_midnight
sec + (min * 60) + (hour * 3600)
end
# Returns a new DateTime where one or more of the elements have been changed according to the +options+ parameter. The time options
# (hour, minute, sec) reset cascadingly, so if only the hour is passed, then minute and sec is set to 0. If the hour and
# minute is passed, then sec is set to 0.
def change(options)
::DateTime.civil(
options[:year] || year,
options[:month] || month,
options[:day] || day,
options[:hour] || hour,
options[:min] || (options[:hour] ? 0 : min),
options[:sec] || ((options[:hour] || options[:min]) ? 0 : sec),
options[:offset] || offset,
options[:start] || start
)
end
# Uses Date to provide precise Time calculations for years, months, and days.
# The +options+ parameter takes a hash with any of these keys: <tt>:years</tt>,
# <tt>:months</tt>, <tt>:weeks</tt>, <tt>:days</tt>, <tt>:hours</tt>,
# <tt>:minutes</tt>, <tt>:seconds</tt>.
def advance(options)
d = to_date.advance(options)
datetime_advanced_by_date = change(:year => d.year, :month => d.month, :day => d.day)
seconds_to_advance = (options[:seconds] || 0) + (options[:minutes] || 0) * 60 + (options[:hours] || 0) * 3600
seconds_to_advance == 0 ? datetime_advanced_by_date : datetime_advanced_by_date.since(seconds_to_advance)
end
# Returns a new DateTime representing the time a number of seconds ago
# Do not use this method in combination with x.months, use months_ago instead!
def ago(seconds)
since(-seconds)
end
# Returns a new DateTime representing the time a number of seconds since the instance time
# Do not use this method in combination with x.months, use months_since instead!
def since(seconds)
self + Rational(seconds.round, 86400)
end
alias :in :since
# Returns a new DateTime representing the start of the day (0:00)
def beginning_of_day
change(:hour => 0)
end
alias :midnight :beginning_of_day
alias :at_midnight :beginning_of_day
alias :at_beginning_of_day :beginning_of_day
# Returns a new DateTime representing the end of the day (23:59:59)
def end_of_day
change(:hour => 23, :min => 59, :sec => 59)
end
# Returns a new DateTime representing the start of the hour (hh:00:00)
def beginning_of_hour
change(:min => 0)
end
alias :at_beginning_of_hour :beginning_of_hour
# Returns a new DateTime representing the end of the hour (hh:59:59)
def end_of_hour
change(: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:
#
# DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)) # => Mon, 21 Feb 2005 10:11:12 -0600
# DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)).utc # => Mon, 21 Feb 2005 16:11:12 +0000
def utc
new_offset(0)
end
alias_method :getutc, :utc
# Returns true if offset == 0
def utc?
offset == 0
end
# Returns the offset value in seconds
def utc_offset
(offset * 86400).to_i
end
# Layers additional behavior on DateTime#<=> so that Time and ActiveSupport::TimeWithZone instances can be compared with a DateTime
def <=>(other)
super other.to_datetime
end
end

Some files were not shown because too many files have changed in this diff Show More