Compare commits

..

46 Commits

Author SHA1 Message Date
Mislav Marohnić
3766b1b377 github35 2014-01-13 13:58:57 -08:00
Mislav Marohnić
d3f87776a3 Merge pull request #41 from github/disable-generated-id
Disable auto-generated form field IDs by passing nil for "id" attribute
2014-01-13 13:57:38 -08:00
Mislav Marohnić
18c7c1f753 Disable auto-generated form field IDs by passing nil for "id" attribute
Previously it was not possible to opt out of auto-generated ID values
for various form fields.
2014-01-13 13:22:06 -08:00
Aman Gupta
f63b0340ff github34 2014-01-08 21:04:30 -08:00
Aman Gupta
7224ee1419 Merge pull request #37 from github/erb-freeze
Freeze ERB string literals
2014-01-08 20:33:01 -08:00
Aman Gupta
0c52ae6df3 Merge pull request #39 from github/write-fragment-fix
Fix fragment caching in mixed encoding scenario
2014-01-08 20:32:40 -08:00
Aman Gupta
f8b7cd2df7 Merge pull request #40 from github/ruby-2.1
Ruby 2.1
2014-01-08 20:32:12 -08:00
Aman Gupta
c73ba86136 use new 2.1 api 2014-01-08 18:03:55 -08:00
Aman Gupta
98fa5dd465 build on ruby 2.1 2014-01-08 17:46:13 -08:00
Mislav Marohnić
fa41bedf6b Don't rely on default encoding always being ASCII-8BIT 2014-01-08 17:41:17 -08:00
Aman Gupta
0a8282c557 freeze literals 2014-01-08 17:28:31 -08:00
Mislav Marohnić
d4a4facfcc Add test for extracting the cache fragment with mixed encodings 2014-01-08 17:12:18 -08:00
Aman Gupta
dd4146854a Fix fragment caching in mixed encodings scenario
To reduce ambiguity between char- and byte-based operations, explicitly
do byte operations when extracting the fragment that needs to be cached.
2014-01-08 16:35:55 -08:00
Charlie Somerville
cedf026a14 bump version to github33 2013-12-30 15:45:48 +11:00
Charlie Somerville
7ac3b0fa4f Merge pull request #34 from github/remove-cgi
Remove CGI support
2013-12-29 19:56:48 -08:00
Charlie Somerville
31cd7ea26d remove this NCGI stuff 2013-12-30 14:29:27 +11:00
Charlie Somerville
df387ab385 remove FastCGI crap 2013-12-30 14:28:24 +11:00
Charlie Somerville
0118959601 remove the webrick server 2013-12-30 14:26:08 +11:00
Charlie Somerville
83448c7de5 remove dispatch.rb and gateway.cgi 2013-12-30 14:23:00 +11:00
Charlie Somerville
8f99d00868 require properly 2013-12-30 14:23:00 +11:00
Charlie Somerville
987b61bd1d kill QueryExtension, it's more dead junk 2013-12-30 14:15:55 +11:00
Charlie Somerville
f05e54a9f3 remove stdinput monkey patch 2013-12-30 14:15:51 +11:00
Charlie Somerville
b9918117bb delete ActionController::CGIHandler and CgiRequest 2013-12-30 14:11:07 +11:00
Charlie Somerville
42f85d118d don't autoload CGIHandler and CgiRequest 2013-12-30 14:10:28 +11:00
Charlie Somerville
acb182d094 @output is never used anywhere, kill it 2013-12-30 14:09:20 +11:00
Charlie Somerville
6e0fcb788d remove CGI from the dispatcher 2013-12-30 14:09:00 +11:00
Charlie Somerville
fed4fafa8a Merge pull request #33 from github/dont-reload-middleware-stack-every-request
Don't reload middleware stack every request
2013-12-29 19:07:59 -08:00
Charlie Somerville
f699184047 test that we never call build_middleware_stack after initialization 2013-12-30 13:59:18 +11:00
Charlie Somerville
55d6a9f2df don't reload the middleware stack every request in development 2013-12-30 13:53:48 +11:00
Ted Nyman
e5bebc01a8 Merge pull request #32 from github/bump-to-github32
Bump to 2.3.14.github32
2013-12-03 14:53:14 -08:00
Ted Nyman
a019f07a39 Bump to 2.3.14.github32 2013-12-03 14:50:02 -08:00
Ted Nyman
d13866d75d Merge pull request #30 from github/CVE-2013-6417
CVE-2013-6417
2013-12-03 14:46:53 -08:00
Nathan Witmer
dfa2f469a4 Merge pull request #31 from github/currency-security-fix
CVE-2013-6415: Escape the unit value provided to number_to_currency
2013-12-03 14:41:51 -08:00
Nathan Witmer
bf0d43bb77 Only escape value if present 2013-12-03 14:47:38 -07:00
Nathan Witmer
72cebbcb59 Escape the unit value provided to number_to_currency
Fixes CVE-2013-6415.

Previously the values were trusted blindly allowing for potential XSS attacks.

This is different from the original upstream patch for 3.x in that return values
from other number helper methods are not marked as html_safe, so the html
escaping always applies. This requires applications to explicitly set .html_safe
on unit strings and number separators when calling number_to_currency.
2013-12-03 14:32:26 -07:00
Ted Nyman
379dd9071c Documentation for #deep_munge 2013-12-03 13:24:11 -08:00
Ted Nyman
a743f17dbd #deep_munge for CVE-2013-6417 2013-12-03 13:23:02 -08:00
Charlie Somerville
25b896611d Merge pull request #29 from github/tzinfo-json
Load timezone data from one big marshalled file
2013-12-03 00:38:50 -08:00
Charlie Somerville
b988837359 load definitions from a marshalled file 2013-12-03 19:32:36 +11:00
Charlie Somerville
890aff3b9d use vendored tzinfo 2013-12-03 18:10:11 +11:00
Charlie Somerville
c0124ba8f3 bump RAILS_VERSION 2013-12-02 20:43:27 +11:00
Charlie Somerville
455cd8c060 Merge pull request #28 from github/dont-turn-constant-names-into-strings
Don't turn constant names into strings prematurely
2013-12-02 01:27:18 -08:00
Charlie Somerville
5d322ad957 delete Module#local_constant_names 2013-12-02 20:09:05 +11:00
Charlie Somerville
3b6b4578c4 don't return anything interesting from require or load_with_new_constant_marking 2013-12-02 19:51:45 +11:00
Charlie Somerville
981016be60 call local_constants instead of local_constant_names 2013-12-02 19:40:28 +11:00
Aman Gupta
3c1e01068b faster String#blank? regex 2013-11-21 13:53:47 -08:00
619 changed files with 32138 additions and 19963 deletions

101
Gemfile
View File

@@ -1,101 +0,0 @@
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,7 @@
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
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

View File

@@ -1 +1 @@
2.3.14.github30
2.3.14.github35

View File

@@ -14,5 +14,4 @@ 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/all'
require 'active_support'
rescue LoadError
activesupport_path = "#{File.dirname(__FILE__)}/../../activesupport/lib"
if File.directory?(activesupport_path)
$:.unshift activesupport_path
require 'active_support/all'
require 'active_support'
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, CGIHandler, CgiRequest, Request, Response, Http::Headers, UrlRewriter, UrlWriter]
[Base, Request, Response, Http::Headers, UrlRewriter, UrlWriter]
end
autoload :Base, 'action_controller/base'
@@ -99,10 +99,6 @@ 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

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

View File

@@ -1,10 +1,6 @@
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,4 +1,6 @@
require 'delegate'
require 'cgi'
require 'cgi/cookie'
CGI.module_eval { remove_const "Cookie" }
@@ -24,7 +26,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

@@ -1,22 +0,0 @@
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

@@ -1,24 +0,0 @@
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

@@ -1,77 +0,0 @@
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,11 +22,6 @@ 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.
@@ -42,13 +37,7 @@ module ActionController
end
def run_prepare_callbacks
if defined?(Rails) && Rails.logger
logger = Rails.logger
else
logger = Logger.new($stderr)
end
new(logger).send :run_callbacks, :prepare_dispatch
new.send :run_callbacks, :prepare_dispatch
end
def reload_application
@@ -75,10 +64,8 @@ module ActionController
include ActiveSupport::Callbacks
define_callbacks :prepare_dispatch, :before_dispatch, :after_dispatch
# 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
def initialize
build_middleware_stack
end
def dispatch
@@ -96,21 +83,11 @@ 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

@@ -423,13 +423,13 @@ EOM
# Override Rack's GET method to support indifferent access
def GET
@env["action_controller.request.query_parameters"] ||= normalize_parameters(super)
@env["action_controller.request.query_parameters"] ||= deep_munge(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"] ||= normalize_parameters(super)
@env["action_controller.request.request_parameters"] ||= deep_munge(normalize_parameters(super) || {})
end
alias_method :request_parameters, :POST
@@ -469,6 +469,22 @@ 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] ||= SecureRandom.base64(32)
session[:_csrf_token] ||= ActiveSupport::SecureRandom.base64(32)
end
def protect_against_forgery?

View File

@@ -212,7 +212,7 @@ module ActionController
end
def generate_sid
SecureRandom.hex(16)
ActiveSupport::SecureRandom.hex(16)
end
def load_session(env)

View File

@@ -201,7 +201,7 @@ module ActionController
if secret.length < SECRET_MIN_LENGTH
raise ArgumentError, "Secret should be something secure, " +
"like \"#{SecureRandom.hex(16)}\". The value you " +
"like \"#{ActiveSupport::SecureRandom.hex(16)}\". The value you " +
"provided, \"#{secret}\", is shorter than the minimum length " +
"of #{SECRET_MIN_LENGTH} characters"
end
@@ -213,7 +213,7 @@ module ActionController
end
def generate_sid
SecureRandom.hex(16)
ActiveSupport::SecureRandom.hex(16)
end
def destroy(env)

View File

@@ -1,3 +1,5 @@
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

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

View File

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

View File

@@ -85,11 +85,13 @@ module ActionView
separator = '' if precision == 0
begin
format.gsub(/%n/, number_with_precision(number,
value = number_with_precision(number,
:precision => precision,
:delimiter => delimiter,
:separator => separator)
).gsub(/%u/, unit).html_safe
value = ERB::Util.html_escape(value) if value
unit = ERB::Util.html_escape(unit)
format.gsub(/%n/, value).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 << "';"
src << "'.freeze;"
@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}';"
src << "@output_buffer.safe_append='#{"\n" * @newline_pending}'.freeze;"
@newline_pending = 0
end
end

View File

@@ -1,9 +1,7 @@
begin
old, $VERBOSE = $VERBOSE, nil
require File.expand_path('../../../load_paths', __FILE__)
ensure
$VERBOSE = old
end
$:.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__)
require 'rubygems'
require 'yaml'

View File

@@ -622,6 +622,19 @@ 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).twice
dispatcher.expects(:build_middleware_stack).never
dispatcher.call(nil)
Reloader.default_lock.unlock
dispatcher.call(nil)

View File

@@ -5,7 +5,6 @@ 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"
SecureRandom.stubs(:base64).returns(@token)
ActiveSupport::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
SecureRandom.stubs(:base64).returns(@token + '<=?')
ActiveSupport::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"
SecureRandom.stubs(:base64).returns(@token)
ActiveSupport::SecureRandom.stubs(:base64).returns(@token)
end
def test_should_not_render_form_with_token_tag

View File

@@ -175,6 +175,10 @@ 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
@@ -274,6 +278,11 @@ 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

@@ -24,9 +24,10 @@ 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 => "&pound;", :separator => ",", :delimiter => ""}))
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("$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 => "K&#269;", :format => "%n %u"}))
assert_equal("1,234,567,890.50 K&#269;", number_to_currency("1234567890.50", {:unit => raw("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

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

View File

@@ -22,12 +22,12 @@
#++
begin
require 'active_support/all'
require 'active_support'
rescue LoadError
activesupport_path = "#{File.dirname(__FILE__)}/../../activesupport/lib"
if File.directory?(activesupport_path)
$:.unshift activesupport_path
require 'active_support/all'
require 'active_support'
end
end
@@ -56,7 +56,6 @@ 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,12 +2487,6 @@ 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

@@ -1,25 +0,0 @@
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,4 +1,5 @@
require 'active_support/json'
require 'active_support/core_ext/module/model_naming'
module ActiveRecord #:nodoc:
module Serialization

View File

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

View File

@@ -11,7 +11,4 @@ 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/all'
require 'active_support'
rescue IOError
end

View File

@@ -0,0 +1,30 @@
#!/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-2011 David Heinemeier Hansson
# Copyright (c) 2005 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,62 +21,40 @@
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#++
require 'securerandom'
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
def self.load_all!
[Dependencies, Deprecation, Gzip, MessageVerifier, Multibyte, SecureRandom, TimeWithZone]
end
self.load_all_hooks = []
on_load_all do
[Dependencies, Deprecation, Gzip, MessageVerifier, Multibyte]
end
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'
end
require "active_support/dependencies/autoload"
require "active_support/version"
require 'active_support/vendor'
require 'active_support/core_ext'
require 'active_support/dependencies'
require 'active_support/json'
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"
I18n.load_path << "#{File.dirname(__FILE__)}/active_support/locale/en.yml"

View File

@@ -1,3 +1,8 @@
# 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,43 +1,27 @@
module ActiveSupport
# 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.
# 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.
#
# 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.
# 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!
#
# ==== 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 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
# Returns the backtrace after all filters and silencers has been run against it. Filters run first, then silencers.
def clean(backtrace)
silence(filter(backtrace))
end
# Adds a filter from the block provided. Each line in the backtrace will be mapped against this filter.
@@ -50,8 +34,8 @@ module ActiveSupport
@filters << block
end
# Adds a silencer from the block provided. If the silencer returns true for a given line, it will be excluded from
# the clean backtrace.
# Adds a silencer from the block provided. If the silencer returns true for a given line, it'll be excluded from the
# clean backtrace.
#
# Example:
#
@@ -62,37 +46,26 @@ 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 one of the libraries you use.
# you suspect a bug in 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,54 +1,33 @@
require 'active_support/deprecation'
begin
require 'base64'
rescue LoadError
# 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
# 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
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
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)
# 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
end

View File

@@ -1,55 +0,0 @@
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,9 +1,4 @@
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
@@ -30,69 +25,62 @@ module ActiveSupport
def silence(temporary_level = ERROR)
if silencer
begin
logger = self.class.new @log_dest.dup, temporary_level
yield logger
old_logger_level, self.level = level, temporary_level
yield self
ensure
logger.close
self.level = old_logger_level
end
else
yield self
end
end
deprecate :silence
attr_accessor :level
attr_reader :auto_flushing
deprecate :auto_flushing
def initialize(log, level = DEBUG)
@log_dest = log
@level = level
@buffer = {}
@auto_flushing = 1
@guard = Mutex.new
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
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])
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)
@log.add(severity, message, progname, &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
end
# Dynamically add methods such as:
# def info
# def warn
# def debug
Severity.constants.each do |severity|
for severity in Severity.constants
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
@@ -101,25 +89,45 @@ Automatic directory creation for '#{log}' is deprecated. Please make sure the d
# 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
end
deprecate :flush
@guard.synchronize do
unless buffer.empty?
old_buffer = buffer
@log.write(old_buffer.join)
end
def respond_to?(method, include_private = false)
return false if method.to_s == "flush"
super
# 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
end
def close
@log.close
flush
@log.close if @log.respond_to?(:close)
@log = nil
end
private
def open_logfile(log)
Logger.new log
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
end
end

View File

@@ -1,6 +0,0 @@
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,99 +1,76 @@
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 :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]
autoload :CompressedMemCacheStore, 'active_support/cache/compressed_mem_cache_store'
module Strategy
autoload :LocalCache, 'active_support/cache/strategy/local_cache'
end
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
# 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)
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
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"]}/"
end
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
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
end.to_s
end
expanded_cache_key
end
# An abstract cache store class. There are multiple cache store
@@ -102,64 +79,28 @@ module ActiveSupport
# ActiveSupport::Cache::MemCacheStore. MemCacheStore is currently the most
# popular cache store for large production websites.
#
# 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.
# 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.
#
# 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
cattr_accessor :logger, :instance_writer => true
attr_reader :silence, :logger_off
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
# Silence the logger within a block.
alias silence? silence
alias logger_off? logger_off
def mute
previous_silence, @silence = defined?(@silence) && @silence, true
yield
@@ -167,27 +108,18 @@ 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), 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.
# 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.
#
# cache.write("today", "Monday")
# cache.fetch("today") # => "Monday"
#
#
# cache.fetch("city") # => nil
# cache.fetch("city") do
# "Duckburgh"
@@ -200,107 +132,42 @@ 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_entry, and calls #write_entry on a cache miss.
# Internally, #fetch calls #read, and calls #write on a cache miss.
# +options+ will be passed to the #read and #write calls.
#
# 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:
# 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:
#
# cache = ActiveSupport::Cache::MemCacheStore.new
# cache.fetch("foo", :force => true, :raw => true) do
# :bar
# cache.fetch("foo", :force => true, :expires_in => 5.seconds) do
# "bar"
# end
# cache.fetch("foo") # => "bar"
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
# 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)
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)
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
end
end
@@ -308,330 +175,73 @@ module ActiveSupport
# the cache with the given key, then that data is returned. Otherwise,
# nil is returned.
#
# 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
# 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
# Read multiple values at once from the cache. Options can be passed
# in the last argument.
# Writes the given value to the cache, with the given key.
#
# Some cache implementation may optimize this method.
# 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:
#
# 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
# 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
# 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
def delete(key, options = nil)
log("delete", key, options)
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")
log("delete matched", matcher.inspect, options)
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")
def exist?(key, options = nil)
log("exist?", key, options)
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
def increment(key, amount = 1)
log("incrementing", key, amount)
if num = read(key)
write(key, num + amount)
else
@value = Marshal.dump(value)
if should_compress?(@value, options)
@value = Zlib::Deflate.deflate(@value)
@compressed = true
end
nil
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
def decrement(key, amount = 1)
log("decrementing", key, amount)
if num = read(key)
write(key, num - amount)
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
nil
end
end
private
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
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?
end
end
end

View File

@@ -0,0 +1,20 @@
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

@@ -0,0 +1,14 @@
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,170 +1,64 @@
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
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
def initialize(cache_path)
@cache_path = cache_path
end
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)})
def read(name, options = nil)
super
File.open(real_file_path(name), 'rb') { |f| Marshal.load(f) } rescue nil
end
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
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
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 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
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)
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)
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
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
def exist?(name, options = nil)
super
File.exist?(real_file_path(name))
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
def real_file_path(name)
'%s/%s.cache' % [@cache_path, name.gsub('?', '.').gsub(':', '.')]
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.in?(EXCLUDED_DIRS)
next if d == "." || d == ".."
name = File.join(dir, d)
if File.directory?(name)
search_dir(name, &callback)

View File

@@ -1,27 +1,19 @@
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'
require 'memcache'
module ActiveSupport
module Cache
# A cache store implementation which stores data in Memcached:
# http://memcached.org/
# http://www.danga.com/memcached/
#
# 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 comes back up.
#
# MemCacheStore implements the Strategy::LocalCache strategy which implements
# an in-memory cache inside of a block.
# 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).
class MemCacheStore < Store
module Response # :nodoc:
STORED = "STORED\r\n"
@@ -31,12 +23,10 @@ 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:11211"] if addresses.empty?
addresses = ["localhost"] if addresses.empty?
MemCache.new(addresses, options)
end
@@ -54,153 +44,100 @@ 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
mem_cache_options = options.dup
UNIVERSAL_OPTIONS.each{|name| mem_cache_options.delete(name)}
@data = self.class.build_mem_cache(*(addresses + [mem_cache_options]))
@data = self.class.build_mem_cache(*addresses)
end
extend Strategy::LocalCache
extend LocalCacheWithRaw
end
# 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
# Reads multiple keys from the cache.
def read_multi(*keys)
@data.get_multi keys
end
# 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
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
rescue MemCache::MemCacheError
nil
end
# 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
def decrement(key, amount = 1) # :nodoc:
log("decrement", key, amount)
response = @data.decr(key, amount)
response == Response::NOT_FOUND ? nil : response
rescue MemCache::MemCacheError
nil
end
# 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)
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
@data.flush_all
end
# Get the statistics from the memcached servers.
def stats
@data.stats
end
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
def raw?(options)
options && options[:raw]
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,159 +1,58 @@
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 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.
# 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.
#
# 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 only able to store strings, but also arbitrary Ruby
# objects.
#
# MemoryStore is thread-safe.
# MemoryStore is not thread-safe. Use SynchronizedMemoryStore instead
# if you need thread-safety.
class MemoryStore < Store
def initialize(options = nil)
options ||= {}
super(options)
def initialize
@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 clear(options = nil)
synchronize do
@data.clear
@key_access.clear
@cache_size = 0
end
def read_multi(*names)
results = {}
names.each { |n| results[n] = read(n) }
results
end
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
def read(name, options = nil)
super
@data[name]
end
# 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
def write(name, value, options = nil)
super
@data[name] = value.freeze
end
# 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
def delete(name, options = nil)
super
@data.delete(name)
end
def delete_matched(matcher, options = nil)
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
super
@data.delete_if { |k,v| k =~ matcher }
end
def inspect # :nodoc:
"<##{self.class.name} entries=#{@data.size}, size=#{@cache_size}, options=#{@options.inspect}>"
def exist?(name, options = nil)
super
@data.has_key?(name)
end
# 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)
def clear
@data.clear
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

@@ -1,44 +0,0 @@
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,168 +1,103 @@
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
# 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
# this allows caching of the fact that there is nothing in the remote cache
NULL = 'remote_cache_store:null'
# Don't allow synchronizing since it isn't thread safe,
def synchronize # :nodoc:
yield
end
def clear(options = nil)
@data.clear
end
def read_entry(key, options)
@data[key]
end
def write_entry(key, value, options)
@data[key] = value
true
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
Thread.current[thread_local_key] = MemoryStore.new
yield
ensure
Thread.current[thread_local_key] = nil
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)
@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
end
end
def clear(options = nil) # :nodoc:
local_cache.clear(options) if local_cache
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
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 cleanup(options = nil) # :nodoc:
local_cache.clear(options) if local_cache
def delete(key, options = nil)
local_cache.mute { local_cache.write(key, NULL) } 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
def exist(key, options = nil)
value = local_cache.read(key) if local_cache
if value == NULL
false
elsif value
true
else
super
end
end
def delete_entry(key, options) # :nodoc:
local_cache.delete_entry(key, options) if local_cache
super
def increment(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 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_#{object_id}".gsub(/[\/-]/, '_').to_sym
@thread_local_key ||= "#{self.class.name.underscore}_local_cache".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

@@ -0,0 +1,47 @@
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,626 +1,279 @@
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 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.
# Callbacks are hooks into the lifecycle of an object that allow you to trigger logic
# before or after an alteration of the object state.
#
# 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+).
# Mixing in this module allows you to define callbacks in your class.
#
# 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
# Example:
# class Storage
# include ActiveSupport::Callbacks
# define_callbacks :save
#
# def save
# run_callbacks :save do
# puts "- save"
# end
# end
# define_callbacks :before_save, :after_save
# end
#
# class PersonRecord < Record
# set_callback :save, :before, :saving_message
# class ConfigStorage < Storage
# before_save :saving_message
# def saving_message
# puts "saving..."
# end
#
# set_callback :save, :after do |object|
# after_save do |object|
# puts "saved"
# end
#
# def save
# run_callbacks(:before_save)
# puts "- save"
# run_callbacks(:after_save)
# end
# end
#
# person = PersonRecord.new
# person.save
# config = ConfigStorage.new
# config.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
extend Concern
included do
extend ActiveSupport::DescendantsTracker
end
# Runs the callbacks for the given event.
#
# Calls the before and around callbacks in the order they were set, yields
# the block (if given one), and then runs the after callbacks in reverse order.
# Optionally accepts a key, which will be used to compile an optimized callback
# method for each key. See +ClassMethods.define_callbacks+ for more information.
#
# If the callback chain was halted, returns +false+. Otherwise returns the result
# of the block, or +true+ if no block is given.
#
# run_callbacks :save do
# save
# end
#
def run_callbacks(kind, *args, &block)
send("_run_#{kind}_callbacks", *args, &block)
end
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
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
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 run(object, options = {}, &terminator)
enumerator = options[:enumerator] || :each
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 name
chain.name
end
def next_id
@@_callback_sequence += 1
end
def matches?(_kind, _filter)
@kind == _kind && @filter == _filter
end
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}
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
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
# 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
# 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]))
def self.extract_options(*methods, &block)
methods.flatten!
options = methods.extract_options!
methods << block if block_given?
return methods, options
end
unless options[:unless].empty?
conditions << Array.wrap(_compile_filter(options[:unless])).map {|f| "!#{f}"}
def extract_options(*methods, &block)
self.class.extract_options(*methods, &block)
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
end
end
# An Array with a compile method
class CallbackChain < Array #:nodoc:#
attr_reader :name, :config
class Callback
attr_reader :kind, :method, :identifier, :options
def initialize(name, config)
@name = name
@config = {
:terminator => "false",
:rescuable => false,
:scope => [ :kind ]
}.merge(config)
def initialize(kind, method, options = {})
@kind = kind
@method = method
@identifier = options[:identifier]
@options = options
end
def compile(key=nil, object=nil)
method = []
method << "value = nil"
method << "halted = false"
each do |callback|
method << callback.start(key, object)
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
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
def eql?(other)
self == other
end
def dup
self.class.new(@kind, @method, @options.dup)
end
def hash
if @identifier
@identifier.hash
else
@method.hash
end
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."
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."
end
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
def self.included(base)
base.extend ClassMethods
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_attribute "_#{callback}_callbacks"
send("_#{callback}_callbacks=", CallbackChain.new(callback, config))
__define_runner(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
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,5 +1,3 @@
require 'active_support/deprecation'
module ActiveSupport
# A typical module looks like this:
#
@@ -7,7 +5,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
#
@@ -16,7 +14,8 @@ 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'
#
@@ -24,7 +23,7 @@ module ActiveSupport
# extend ActiveSupport::Concern
#
# included do
# scope :disabled, where(:disabled => true)
# scope :disabled, -> { where(disabled: true) }
# end
#
# module ClassMethods
@@ -32,8 +31,9 @@ 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,18 +70,17 @@ 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
# class_eval do
# def self.method_injected_by_foo
# ...
# end
# def self.method_injected_by_foo
# ...
# end
# end
# end
@@ -98,36 +97,38 @@ module ActiveSupport
# class Host
# include Bar # works, Bar takes care now of its dependencies
# end
#
module Concern
def self.extended(base)
base.instance_variable_set("@_dependencies", [])
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, [])
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")
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")
base.extend const_get(:ClassMethods) if const_defined?(:ClassMethods)
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

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

View File

@@ -1,8 +1,15 @@
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/prepend_and_append'
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

View File

@@ -1,46 +1,53 @@
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
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
# 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[1]</tt>.
def second
self[1]
end
# Equal to <tt>self[1]</tt>.
def second
self[1]
end
# Equal to <tt>self[2]</tt>.
def third
self[2]
end
# Equal to <tt>self[2]</tt>.
def third
self[2]
end
# Equal to <tt>self[3]</tt>.
def fourth
self[3]
end
# Equal to <tt>self[3]</tt>.
def fourth
self[3]
end
# Equal to <tt>self[4]</tt>.
def fifth
self[4]
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]
# Equal to <tt>self[41]</tt>. Also known as accessing "the reddit".
def forty_two
self[41]
end
end
end
end
end

View File

@@ -1,164 +1,197 @@
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'
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])
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
# 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
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.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(",")
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
else
to_default_s
# 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
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,29 +1,20 @@
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
{}
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
end
end
end

View File

@@ -1,100 +1,106 @@
require 'enumerator'
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
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
if block_given?
collection.each_slice(number) { |slice| yield(slice) }
else
groups = []
collection.each_slice(number) { |group| groups << group }
groups
end
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
# 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
# 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
# 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
inject([[]]) do |results, element|
if (using_block && yield(element)) || (value == element)
results << []
else
results.last << element
end
results
end
end
end
results
end
end
end

View File

@@ -1,7 +0,0 @@
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,30 +1,42 @@
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]
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
end
result[n..size] = []
result
end unless method_defined? :sample
end
end

View File

@@ -1,16 +0,0 @@
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

@@ -1,48 +0,0 @@
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

@@ -0,0 +1,24 @@
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

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

View File

@@ -0,0 +1,16 @@
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,6 +1,18 @@
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

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

View File

@@ -1,45 +0,0 @@
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

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

View File

@@ -0,0 +1,37 @@
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

@@ -0,0 +1,2 @@
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

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

View File

@@ -0,0 +1,23 @@
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'
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'
require 'active_support/core_ext/class/removal'
require 'active_support/core_ext/class/delegating_attributes'
require 'active_support/core_ext/class/attribute'

View File

@@ -1,10 +1,9 @@
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 by subclasses.
# Subclasses can change their own value and it will not impact parent class.
# Declare a class-level attribute whose value is inheritable and
# overwritable by subclasses:
#
# class Base
# class_attribute :setting
@@ -19,34 +18,12 @@ 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. 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]
# on a subclass as overriding the reader method.
#
# 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:
#
@@ -57,18 +34,11 @@ 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)
options = attrs.extract_options!
instance_reader = options.fetch(:instance_reader, true)
instance_writer = options.fetch(:instance_writer, true)
instance_writer = !attrs.last.is_a?(Hash) || attrs.pop[:instance_writer]
attrs.each do |name|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
@@ -80,36 +50,18 @@ 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
if instance_reader
remove_possible_method :#{name}
def #{name}
defined?(@#{name}) ? @#{name} : self.class.#{name}
end
def #{name}
defined?(@#{name}) ? @#{name} : singleton_class.#{name}
end
def #{name}?
!!#{name}
end
def #{name}?
!!#{name}
end
RUBY
attr_writer name if instance_writer
end
end
private
def singleton_class?
ancestors.first != self
end
end

View File

@@ -1,37 +1,16 @@
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
@@ -42,7 +21,7 @@ class Class
end
EOS
unless options[:instance_reader] == false || options[:instance_accessor] == false
unless options[:instance_reader] == false
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
def #{sym}
@@#{sym}
@@ -52,43 +31,6 @@ 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|
@@ -102,67 +44,18 @@ class Class
end
EOS
unless options[:instance_writer] == false || options[:instance_accessor] == false
unless options[:instance_writer] == false
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
def #{sym}=(obj)
@@#{sym} = obj
end
EOS
end
self.send("#{sym}=", yield) if block_given?
end
end
# 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)
def cattr_accessor(*syms)
cattr_reader(*syms)
cattr_writer(*syms, &blk)
cattr_writer(*syms)
end
end

View File

@@ -1,44 +1,47 @@
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'
# 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.
class Class
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
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(name, options = {})
singleton_class.send(:define_method, "#{name}=") do |value|
_stash_object_in_method(value, name, options[:instance_reader] != false)
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
send("#{name}=", nil)
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
end
def superclass_delegating_accessor(*names)
superclass_delegating_reader(*names)
superclass_delegating_writer(*names)
end
end

View File

@@ -1,3 +1,7 @@
# 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

@@ -0,0 +1,50 @@
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

@@ -1,36 +0,0 @@
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

@@ -0,0 +1,10 @@
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

@@ -1,8 +0,0 @@
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

@@ -0,0 +1,42 @@
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,276 +1,241 @@
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'
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
class Date
DAYS_INTO_WEEK = { :monday => 0, :tuesday => 1, :wednesday => 2, :thursday => 3, :friday => 4, :saturday => 5, :sunday => 6 }
base.instance_eval do
alias_method :plus_without_duration, :+
alias_method :+, :plus_with_duration
if RUBY_VERSION < '1.9'
undef :>>
alias_method :minus_without_duration, :-
alias_method :-, :minus_with_duration
end
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
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
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,106 +1,107 @@
require 'date'
require 'active_support/inflector/methods'
require 'active_support/core_ext/date/zones'
require 'active_support/core_ext/module/remove_method'
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"
}
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"
}
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
# Ruby 1.9 has Date#to_time which converts to localtime only.
remove_possible_method :to_time
# 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#xmlschema which converts to a string without the time component.
remove_possible_method :xmlschema
# 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
# 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)
# 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
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

@@ -1,33 +0,0 @@
# 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

@@ -1,14 +0,0 @@
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

@@ -0,0 +1,12 @@
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

@@ -1,13 +0,0 @@
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,143 +1,126 @@
require 'rational' unless RUBY_VERSION >= '1.9.2'
require 'rational'
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
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
# 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
base.class_eval do
alias_method :compare_without_coercion, :<=>
alias_method :<=>, :compare_with_coercion
end
end
# Tells whether the DateTime object's datetime lies in the past
def past?
self < ::DateTime.current
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
# Tells whether the DateTime object's datetime lies in the future
def future?
self > ::DateTime.current
end
def current
::Time.zone_default ? ::Time.zone.now.to_datetime : ::Time.now.to_datetime
end
end
# Seconds since midnight: DateTime.now.seconds_since_midnight
def seconds_since_midnight
sec + (min * 60) + (hour * 3600)
end
# Tells whether the DateTime object's datetime lies in the past
def past?
self < ::DateTime.current
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
# Tells whether the DateTime object's datetime lies in the future
def future?
self > ::DateTime.current
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
# 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 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 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
# 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
# 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 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 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 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 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 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 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 hour (hh:59:59)
def end_of_hour
change(:min => 59, :sec => 59)
end
# 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
# 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)
# 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
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

View File

@@ -1,103 +1,107 @@
require 'active_support/inflector/methods'
require 'active_support/core_ext/time/conversions'
require 'active_support/core_ext/date_time/calculations'
require 'active_support/values/time_zone'
module ActiveSupport #:nodoc:
module CoreExtensions #:nodoc:
module DateTime #:nodoc:
# Converting datetimes to formatted strings, dates, and times.
module Conversions
def self.append_features(base) #:nodoc:
base.class_eval do
alias_method :default_inspect, :inspect
alias_method :to_default_s, :to_s unless (instance_methods(false) & [:to_s, 'to_s']).empty?
class DateTime
# Ruby 1.9 has DateTime#to_time which internally relies on Time. We define our own #to_time which allows
# DateTimes outside the range of what can be created with Time.
remove_method :to_time if instance_methods.include?(:to_time)
# Ruby 1.9 has DateTime#to_time which internally relies on Time. We define our own #to_time which allows
# DateTimes outside the range of what can be created with Time.
remove_method :to_time if instance_methods.include?(:to_time)
end
# Convert to a formatted string. See Time::DATE_FORMATS for predefined formats.
#
# This method is aliased to <tt>to_s</tt>.
#
# === Examples
# datetime = DateTime.civil(2007, 12, 4, 0, 0, 0, 0) # => Tue, 04 Dec 2007 00:00:00 +0000
#
# datetime.to_formatted_s(:db) # => "2007-12-04 00:00:00"
# datetime.to_s(:db) # => "2007-12-04 00:00:00"
# datetime.to_s(:number) # => "20071204000000"
# datetime.to_formatted_s(:short) # => "04 Dec 00:00"
# datetime.to_formatted_s(:long) # => "December 04, 2007 00:00"
# datetime.to_formatted_s(:long_ordinal) # => "December 4th, 2007 00:00"
# datetime.to_formatted_s(:rfc822) # => "Tue, 04 Dec 2007 00:00:00 +0000"
#
# == Adding your own datetime formats to to_formatted_s
# DateTime formats are shared with Time. You can add your own to the
# Time::DATE_FORMATS hash. Use the format name as the hash key and
# either a strftime string or Proc instance that takes a time or
# datetime argument as the value.
#
# # config/initializers/time_formats.rb
# Time::DATE_FORMATS[:month_and_year] = "%B %Y"
# Time::DATE_FORMATS[:short_ordinal] = lambda { |time| time.strftime("%B #{time.day.ordinalize}") }
def to_formatted_s(format = :default)
if formatter = ::Time::DATE_FORMATS[format]
formatter.respond_to?(:call) ? formatter.call(self).to_s : strftime(formatter)
else
to_default_s
super
base.class_eval do
alias_method :to_s, :to_formatted_s
alias_method :inspect, :readable_inspect
end
end
# Convert to a formatted string. See Time::DATE_FORMATS for predefined formats.
#
# This method is aliased to <tt>to_s</tt>.
#
# === Examples
# datetime = DateTime.civil(2007, 12, 4, 0, 0, 0, 0) # => Tue, 04 Dec 2007 00:00:00 +0000
#
# datetime.to_formatted_s(:db) # => "2007-12-04 00:00:00"
# datetime.to_s(:db) # => "2007-12-04 00:00:00"
# datetime.to_s(:number) # => "20071204000000"
# datetime.to_formatted_s(:short) # => "04 Dec 00:00"
# datetime.to_formatted_s(:long) # => "December 04, 2007 00:00"
# datetime.to_formatted_s(:long_ordinal) # => "December 4th, 2007 00:00"
# datetime.to_formatted_s(:rfc822) # => "Tue, 04 Dec 2007 00:00:00 +0000"
#
# == Adding your own datetime formats to to_formatted_s
# DateTime formats are shared with Time. You can add your own to the
# Time::DATE_FORMATS hash. Use the format name as the hash key and
# either a strftime string or Proc instance that takes a time or
# datetime argument as the value.
#
# # config/initializers/time_formats.rb
# Time::DATE_FORMATS[:month_and_year] = "%B %Y"
# Time::DATE_FORMATS[:short_ordinal] = lambda { |time| time.strftime("%B #{time.day.ordinalize}") }
def to_formatted_s(format = :default)
return to_default_s unless formatter = ::Time::DATE_FORMATS[format]
formatter.respond_to?(:call) ? formatter.call(self).to_s : strftime(formatter)
end
# Returns the +utc_offset+ as an +HH:MM formatted string. Examples:
#
# datetime = DateTime.civil(2000, 1, 1, 0, 0, 0, Rational(-6, 24))
# datetime.formatted_offset # => "-06:00"
# datetime.formatted_offset(false) # => "-0600"
def formatted_offset(colon = true, alternate_utc_string = nil)
utc? && alternate_utc_string || utc_offset.to_utc_offset_s(colon)
end
# Overrides the default inspect method with a human readable one, e.g., "Mon, 21 Feb 2005 14:30:00 +0000"
def readable_inspect
to_s(:rfc822)
end
# Converts self to a Ruby Date object; time portion is discarded
def to_date
::Date.new(year, month, day)
end
# Attempts to convert self to a Ruby Time object; returns self if out of range of Ruby Time class
# If self has an offset other than 0, self will just be returned unaltered, since there's no clean way to map it to a Time
def to_time
self.offset == 0 ? ::Time.utc_time(year, month, day, hour, min, sec) : self
end
# To be able to keep Times, Dates and DateTimes interchangeable on conversions
def to_datetime
self
end
# Converts datetime to an appropriate format for use in XML
def xmlschema
strftime("%Y-%m-%dT%H:%M:%S%Z")
end if RUBY_VERSION < '1.9'
# Converts self to a floating-point number of seconds since the Unix epoch
def to_f
seconds_since_unix_epoch.to_f
end
# Converts self to an integer number of seconds since the Unix epoch
def to_i
seconds_since_unix_epoch.to_i
end
private
def seconds_since_unix_epoch
seconds_per_day = 86_400
(self - ::DateTime.civil(1970)) * seconds_per_day
end
end
end
end
alias_method :to_default_s, :to_s unless (instance_methods(false) & [:to_s, 'to_s']).empty?
alias_method :to_s, :to_formatted_s
# Returns the +utc_offset+ as an +HH:MM formatted string. Examples:
#
# datetime = DateTime.civil(2000, 1, 1, 0, 0, 0, Rational(-6, 24))
# datetime.formatted_offset # => "-06:00"
# datetime.formatted_offset(false) # => "-0600"
def formatted_offset(colon = true, alternate_utc_string = nil)
utc? && alternate_utc_string || ActiveSupport::TimeZone.seconds_to_utc_offset(utc_offset, colon)
end
# Overrides the default inspect method with a human readable one, e.g., "Mon, 21 Feb 2005 14:30:00 +0000".
def readable_inspect
to_s(:rfc822)
end
alias_method :default_inspect, :inspect
alias_method :inspect, :readable_inspect
# Converts self to a Ruby Date object; time portion is discarded.
def to_date
::Date.new(year, month, day)
end unless instance_methods(false).include?(:to_date)
# Attempts to convert self to a Ruby Time object; returns self if out of range of Ruby Time class.
# If self has an offset other than 0, self will just be returned unaltered, since there's no clean way to map it to a Time.
def to_time
self.offset == 0 ? ::Time.utc_time(year, month, day, hour, min, sec, sec_fraction * (RUBY_VERSION < '1.9' ? 86400000000 : 1000000)) : self
end
# To be able to keep Times, Dates and DateTimes interchangeable on conversions.
def to_datetime
self
end unless instance_methods(false).include?(:to_datetime)
def self.civil_from_format(utc_or_local, year, month=1, day=1, hour=0, min=0, sec=0)
offset = utc_or_local.to_sym == :local ? local_offset : 0
civil(year, month, day, hour, min, sec, offset)
end
# Converts datetime to an appropriate format for use in XML.
def xmlschema
strftime("%Y-%m-%dT%H:%M:%S%Z")
end unless instance_methods(false).include?(:xmlschema)
# Converts self to a floating-point number of seconds since the Unix epoch.
def to_f
seconds_since_unix_epoch.to_f
end
# Converts self to an integer number of seconds since the Unix epoch.
def to_i
seconds_since_unix_epoch.to_i
end
private
def seconds_since_unix_epoch
seconds_per_day = 86_400
(self - ::DateTime.civil(1970)) * seconds_per_day
end
end

View File

@@ -1,21 +0,0 @@
require 'active_support/core_ext/time/zones'
class DateTime
# Returns the simultaneous time in <tt>Time.zone</tt>.
#
# Time.zone = 'Hawaii' # => 'Hawaii'
# DateTime.new(2000).in_time_zone # => Fri, 31 Dec 1999 14:00:00 HST -10:00
#
# This method is similar to Time#localtime, except that it uses <tt>Time.zone</tt> as the local zone
# instead of the operating system's time zone.
#
# You can also pass in a TimeZone instance or string that identifies a TimeZone as an argument,
# and the conversion will be based on that zone instead of <tt>Time.zone</tt>.
#
# DateTime.new(2000).in_time_zone('Alaska') # => Fri, 31 Dec 1999 15:00:00 AKST -09:00
def in_time_zone(zone = ::Time.zone)
return self unless zone
ActiveSupport::TimeWithZone.new(utc? ? self : getutc, ::Time.find_zone!(zone))
end
end

View File

@@ -0,0 +1,43 @@
class Object
# Can you safely .dup this object?
# False for nil, false, true, symbols, and numbers; true otherwise.
def duplicable?
true
end
end
class NilClass #:nodoc:
def duplicable?
false
end
end
class FalseClass #:nodoc:
def duplicable?
false
end
end
class TrueClass #:nodoc:
def duplicable?
false
end
end
class Symbol #:nodoc:
def duplicable?
false
end
end
class Numeric #:nodoc:
def duplicable?
false
end
end
class Class #:nodoc:
def duplicable?
false
end
end

View File

@@ -1,41 +1,6 @@
require 'active_support/ordered_hash'
module Enumerable
# Ruby 1.8.7 introduces group_by, but the result isn't ordered. Override it.
remove_method(:group_by) if [].respond_to?(:group_by) && RUBY_VERSION < '1.9'
# Collect an enumerable into sets, grouped by the result of a block. Useful,
# for example, for grouping records by date.
#
# Example:
#
# latest_transcripts.group_by(&:day).each do |day, transcripts|
# p "#{day} -> #{transcripts.map(&:class).join(', ')}"
# end
# "2006-03-01 -> Transcript"
# "2006-02-28 -> Transcript"
# "2006-02-27 -> Transcript, Transcript"
# "2006-02-26 -> Transcript, Transcript"
# "2006-02-25 -> Transcript"
# "2006-02-24 -> Transcript, Transcript"
# "2006-02-23 -> Transcript"
def group_by
return to_enum :group_by unless block_given?
assoc = ActiveSupport::OrderedHash.new
each do |element|
key = yield(element)
if assoc.has_key?(key)
assoc[key] << element
else
assoc[key] = [element]
end
end
assoc
end unless [].respond_to?(:group_by)
# Calculates a sum from the elements. Examples:
#
# payments.sum { |p| p.price * p.tax_rate }
@@ -43,7 +8,7 @@ module Enumerable
#
# The latter is a shortcut for:
#
# payments.inject(0) { |sum, p| sum + p.price }
# payments.inject { |sum, p| sum + p.price }
#
# It can also calculate the sum without the use of a block.
#
@@ -59,69 +24,34 @@ module Enumerable
if block_given?
map(&block).sum(identity)
else
inject(:+) || identity
inject { |sum, element| sum + element } || identity
end
end
# Iterates over a collection, passing the current element *and* the
# +memo+ to the block. Handy for building up hashes or
# reducing collections down to one object. Examples:
#
# %w(foo bar).each_with_object({}) { |str, hsh| hsh[str] = str.upcase }
# # => {'foo' => 'FOO', 'bar' => 'BAR'}
#
# *Note* that you can't use immutable objects like numbers, true or false as
# the memo. You would think the following returns 120, but since the memo is
# never changed, it does not.
#
# (1..5).each_with_object(1) { |value, memo| memo *= value } # => 1
#
def each_with_object(memo)
return to_enum :each_with_object, memo unless block_given?
each do |element|
yield element, memo
end
memo
end unless [].respond_to?(:each_with_object)
# Convert an enumerable to a hash. Examples:
#
# people.index_by(&:login)
# => { "nextangle" => <Person ...>, "chade-" => <Person ...>, ...}
# people.index_by { |person| "#{person.first_name} #{person.last_name}" }
# => { "Chade- Fowlersburg-e" => <Person ...>, "David Heinemeier Hansson" => <Person ...>, ...}
#
#
def index_by
return to_enum :index_by unless block_given?
Hash[map { |elem| [yield(elem), elem] }]
end
# Returns true if the enumerable has more than 1 element. Functionally equivalent to enum.to_a.size > 1.
# Can be called with a block too, much like any?, so <tt>people.many? { |p| p.age > 26 }</tt> returns true if more than one person is over 26.
def many?
cnt = 0
if block_given?
any? do |element|
cnt += 1 if yield element
cnt > 1
end
else
any?{ (cnt += 1) > 1 }
inject({}) do |accum, elem|
accum[yield(elem)] = elem
accum
end
end
# Returns true if the collection has more than 1 element. Functionally equivalent to collection.size > 1.
# Works with a block too ala any?, so people.many? { |p| p.age > 26 } # => returns true if more than 1 person is over 26.
def many?(&block)
size = block_given? ? select(&block).size : self.size
size > 1
end
# The negative of the <tt>Enumerable#include?</tt>. Returns true if the collection does not include the object.
# The negative of the Enumerable#include?. Returns true if the collection does not include the object.
def exclude?(object)
!include?(object)
end
end
class Range #:nodoc:
# Optimize range sum to use arithmetic progression if a block is not given and
# we have a range of numeric values.
def sum(identity = 0)
return super if block_given? || !(first.instance_of?(Integer) && last.instance_of?(Integer))
actual_last = exclude_end? ? (last - 1) : last
(actual_last - first + 1) * (actual_last + first) / 2
end
end

View File

@@ -1,3 +1,45 @@
module ActiveSupport
FrozenObjectError = RUBY_VERSION < '1.9' ? TypeError : RuntimeError
if RUBY_VERSION >= '1.9'
FrozenObjectError = RuntimeError
else
FrozenObjectError = TypeError
end
end
# TODO: Turn all this into using the BacktraceCleaner.
class Exception # :nodoc:
def clean_message
Pathname.clean_within message
end
TraceSubstitutions = []
FrameworkStart = /action_controller\/dispatcher\.rb/.freeze
FrameworkRegexp = /generated|vendor|dispatch|ruby|script\/\w+/.freeze
def clean_backtrace
backtrace.collect do |line|
Pathname.clean_within(TraceSubstitutions.inject(line) do |result, (regexp, sub)|
result.gsub regexp, sub
end)
end
end
def application_backtrace
before_framework_frame = nil
before_application_frame = true
trace = clean_backtrace.reject do |line|
before_framework_frame ||= (line =~ FrameworkStart)
non_app_frame = (line =~ FrameworkRegexp)
before_application_frame = false unless non_app_frame
before_framework_frame || (non_app_frame && !before_application_frame)
end
# If we didn't find any application frames, return an empty app trace.
before_application_frame ? [] : trace
end
def framework_backtrace
clean_backtrace.grep FrameworkRegexp
end
end

View File

@@ -1,2 +1,5 @@
require 'active_support/core_ext/file/atomic'
require 'active_support/core_ext/file/path'
class File #:nodoc:
extend ActiveSupport::CoreExtensions::File::Atomic
end

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