Merge commit 'mainstream/master'

This commit is contained in:
Pratik Naik
2008-12-19 14:10:42 +00:00
55 changed files with 1861 additions and 1517 deletions

View File

@@ -12,7 +12,7 @@
*2.2.0 [RC1] (October 24th, 2008)*
* Add layout functionality to mailers [Pratik]
* Add layout functionality to mailers [Pratik Naik]
Mailer layouts behaves just like controller layouts, except layout names need to
have '_mailer' postfix for them to be automatically picked up.
@@ -24,7 +24,7 @@
* Less verbose mail logging: just recipients for :info log level; the whole email for :debug only. #8000 [iaddict, Tarmo Tänav]
* Updated TMail to version 1.2.1 [raasdnil]
* Updated TMail to version 1.2.1 [Mikel Lindsaar]
* Fixed that you don't have to call super in ActionMailer::TestCase#setup #10406 [jamesgolick]
@@ -36,7 +36,7 @@
*2.0.1* (December 7th, 2007)
* Update ActionMailer so it treats ActionView the same way that ActionController does. Closes #10244 [rick]
* Update ActionMailer so it treats ActionView the same way that ActionController does. Closes #10244 [Rick Olson]
* Pass the template_root as an array as ActionView's view_path
* Request templates with the "#{mailer_name}/#{action}" as opposed to just "#{action}"
@@ -45,11 +45,11 @@
* Update README to use new smtp settings configuration API. Closes #10060 [psq]
* Allow ActionMailer subclasses to individually set their delivery method (so two subclasses can have different delivery methods) #10033 [zdennis]
* Allow ActionMailer subclasses to individually set their delivery method (so two subclasses can have different delivery methods) #10033 [Zach Dennis]
* Update TMail to v1.1.0. Use an updated version of TMail if available. [mikel]
* Update TMail to v1.1.0. Use an updated version of TMail if available. [Mikel Lindsaar]
* Introduce a new base test class for testing Mailers. ActionMailer::TestCase [Koz]
* Introduce a new base test class for testing Mailers. ActionMailer::TestCase [Michael Koziarski]
* Fix silent failure of rxml templates. #9879 [jstewart]
@@ -84,7 +84,7 @@
*1.3.2* (February 5th, 2007)
* Deprecate server_settings renaming it to smtp_settings, add sendmail_settings to allow you to override the arguments to and location of the sendmail executable. [Koz]
* Deprecate server_settings renaming it to smtp_settings, add sendmail_settings to allow you to override the arguments to and location of the sendmail executable. [Michael Koziarski]
*1.3.1* (January 16th, 2007)
@@ -104,7 +104,7 @@
* Tighten rescue clauses. #5985 [james@grayproductions.net]
* Automatically included ActionController::UrlWriter, such that URL generation can happen within ActionMailer controllers. [DHH]
* Automatically included ActionController::UrlWriter, such that URL generation can happen within ActionMailer controllers. [David Heinemeier Hansson]
* Replace Reloadable with Reloadable::Deprecated. [Nicholas Seckar]

File diff suppressed because it is too large Load Diff

View File

@@ -56,6 +56,7 @@ module ActionController
autoload :Integration, 'action_controller/integration'
autoload :IntegrationTest, 'action_controller/integration'
autoload :Layout, 'action_controller/layout'
autoload :Lock, 'action_controller/lock'
autoload :MiddlewareStack, 'action_controller/middleware_stack'
autoload :MimeResponds, 'action_controller/mime_responds'
autoload :PolymorphicRoutes, 'action_controller/polymorphic_routes'

View File

@@ -1160,13 +1160,8 @@ module ActionController #:nodoc:
def reset_session #:doc:
request.reset_session
@_session = request.session
#http://rails.lighthouseapp.com/projects/8994/tickets/1558-memory-problem-on-reset_session-in-around_filter#ticket-1558-1
#MRI appears to have a GC related memory leak to do with the finalizer that is defined on CGI::Session
ObjectSpace.undefine_finalizer(@_session)
response.session = @_session
end
private
def render_for_file(template_path, status = nil, layout = nil, locals = {}) #:nodoc:
logger.info("Rendering #{template_path}" + (status ? " (#{status})" : '')) if logger

View File

@@ -67,6 +67,8 @@ module ActionController #:nodoc:
cookie = @cookies[name.to_s]
if cookie && cookie.respond_to?(:value)
cookie.size > 1 ? cookie.value : cookie.value[0]
else
cookie
end
end

View File

@@ -2,8 +2,6 @@ module ActionController
# Dispatches requests to the appropriate controller and takes care of
# reloading the app after each request when Dependencies.load? is true.
class Dispatcher
@@guard = Mutex.new
class << self
def define_dispatcher_callbacks(cache_classes)
unless cache_classes
@@ -46,40 +44,49 @@ module ActionController
cattr_accessor :middleware
self.middleware = MiddlewareStack.new do |middleware|
middleware.use "ActionController::Lock", :if => lambda {
!ActionController::Base.allow_concurrency
}
middleware.use "ActionController::Failsafe"
middleware.use "ActionController::SessionManagement::Middleware"
["ActionController::Session::CookieStore",
"ActionController::Session::MemCacheStore",
"ActiveRecord::SessionStore"].each do |store|
middleware.use(store, ActionController::Base.session_options,
:if => lambda {
if session_store = ActionController::Base.session_store
session_store.name == store
end
}
)
end
end
include ActiveSupport::Callbacks
define_callbacks :prepare_dispatch, :before_dispatch, :after_dispatch
# DEPRECATE: Remove arguments
# DEPRECATE: Remove arguments, since they are only used by CGI
def initialize(output = $stdout, request = nil, response = nil)
@output, @request, @response = output, request, response
@output = output
@app = @@middleware.build(lambda { |env| self.dup._call(env) })
end
def dispatch_unlocked
def dispatch
begin
run_callbacks :before_dispatch
handle_request
controller = Routing::Routes.recognize(@request)
controller.process(@request, @response).to_a
rescue Exception => exception
failsafe_rescue exception
if controller ||= (::ApplicationController rescue Base)
controller.process_with_exception(@request, @response, exception).to_a
else
raise exception
end
ensure
run_callbacks :after_dispatch, :enumerator => :reverse_each
end
end
def dispatch
if ActionController::Base.allow_concurrency
dispatch_unlocked
else
@@guard.synchronize do
dispatch_unlocked
end
end
end
# DEPRECATE: Remove CGI support
def dispatch_cgi(cgi, session_options)
CGIHandler.dispatch_cgi(self, cgi, @output)
@@ -118,22 +125,8 @@ module ActionController
def checkin_connections
# Don't return connection (and peform implicit rollback) if this request is a part of integration test
# TODO: This callback should have direct access to env
return if @request.key?("action_controller.test")
return if @request.key?("rack.test")
ActiveRecord::Base.clear_active_connections!
end
protected
def handle_request
@controller = Routing::Routes.recognize(@request)
@controller.process(@request, @response).out
end
def failsafe_rescue(exception)
if @controller ||= (::ApplicationController rescue Base)
@controller.process_with_exception(@request, @response, exception).out
else
raise exception
end
end
end
end

View File

@@ -11,7 +11,7 @@ module ActionController
@app.call(env)
rescue Exception => exception
# Reraise exception in test environment
if env["action_controller.test"]
if env["rack.test"]
raise exception
else
failsafe_response(exception)

View File

@@ -276,6 +276,7 @@ module ActionController
"SCRIPT_NAME" => "",
"REQUEST_URI" => path,
"PATH_INFO" => path,
"HTTP_HOST" => host,
"REMOTE_ADDR" => remote_addr,
"CONTENT_TYPE" => "application/x-www-form-urlencoded",
@@ -290,7 +291,7 @@ module ActionController
"rack.multiprocess" => true,
"rack.run_once" => false,
"action_controller.test" => true
"rack.test" => true
)
(headers || {}).each do |key, value|
@@ -310,16 +311,6 @@ module ActionController
status, headers, body = app.call(env)
@request_count += 1
if @controller = ActionController::Base.last_instantiation
@request = @controller.request
@response = @controller.response
# Decorate the response with the standard behavior of the
# TestResponse so that things like assert_response can be
# used in integration tests.
@response.extend(TestResponseBehavior)
end
@html_document = nil
@status = status.to_i
@@ -335,6 +326,22 @@ module ActionController
@body = ""
body.each { |part| @body << part }
if @controller = ActionController::Base.last_instantiation
@request = @controller.request
@response = @controller.response
else
# Decorate responses from Rack Middleware and Rails Metal
# as an AbstractResponse for the purposes of integration testing
@response = AbstractResponse.new
@response.headers = @headers.merge('Status' => status.to_s)
@response.body = @body
end
# Decorate the response with the standard behavior of the
# TestResponse so that things like assert_response can be
# used in integration tests.
@response.extend(TestResponseBehavior)
return @status
rescue MultiPartNeededException
boundary = "----------XnJLe9ZIbbGUYtzPQJ16u1"

View File

@@ -0,0 +1,16 @@
module ActionController
class Lock
FLAG = 'rack.multithread'.freeze
def initialize(app, lock = Mutex.new)
@app, @lock = app, lock
end
def call(env)
old, env[FLAG] = env[FLAG], false
@lock.synchronize { @app.call(env) }
ensure
env[FLAG] = old
end
end
end

View File

@@ -1,19 +1,39 @@
module ActionController
class MiddlewareStack < Array
class Middleware
attr_reader :klass, :args, :block
attr_reader :args, :block
def initialize(klass, *args, &block)
if klass.is_a?(Class)
@klass = klass
@klass = klass
options = args.extract_options!
if options.has_key?(:if)
@conditional = options.delete(:if)
else
@klass = klass.to_s.constantize
@conditional = true
end
args << options unless options.empty?
@args = args
@block = block
end
def klass
if @klass.is_a?(Class)
@klass
else
@klass.to_s.constantize
end
end
def active?
if @conditional.respond_to?(:call)
@conditional.call
else
@conditional
end
end
def ==(middleware)
case middleware
when Middleware
@@ -50,8 +70,12 @@ module ActionController
push(middleware)
end
def active
find_all { |middleware| middleware.active? }
end
def build(app)
reverse.inject(app) { |a, e| e.build(a) }
active.reverse.inject(app) { |a, e| e.build(a) }
end
end
end

View File

@@ -83,11 +83,7 @@ module ActionController #:nodoc:
@status || super
end
def out(&block)
# Nasty hack because CGI sessions are closed after the normal
# prepare! statement
set_cookies!
def to_a(&block)
@block = block
@status = headers.delete("Status")
if [204, 304].include?(status.to_i)
@@ -97,7 +93,6 @@ module ActionController #:nodoc:
[status, headers.to_hash, self]
end
end
alias to_a out
def each(&callback)
if @body.respond_to?(:call)
@@ -132,7 +127,7 @@ module ActionController #:nodoc:
convert_language!
convert_expires!
set_status!
# set_cookies!
set_cookies!
end
private

View File

@@ -104,7 +104,7 @@ module ActionController #:nodoc:
status = interpret_status(status_code)
path = "#{Rails.public_path}/#{status[0,3]}.html"
if File.exist?(path)
render :file => path, :status => status
render :file => path, :status => status, :content_type => Mime::HTML
else
head status
end

View File

@@ -56,7 +56,7 @@ module ActionController
result = recognize_optimized(path, environment) and return result
# Route was not recognized. Try to find out why (maybe wrong verb).
allows = HTTP_METHODS.select { |verb| routes.find { |r| r.recognize(path, :method => verb) } }
allows = HTTP_METHODS.select { |verb| routes.find { |r| r.recognize(path, environment.merge(:method => verb)) } }
if environment[:method] && !HTTP_METHODS.include?(environment[:method])
raise NotImplemented.new(*allows)

View File

@@ -11,6 +11,7 @@ module ActionController
class SessionHash < Hash
def initialize(by, env)
super()
@by = by
@env = env
@loaded = false
@@ -21,6 +22,13 @@ module ActionController
@id
end
def session_id
ActiveSupport::Deprecation.warn(
"ActionController::Session::AbstractStore::SessionHash#session_id" +
"has been deprecated.Please use #id instead.", caller)
id
end
def [](key)
load! unless @loaded
super
@@ -37,6 +45,13 @@ module ActionController
h
end
def data
ActiveSupport::Deprecation.warn(
"ActionController::Session::AbstractStore::SessionHash#data" +
"has been deprecated.Please use #to_hash instead.", caller)
to_hash
end
private
def load!
@id, session = @by.send(:load_session, @env)
@@ -46,7 +61,7 @@ module ActionController
end
DEFAULT_OPTIONS = {
:key => 'rack.session',
:key => '_session_id',
:path => '/',
:domain => nil,
:expire_after => nil,
@@ -56,6 +71,18 @@ module ActionController
}
def initialize(app, options = {})
# Process legacy CGI options
options = options.symbolize_keys
if options.has_key?(:session_path)
options[:path] = options.delete(:session_path)
end
if options.has_key?(:session_key)
options[:key] = options.delete(:session_key)
end
if options.has_key?(:session_http_only)
options[:httponly] = options.delete(:session_http_only)
end
@app = app
@default_options = DEFAULT_OPTIONS.merge(options)
@key = @default_options[:key]

View File

@@ -41,9 +41,11 @@ module ActionController
SECRET_MIN_LENGTH = 30 # characters
DEFAULT_OPTIONS = {
:domain => nil,
:path => "/",
:expire_after => nil
:key => '_session_id',
:domain => nil,
:path => "/",
:expire_after => nil,
:httponly => false
}.freeze
ENV_SESSION_KEY = "rack.session".freeze
@@ -56,6 +58,18 @@ module ActionController
def initialize(app, options = {})
options = options.dup
# Process legacy CGI options
options = options.symbolize_keys
if options.has_key?(:session_path)
options[:path] = options.delete(:session_path)
end
if options.has_key?(:session_key)
options[:key] = options.delete(:session_key)
end
if options.has_key?(:session_http_only)
options[:httponly] = options.delete(:session_http_only)
end
@app = app
# The session_key option is required.
@@ -74,21 +88,12 @@ module ActionController
freeze
end
class SessionHash < AbstractStore::SessionHash
private
def load!
session = @by.send(:load_session, @env)
replace(session)
@loaded = true
end
end
def call(env)
session_data = SessionHash.new(self, env)
session_data = AbstractStore::SessionHash.new(self, env)
original_value = session_data.dup
env[ENV_SESSION_KEY] = session_data
env[ENV_SESSION_OPTIONS_KEY] = @default_options.dup
env[ENV_SESSION_OPTIONS_KEY] = @default_options
status, headers, body = @app.call(env)
@@ -142,17 +147,18 @@ module ActionController
def load_session(env)
request = Rack::Request.new(env)
session_data = request.cookies[@key]
unmarshal(session_data) || {}
data = unmarshal(session_data) || persistent_session_id!({})
[data[:session_id], data]
end
# Marshal a session hash into safe cookie data. Include an integrity hash.
def marshal(session)
@verifier.generate(session)
@verifier.generate( persistent_session_id!(session))
end
# Unmarshal cookie data to a hash and verify its integrity.
def unmarshal(cookie)
@verifier.verify(cookie) if cookie
persistent_session_id!(@verifier.verify(cookie)) if cookie
rescue ActiveSupport::MessageVerifier::InvalidSignature
nil
end
@@ -195,6 +201,26 @@ module ActionController
key = secret.respond_to?(:call) ? secret.call : secret
ActiveSupport::MessageVerifier.new(key, digest)
end
def generate_sid
ActiveSupport::SecureRandom.hex(16)
end
def persistent_session_id!(data)
(data ||= {}).merge!(inject_persistent_session_id(data))
end
def inject_persistent_session_id(data)
requires_session_id?(data) ? { :session_id => generate_sid } : {}
end
def requires_session_id?(data)
if data
data.respond_to?(:key?) && !data.key?(:session_id)
else
true
end
end
end
end
end

View File

@@ -6,35 +6,6 @@ module ActionController #:nodoc:
end
end
class Middleware
DEFAULT_OPTIONS = {
:path => "/",
:key => "_session_id",
:httponly => true,
}.freeze
def self.new(app)
cgi_options = ActionController::Base.session_options
options = cgi_options.symbolize_keys
options = DEFAULT_OPTIONS.merge(options)
if options.has_key?(:session_path)
options[:path] = options.delete(:session_path)
end
if options.has_key?(:session_key)
options[:key] = options.delete(:session_key)
end
if options.has_key?(:session_http_only)
options[:httponly] = options.delete(:session_http_only)
end
if store = ActionController::Base.session_store
store.new(app, options)
else # Sessions disabled
lambda { |env| app.call(env) }
end
end
end
module ClassMethods
# Set the session store to be used for keeping the session data between requests.
# By default, sessions are stored in browser cookies (<tt>:cookie_store</tt>),

View File

@@ -98,6 +98,10 @@ module ActionView #:nodoc:
end
private
def valid_extension?(extension)
Template.template_handler_extensions.include?(extension)
end
def find_full_path(path, load_paths)
load_paths = Array(load_paths) + [nil]
load_paths.each do |load_path|
@@ -111,11 +115,11 @@ module ActionView #:nodoc:
# [base_path, name, format, extension]
def split(file)
if m = file.match(/^(.*\/)?([^\.]+)\.?(\w+)?\.?(\w+)?\.?(\w+)?$/)
if Template.valid_extension?(m[5]) # Multipart formats
if valid_extension?(m[5]) # Multipart formats
[m[1], m[2], "#{m[3]}.#{m[4]}", m[5]]
elsif Template.valid_extension?(m[4]) # Single format
elsif valid_extension?(m[4]) # Single format
[m[1], m[2], m[3], m[4]]
elsif Template.valid_extension?(m[3]) # No format
elsif valid_extension?(m[3]) # No format
[m[1], m[2], nil, m[3]]
else # No extension
[m[1], m[2], m[3], nil]

View File

@@ -28,10 +28,6 @@ module ActionView #:nodoc:
@@template_handlers[extension.to_sym] = klass
end
def valid_extension?(extension)
template_handler_extensions.include?(extension) || init_path_for_extension(extension)
end
def template_handler_extensions
@@template_handlers.keys.map(&:to_s).sort
end
@@ -42,26 +38,7 @@ module ActionView #:nodoc:
end
def handler_class_for_extension(extension)
(extension && @@template_handlers[extension.to_sym] || autoload_handler_class(extension)) ||
@@default_template_handlers
(extension && @@template_handlers[extension.to_sym]) || @@default_template_handlers
end
private
def autoload_handler_class(extension)
return if Gem.loaded_specs[extension]
return unless init_path = init_path_for_extension(extension)
Gem.activate(extension)
load(init_path)
handler_class_for_extension(extension)
end
# Returns the path to the rails/init.rb file for the given extension,
# or nil if no gem provides it.
def init_path_for_extension(extension)
return unless spec = Gem.searcher.find(extension.to_s)
returning File.join(spec.full_gem_path, 'rails', 'init.rb') do |path|
return unless File.file?(path)
end
end
end
end

View File

@@ -50,7 +50,7 @@ class DispatcherTest < Test::Unit::TestCase
end
def test_failsafe_response
Dispatcher.any_instance.expects(:dispatch_unlocked).raises('b00m')
Dispatcher.any_instance.expects(:dispatch).raises('b00m')
ActionController::Failsafe.any_instance.expects(:log_failsafe_exception)
assert_nothing_raised do
@@ -96,7 +96,9 @@ class DispatcherTest < Test::Unit::TestCase
private
def dispatch(cache_classes = true)
Dispatcher.any_instance.stubs(:handle_request).returns([200, {}, 'response'])
controller = mock()
controller.stubs(:process).returns([200, {}, 'response'])
ActionController::Routing::Routes.stubs(:recognize).returns(controller)
Dispatcher.define_dispatcher_callbacks(cache_classes)
@dispatcher.call({})
end

View File

@@ -373,4 +373,35 @@ class IntegrationProcessTest < ActionController::IntegrationTest
end
end
class MetalTest < ActionController::IntegrationTest
class Poller
def self.call(env)
if env["PATH_INFO"] =~ /^\/success/
[200, {"Content-Type" => "text/plain"}, "Hello World!"]
else
[404, {"Content-Type" => "text/plain"}, '']
end
end
end
def setup
@integration_session = ActionController::Integration::Session.new(Poller)
end
def test_successful_get
get "/success"
assert_response 200
assert_response :success
assert_response :ok
assert_equal "Hello World!", response.body
end
def test_failed_get
get "/failure"
assert_response 404
assert_response :not_found
assert_equal '', response.body
end
end
end

View File

@@ -236,7 +236,7 @@ class RackResponseTest < BaseRackTest
@response.body = "Hello, World!"
@response.prepare!
status, headers, body = @response.out
status, headers, body = @response.to_a
assert_equal "200 OK", status
assert_equal({
"Content-Type" => "text/html; charset=utf-8",
@@ -257,7 +257,7 @@ class RackResponseTest < BaseRackTest
end
@response.prepare!
status, headers, body = @response.out
status, headers, body = @response.to_a
assert_equal "200 OK", status
assert_equal({"Content-Type" => "text/html; charset=utf-8", "Cache-Control" => "no-cache", "Set-Cookie" => []}, headers)
@@ -293,6 +293,6 @@ class RackResponseHeadersTest < BaseRackTest
private
def response_headers
@response.prepare!
@response.out[1]
@response.to_a[1]
end
end

View File

@@ -9,6 +9,8 @@ class CookieStoreTest < ActionController::IntegrationTest
CookieStoreApp = ActionController::Session::CookieStore.new(DispatcherApp,
:key => SessionKey, :secret => SessionSecret)
Verifier = ActiveSupport::MessageVerifier.new(SessionSecret, 'SHA1')
SignedBar = "BAh7BjoIZm9vIghiYXI%3D--" +
"fef868465920f415f2c0652d6910d3af288a0367"
@@ -17,9 +19,13 @@ class CookieStoreTest < ActionController::IntegrationTest
head :ok
end
def persistent_session_id
render :text => session[:session_id]
end
def set_session_value
session[:foo] = "bar"
head :ok
render :text => Marshal.dump(session.to_hash)
end
def get_session_value
@@ -83,7 +89,8 @@ class CookieStoreTest < ActionController::IntegrationTest
with_test_route_set do
get '/set_session_value'
assert_response :success
assert_equal ["_myapp_session=#{SignedBar}; path=/"],
session_payload = Verifier.generate( Marshal.load(response.body) )
assert_equal ["_myapp_session=#{session_payload}; path=/"],
headers['Set-Cookie']
end
end
@@ -132,6 +139,21 @@ class CookieStoreTest < ActionController::IntegrationTest
end
end
def test_persistent_session_id
with_test_route_set do
cookies[SessionKey] = SignedBar
get '/persistent_session_id'
assert_response :success
assert_equal response.body.size, 32
session_id = response.body
get '/persistent_session_id'
assert_equal session_id, response.body
reset!
get '/persistent_session_id'
assert_not_equal session_id, response.body
end
end
private
def with_test_route_set
with_routing do |set|

View File

@@ -1,84 +0,0 @@
# require 'abstract_unit'
#
# class SessionFixationTest < ActionController::IntegrationTest
# class TestController < ActionController::Base
# session :session_key => '_myapp_session_id',
# :secret => CGI::Session.generate_unique_id,
# :except => :default_session_key
#
# session :cookie_only => false,
# :only => :allow_session_fixation
#
# def default_session_key
# render :text => "default_session_key"
# end
#
# def custom_session_key
# render :text => "custom_session_key: #{params[:id]}"
# end
#
# def allow_session_fixation
# render :text => "allow_session_fixation"
# end
#
# def rescue_action(e) raise end
# end
#
# def setup
# @controller = TestController.new
# end
#
# def test_should_be_able_to_make_a_successful_request
# with_test_route_set do
# assert_nothing_raised do
# get '/custom_session_key', :id => "1"
# end
# assert_equal 'custom_session_key: 1', @controller.response.body
# assert_not_nil @controller.session
# end
# end
#
# def test_should_catch_session_fixation_attempt
# with_test_route_set do
# assert_raises(ActionController::RackRequest::SessionFixationAttempt) do
# get '/custom_session_key', :_myapp_session_id => "42"
# end
# assert_nil @controller.session
# end
# end
#
# def test_should_not_catch_session_fixation_attempt_when_cookie_only_setting_is_disabled
# with_test_route_set do
# assert_nothing_raised do
# get '/allow_session_fixation', :_myapp_session_id => "42"
# end
# assert !@controller.response.body.blank?
# assert_not_nil @controller.session
# end
# end
#
# def test_should_catch_session_fixation_attempt_with_default_session_key
# # using the default session_key is not possible with cookie store
# ActionController::Base.session_store = :p_store
#
# with_test_route_set do
# assert_raises ActionController::RackRequest::SessionFixationAttempt do
# get '/default_session_key', :_session_id => "42"
# end
# assert_nil @controller.response
# assert_nil @controller.session
# end
# end
#
# private
# def with_test_route_set
# with_routing do |set|
# set.draw do |map|
# map.with_options :controller => "session_fixation_test/test" do |c|
# c.connect "/:action"
# end
# end
# yield
# end
# end
# end

File diff suppressed because it is too large Load Diff

View File

@@ -204,9 +204,18 @@ module ActiveRecord
unless through_records.empty?
source = reflection.source_reflection.name
through_records.first.class.preload_associations(through_records, source)
through_records.each do |through_record|
add_preloaded_record_to_collection(id_to_record_map[through_record[through_primary_key].to_s],
reflection.name, through_record.send(source))
if through_reflection.macro == :belongs_to
rev_id_to_record_map, rev_ids = construct_id_map(records, through_primary_key)
rev_primary_key = through_reflection.klass.primary_key
through_records.each do |through_record|
add_preloaded_record_to_collection(rev_id_to_record_map[through_record[rev_primary_key].to_s],
reflection.name, through_record.send(source))
end
else
through_records.each do |through_record|
add_preloaded_record_to_collection(id_to_record_map[through_record[through_primary_key].to_s],
reflection.name, through_record.send(source))
end
end
end
else
@@ -307,6 +316,7 @@ module ActiveRecord
klasses_and_ids.each do |klass_and_id|
klass_name, id_map = *klass_and_id
next if id_map.empty?
klass = klass_name.constantize
table_name = klass.quoted_table_name

View File

@@ -1731,6 +1731,11 @@ module ActiveRecord
return sanitize_sql(sql)
end
def tables_in_string(string)
return [] if string.blank?
string.scan(/([\.a-zA-Z_]+).?\./).flatten
end
def conditions_tables(options)
# look in both sets of conditions
conditions = [scope(:find, :conditions), options[:conditions]].inject([]) do |all, cond|
@@ -1741,37 +1746,55 @@ module ActiveRecord
else all << cond
end
end
conditions.join(' ').scan(/([\.a-zA-Z_]+).?\./).flatten
tables_in_string(conditions.join(' '))
end
def order_tables(options)
order = [options[:order], scope(:find, :order) ].join(", ")
return [] unless order && order.is_a?(String)
order.scan(/([\.a-zA-Z_]+).?\./).flatten
tables_in_string(order)
end
def selects_tables(options)
select = options[:select]
return [] unless select && select.is_a?(String)
select.scan(/"?([\.a-zA-Z_]+)"?.?\./).flatten
tables_in_string(select)
end
def joined_tables(options)
scope = scope(:find)
joins = options[:joins]
merged_joins = scope && scope[:joins] && joins ? merge_joins(scope[:joins], joins) : (joins || scope && scope[:joins])
[table_name] + case merged_joins
when Symbol, Hash, Array
if array_of_strings?(merged_joins)
tables_in_string(merged_joins.join(' '))
else
join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, merged_joins, nil)
join_dependency.join_associations.collect {|join_association| [join_association.aliased_join_table_name, join_association.aliased_table_name]}.flatten.compact
end
else
tables_in_string(merged_joins)
end
end
# Checks if the conditions reference a table other than the current model table
def include_eager_conditions?(options, tables = nil)
((tables || conditions_tables(options)) - [table_name]).any?
def include_eager_conditions?(options, tables = nil, joined_tables = nil)
((tables || conditions_tables(options)) - (joined_tables || joined_tables(options))).any?
end
# Checks if the query order references a table other than the current model's table.
def include_eager_order?(options, tables = nil)
((tables || order_tables(options)) - [table_name]).any?
def include_eager_order?(options, tables = nil, joined_tables = nil)
((tables || order_tables(options)) - (joined_tables || joined_tables(options))).any?
end
def include_eager_select?(options)
(selects_tables(options) - [table_name]).any?
def include_eager_select?(options, joined_tables = nil)
(selects_tables(options) - (joined_tables || joined_tables(options))).any?
end
def references_eager_loaded_tables?(options)
include_eager_order?(options) || include_eager_conditions?(options) || include_eager_select?(options)
joined_tables = joined_tables(options)
include_eager_order?(options, nil, joined_tables) || include_eager_conditions?(options, nil, joined_tables) || include_eager_select?(options, joined_tables)
end
def using_limitable_reflections?(reflections)

View File

@@ -83,7 +83,11 @@ module ActiveRecord
def to_ary
load_target
@target.to_ary
if @target.is_a?(Array)
@target.to_ary
else
Array(@target)
end
end
def reset

View File

@@ -1828,7 +1828,7 @@ module ActiveRecord #:nodoc:
else
find(:#{finder}, options.merge(finder_options))
end
#{'result || raise(RecordNotFound)' if bang}
#{'result || raise(RecordNotFound, "Couldn\'t find #{name} with #{attributes.to_a.collect {|pair| "#{pair.first} = #{pair.second}"}.join(\', \')}")' if bang}
end
}, __FILE__, __LINE__
send(method_id, *arguments)

View File

@@ -308,6 +308,7 @@ module ActiveRecord
rows
end
# Executes a SQL query and returns a MySQL::Result object. Note that you have to free the Result object after you're done using it.
def execute(sql, name = nil) #:nodoc:
log(sql, name) { @connection.query(sql) }
rescue ActiveRecord::StatementInvalid => exception
@@ -414,7 +415,9 @@ module ActiveRecord
def tables(name = nil) #:nodoc:
tables = []
execute("SHOW TABLES", name).each { |field| tables << field[0] }
result = execute("SHOW TABLES", name)
result.each { |field| tables << field[0] }
result.free
tables
end
@@ -425,7 +428,8 @@ module ActiveRecord
def indexes(table_name, name = nil)#:nodoc:
indexes = []
current_index = nil
execute("SHOW KEYS FROM #{quote_table_name(table_name)}", name).each do |row|
result = execute("SHOW KEYS FROM #{quote_table_name(table_name)}", name)
result.each do |row|
if current_index != row[2]
next if row[2] == "PRIMARY" # skip the primary key
current_index = row[2]
@@ -434,13 +438,16 @@ module ActiveRecord
indexes.last.columns << row[4]
end
result.free
indexes
end
def columns(table_name, name = nil)#:nodoc:
sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
columns = []
execute(sql, name).each { |field| columns << MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") }
result = execute(sql, name)
result.each { |field| columns << MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") }
result.free
columns
end
@@ -521,9 +528,11 @@ module ActiveRecord
# Returns a table's primary key and belonging sequence.
def pk_and_sequence_for(table) #:nodoc:
keys = []
execute("describe #{quote_table_name(table)}").each_hash do |h|
result = execute("describe #{quote_table_name(table)}")
result.each_hash do |h|
keys << h["Field"]if h["Key"] == "PRI"
end
result.free
keys.length == 1 ? [keys.first, nil] : nil
end

View File

@@ -1,6 +1,7 @@
require "cases/helper"
require 'models/post'
require 'models/tagging'
require 'models/tag'
require 'models/comment'
require 'models/author'
require 'models/category'
@@ -145,7 +146,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_finding_with_includes_on_null_belongs_to_association_with_same_include_includes_only_once
post = posts(:welcome)
post.update_attributes!(:author => nil)
post = assert_queries(2) { Post.find(post.id, :include => {:author_with_address => :author_address}) } # find the post, then find the author which is null so no query for the address
post = assert_queries(1) { Post.find(post.id, :include => {:author_with_address => :author_address}) } # find the post, then find the author which is null so no query for the author or address
assert_no_queries do
assert_equal nil, post.author_with_address
end
@@ -705,4 +706,69 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_order_on_join_table_with_include_and_limit
assert_equal 5, Developer.find(:all, :include => 'projects', :order => 'developers_projects.joined_on DESC', :limit => 5).size
end
def test_eager_loading_with_order_on_joined_table_preloads
posts = assert_queries(2) do
Post.find(:all, :joins => :comments, :include => :author, :order => 'comments.id DESC')
end
assert_equal posts(:eager_other), posts[0]
assert_equal authors(:mary), assert_no_queries { posts[0].author}
end
def test_eager_loading_with_conditions_on_joined_table_preloads
posts = assert_queries(2) do
Post.find(:all, :select => 'distinct posts.*', :include => :author, :joins => [:comments], :conditions => "comments.body like 'Thank you%'", :order => 'posts.id')
end
assert_equal [posts(:welcome)], posts
assert_equal authors(:david), assert_no_queries { posts[0].author}
posts = assert_queries(2) do
Post.find(:all, :select => 'distinct posts.*', :include => :author, :joins => [:comments], :conditions => "comments.body like 'Thank you%'", :order => 'posts.id')
end
assert_equal [posts(:welcome)], posts
assert_equal authors(:david), assert_no_queries { posts[0].author}
posts = assert_queries(2) do
Post.find(:all, :include => :author, :joins => {:taggings => :tag}, :conditions => "tags.name = 'General'")
end
assert_equal posts(:welcome, :thinking), posts
posts = assert_queries(2) do
Post.find(:all, :include => :author, :joins => {:taggings => {:tag => :taggings}}, :conditions => "taggings_tags.super_tag_id=2")
end
assert_equal posts(:welcome, :thinking), posts
end
def test_eager_loading_with_conditions_on_string_joined_table_preloads
posts = assert_queries(2) do
Post.find(:all, :select => 'distinct posts.*', :include => :author, :joins => "INNER JOIN comments on comments.post_id = posts.id", :conditions => "comments.body like 'Thank you%'", :order => 'posts.id')
end
assert_equal [posts(:welcome)], posts
assert_equal authors(:david), assert_no_queries { posts[0].author}
posts = assert_queries(2) do
Post.find(:all, :select => 'distinct posts.*', :include => :author, :joins => ["INNER JOIN comments on comments.post_id = posts.id"], :conditions => "comments.body like 'Thank you%'", :order => 'posts.id')
end
assert_equal [posts(:welcome)], posts
assert_equal authors(:david), assert_no_queries { posts[0].author}
end
def test_eager_loading_with_select_on_joined_table_preloads
posts = assert_queries(2) do
Post.find(:all, :select => 'posts.*, authors.name as author_name', :include => :comments, :joins => :author, :order => 'posts.id')
end
assert_equal 'David', posts[0].author_name
assert_equal posts(:welcome).comments, assert_no_queries { posts[0].comments}
end
def test_eager_loading_with_conditions_on_join_model_preloads
authors = assert_queries(2) do
Author.find(:all, :include => :author_address, :joins => :comments, :conditions => "posts.title like 'Welcome%'")
end
assert_equal authors(:david), authors[0]
assert_equal author_addresses(:david_address), authors[0].author_address
end
end

View File

@@ -3,6 +3,9 @@ require 'models/post'
require 'models/person'
require 'models/reader'
require 'models/comment'
require 'models/tag'
require 'models/tagging'
require 'models/author'
class HasManyThroughAssociationsTest < ActiveRecord::TestCase
fixtures :posts, :readers, :people, :comments, :authors

View File

@@ -1,5 +1,6 @@
require "cases/helper"
require 'models/club'
require 'models/member_type'
require 'models/member'
require 'models/membership'
require 'models/sponsor'
@@ -7,7 +8,7 @@ require 'models/organization'
require 'models/member_detail'
class HasOneThroughAssociationsTest < ActiveRecord::TestCase
fixtures :members, :clubs, :memberships, :sponsors, :organizations
fixtures :member_types, :members, :clubs, :memberships, :sponsors, :organizations
def setup
@member = members(:groucho)
@@ -158,4 +159,18 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
assert @new_organization.members.include?(@member)
end
def test_preloading_has_one_through_on_belongs_to
assert_not_nil @member.member_type
@organization = organizations(:nsa)
@member_detail = MemberDetail.new
@member.member_detail = @member_detail
@member.organization = @organization
@member_details = assert_queries(3) do
MemberDetail.find(:all, :include => :member_type)
end
@new_detail = @member_details[0]
assert @new_detail.loaded_member_type?
assert_not_nil assert_no_queries { @new_detail.member_type }
end
end

View File

@@ -9,6 +9,8 @@ require 'active_record/test_case'
require 'active_record/fixtures'
require 'connection'
require 'cases/repair_helper'
# Show backtraces for deprecated behavior for quicker cleanup.
ActiveSupport::Deprecation.debug = true
@@ -60,6 +62,8 @@ end
class ActiveSupport::TestCase
include ActiveRecord::TestFixtures
include ActiveRecord::Testing::RepairHelper
self.fixture_path = FIXTURES_ROOT
self.use_instantiated_fixtures = false
self.use_transactional_fixtures = true

View File

@@ -0,0 +1,50 @@
module ActiveRecord
module Testing
module RepairHelper
def self.included(base)
base.class_eval do
extend ClassMethods
end
end
module Toolbox
def self.record_validations(*model_classes)
model_classes.inject({}) do |repair, klass|
repair[klass] ||= {}
[:validate, :validate_on_create, :validate_on_update].each do |callback|
the_callback = klass.instance_variable_get("@#{callback.to_s}_callbacks")
repair[klass][callback] = (the_callback.nil? ? nil : the_callback.dup)
end
repair
end
end
def self.reset_validations(recorded)
recorded.each do |klass, repairs|
[:validate, :validate_on_create, :validate_on_update].each do |callback|
klass.instance_variable_set("@#{callback.to_s}_callbacks", repairs[callback])
end
end
end
end
module ClassMethods
def repair_validations(*model_classes)
setup do
@validation_repairs = ActiveRecord::Testing::RepairHelper::Toolbox.record_validations(*model_classes)
end
teardown do
ActiveRecord::Testing::RepairHelper::Toolbox.reset_validations(@validation_repairs)
end
end
end
def repair_validations(*model_classes, &block)
validation_repairs = ActiveRecord::Testing::RepairHelper::Toolbox.record_validations(*model_classes)
return block.call
ensure
ActiveRecord::Testing::RepairHelper::Toolbox.reset_validations(validation_repairs)
end
end
end
end

View File

@@ -6,6 +6,8 @@ require 'models/person'
require 'models/developer'
require 'models/warehouse_thing'
require 'models/guid'
require 'models/owner'
require 'models/pet'
# The following methods in Topic are used in test_conditional_validation_*
class Topic
@@ -31,10 +33,6 @@ class UniqueReply < Reply
validates_uniqueness_of :content, :scope => 'parent_id'
end
class PlagiarizedReply < Reply
validates_acceptance_of :author_name
end
class SillyUniqueReply < UniqueReply
end
@@ -58,11 +56,9 @@ end
class ValidationsTest < ActiveRecord::TestCase
fixtures :topics, :developers, 'warehouse-things'
def setup
Topic.instance_variable_set("@validate_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
Topic.instance_variable_set("@validate_on_create_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
Topic.instance_variable_set("@validate_on_update_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
end
# Most of the tests mess with the validations of Topic, so lets repair it all the time.
# Other classes we mess with will be dealt with in the specific tests
repair_validations(Topic)
def test_single_field_validation
r = Reply.new
@@ -134,7 +130,7 @@ class ValidationsTest < ActiveRecord::TestCase
Reply.create!([ { "title" => "OK" }, { "title" => "Wrong Create" }])
end
end
def test_exception_on_create_bang_with_block
assert_raises(ActiveRecord::RecordInvalid) do
Reply.create!({ "title" => "OK" }) do |r|
@@ -142,7 +138,7 @@ class ValidationsTest < ActiveRecord::TestCase
end
end
end
def test_exception_on_create_bang_many_with_block
assert_raises(ActiveRecord::RecordInvalid) do
Reply.create!([{ "title" => "OK" }, { "title" => "Wrong Create" }]) do |r|
@@ -229,21 +225,16 @@ class ValidationsTest < ActiveRecord::TestCase
end
def test_validates_each
perform = true
hits = 0
Topic.validates_each(:title, :content, [:title, :content]) do |record, attr|
if perform
record.errors.add attr, 'gotcha'
hits += 1
end
record.errors.add attr, 'gotcha'
hits += 1
end
t = Topic.new("title" => "valid", "content" => "whatever")
assert !t.save
assert_equal 4, hits
assert_equal %w(gotcha gotcha), t.errors.on(:title)
assert_equal %w(gotcha gotcha), t.errors.on(:content)
ensure
perform = false
end
def test_no_title_confirmation
@@ -315,8 +306,12 @@ class ValidationsTest < ActiveRecord::TestCase
end
def test_validates_acceptance_of_as_database_column
reply = PlagiarizedReply.create("author_name" => "Dan Brown")
assert_equal "Dan Brown", reply["author_name"]
repair_validations(Reply) do
Reply.validates_acceptance_of(:author_name)
reply = Reply.create("author_name" => "Dan Brown")
assert_equal "Dan Brown", reply["author_name"]
end
end
def test_validates_acceptance_of_with_non_existant_table
@@ -372,22 +367,24 @@ class ValidationsTest < ActiveRecord::TestCase
end
def test_validate_uniqueness_with_scope
Reply.validates_uniqueness_of(:content, :scope => "parent_id")
repair_validations(Reply) do
Reply.validates_uniqueness_of(:content, :scope => "parent_id")
t = Topic.create("title" => "I'm unique!")
t = Topic.create("title" => "I'm unique!")
r1 = t.replies.create "title" => "r1", "content" => "hello world"
assert r1.valid?, "Saving r1"
r1 = t.replies.create "title" => "r1", "content" => "hello world"
assert r1.valid?, "Saving r1"
r2 = t.replies.create "title" => "r2", "content" => "hello world"
assert !r2.valid?, "Saving r2 first time"
r2 = t.replies.create "title" => "r2", "content" => "hello world"
assert !r2.valid?, "Saving r2 first time"
r2.content = "something else"
assert r2.save, "Saving r2 second time"
r2.content = "something else"
assert r2.save, "Saving r2 second time"
t2 = Topic.create("title" => "I'm unique too!")
r3 = t2.replies.create "title" => "r3", "content" => "hello world"
assert r3.valid?, "Saving r3"
t2 = Topic.create("title" => "I'm unique too!")
r3 = t2.replies.create "title" => "r3", "content" => "hello world"
assert r3.valid?, "Saving r3"
end
end
def test_validate_uniqueness_scoped_to_defining_class
@@ -406,27 +403,29 @@ class ValidationsTest < ActiveRecord::TestCase
end
def test_validate_uniqueness_with_scope_array
Reply.validates_uniqueness_of(:author_name, :scope => [:author_email_address, :parent_id])
repair_validations(Reply) do
Reply.validates_uniqueness_of(:author_name, :scope => [:author_email_address, :parent_id])
t = Topic.create("title" => "The earth is actually flat!")
t = Topic.create("title" => "The earth is actually flat!")
r1 = t.replies.create "author_name" => "jeremy", "author_email_address" => "jeremy@rubyonrails.com", "title" => "You're crazy!", "content" => "Crazy reply"
assert r1.valid?, "Saving r1"
r1 = t.replies.create "author_name" => "jeremy", "author_email_address" => "jeremy@rubyonrails.com", "title" => "You're crazy!", "content" => "Crazy reply"
assert r1.valid?, "Saving r1"
r2 = t.replies.create "author_name" => "jeremy", "author_email_address" => "jeremy@rubyonrails.com", "title" => "You're crazy!", "content" => "Crazy reply again..."
assert !r2.valid?, "Saving r2. Double reply by same author."
r2 = t.replies.create "author_name" => "jeremy", "author_email_address" => "jeremy@rubyonrails.com", "title" => "You're crazy!", "content" => "Crazy reply again..."
assert !r2.valid?, "Saving r2. Double reply by same author."
r2.author_email_address = "jeremy_alt_email@rubyonrails.com"
assert r2.save, "Saving r2 the second time."
r2.author_email_address = "jeremy_alt_email@rubyonrails.com"
assert r2.save, "Saving r2 the second time."
r3 = t.replies.create "author_name" => "jeremy", "author_email_address" => "jeremy_alt_email@rubyonrails.com", "title" => "You're wrong", "content" => "It's cubic"
assert !r3.valid?, "Saving r3"
r3 = t.replies.create "author_name" => "jeremy", "author_email_address" => "jeremy_alt_email@rubyonrails.com", "title" => "You're wrong", "content" => "It's cubic"
assert !r3.valid?, "Saving r3"
r3.author_name = "jj"
assert r3.save, "Saving r3 the second time."
r3.author_name = "jj"
assert r3.save, "Saving r3 the second time."
r3.author_name = "jeremy"
assert !r3.save, "Saving r3 the third time."
r3.author_name = "jeremy"
assert !r3.save, "Saving r3 the third time."
end
end
def test_validate_case_insensitive_uniqueness
@@ -523,10 +522,12 @@ class ValidationsTest < ActiveRecord::TestCase
end
def test_validate_uniqueness_with_columns_which_are_sql_keywords
Guid.validates_uniqueness_of :key
g = Guid.new
g.key = "foo"
assert_nothing_raised { !g.valid? }
repair_validations(Guid) do
Guid.validates_uniqueness_of :key
g = Guid.new
g.key = "foo"
assert_nothing_raised { !g.valid? }
end
end
def test_validate_straight_inheritance_uniqueness
@@ -648,10 +649,12 @@ class ValidationsTest < ActiveRecord::TestCase
end
def test_numericality_with_getter_method
Developer.validates_numericality_of( :salary )
developer = Developer.new("name" => "michael", "salary" => nil)
developer.instance_eval("def salary; read_attribute('salary') ? read_attribute('salary') : 100000; end")
assert developer.valid?
repair_validations(Developer) do
Developer.validates_numericality_of( :salary )
developer = Developer.new("name" => "michael", "salary" => nil)
developer.instance_eval("def salary; read_attribute('salary') ? read_attribute('salary') : 100000; end")
assert developer.valid?
end
end
def test_validates_length_of_with_allow_nil
@@ -684,10 +687,12 @@ class ValidationsTest < ActiveRecord::TestCase
end
def test_numericality_with_allow_nil_and_getter_method
Developer.validates_numericality_of( :salary, :allow_nil => true)
developer = Developer.new("name" => "michael", "salary" => nil)
developer.instance_eval("def salary; read_attribute('salary') ? read_attribute('salary') : 100000; end")
assert developer.valid?
repair_validations(Developer) do
Developer.validates_numericality_of( :salary, :allow_nil => true)
developer = Developer.new("name" => "michael", "salary" => nil)
developer.instance_eval("def salary; read_attribute('salary') ? read_attribute('salary') : 100000; end")
assert developer.valid?
end
end
def test_validates_exclusion_of
@@ -892,26 +897,30 @@ class ValidationsTest < ActiveRecord::TestCase
end
def test_validates_size_of_association
assert_nothing_raised { Topic.validates_size_of :replies, :minimum => 1 }
t = Topic.new('title' => 'noreplies', 'content' => 'whatever')
assert !t.save
assert t.errors.on(:replies)
reply = t.replies.build('title' => 'areply', 'content' => 'whateveragain')
assert t.valid?
repair_validations(Owner) do
assert_nothing_raised { Owner.validates_size_of :pets, :minimum => 1 }
o = Owner.new('name' => 'nopets')
assert !o.save
assert o.errors.on(:pets)
pet = o.pets.build('name' => 'apet')
assert o.valid?
end
end
def test_validates_size_of_association_using_within
assert_nothing_raised { Topic.validates_size_of :replies, :within => 1..2 }
t = Topic.new('title' => 'noreplies', 'content' => 'whatever')
assert !t.save
assert t.errors.on(:replies)
repair_validations(Owner) do
assert_nothing_raised { Owner.validates_size_of :pets, :within => 1..2 }
o = Owner.new('name' => 'nopets')
assert !o.save
assert o.errors.on(:pets)
reply = t.replies.build('title' => 'areply', 'content' => 'whateveragain')
assert t.valid?
pet = o.pets.build('name' => 'apet')
assert o.valid?
2.times { t.replies.build('title' => 'areply', 'content' => 'whateveragain') }
assert !t.save
assert t.errors.on(:replies)
2.times { o.pets.build('name' => 'apet') }
assert !o.save
assert o.errors.on(:pets)
end
end
def test_validates_length_of_nasty_params
@@ -1102,13 +1111,15 @@ class ValidationsTest < ActiveRecord::TestCase
end
def test_validates_size_of_association_utf8
with_kcode('UTF8') do
assert_nothing_raised { Topic.validates_size_of :replies, :minimum => 1 }
t = Topic.new('title' => 'あいうえお', 'content' => 'かきくけこ')
assert !t.save
assert t.errors.on(:replies)
t.replies.build('title' => 'あいうえお', 'content' => 'かきくけこ')
assert t.valid?
repair_validations(Owner) do
with_kcode('UTF8') do
assert_nothing_raised { Owner.validates_size_of :pets, :minimum => 1 }
o = Owner.new('name' => 'あいうえおかきくけこ')
assert !o.save
assert o.errors.on(:pets)
o.pets.build('name' => 'あいうえおかきくけこ')
assert o.valid?
end
end
end
@@ -1127,14 +1138,16 @@ class ValidationsTest < ActiveRecord::TestCase
end
def test_validates_associated_one
Reply.validates_associated( :topic )
Topic.validates_presence_of( :content )
r = Reply.new("title" => "A reply", "content" => "with content!")
r.topic = Topic.create("title" => "uhohuhoh")
assert !r.valid?
assert r.errors.on(:topic)
r.topic.content = "non-empty"
assert r.valid?
repair_validations(Reply) do
Reply.validates_associated( :topic )
Topic.validates_presence_of( :content )
r = Reply.new("title" => "A reply", "content" => "with content!")
r.topic = Topic.create("title" => "uhohuhoh")
assert !r.valid?
assert r.errors.on(:topic)
r.topic.content = "non-empty"
assert r.valid?
end
end
def test_validate_block
@@ -1158,85 +1171,105 @@ class ValidationsTest < ActiveRecord::TestCase
end
def test_validates_acceptance_of_with_custom_error_using_quotes
Developer.validates_acceptance_of :salary, :message=> "This string contains 'single' and \"double\" quotes"
d = Developer.new
d.salary = "0"
assert !d.valid?
assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:salary).last
repair_validations(Developer) do
Developer.validates_acceptance_of :salary, :message=> "This string contains 'single' and \"double\" quotes"
d = Developer.new
d.salary = "0"
assert !d.valid?
assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:salary).last
end
end
def test_validates_confirmation_of_with_custom_error_using_quotes
Developer.validates_confirmation_of :name, :message=> "confirm 'single' and \"double\" quotes"
d = Developer.new
d.name = "John"
d.name_confirmation = "Johnny"
assert !d.valid?
assert_equal "confirm 'single' and \"double\" quotes", d.errors.on(:name)
repair_validations(Developer) do
Developer.validates_confirmation_of :name, :message=> "confirm 'single' and \"double\" quotes"
d = Developer.new
d.name = "John"
d.name_confirmation = "Johnny"
assert !d.valid?
assert_equal "confirm 'single' and \"double\" quotes", d.errors.on(:name)
end
end
def test_validates_format_of_with_custom_error_using_quotes
Developer.validates_format_of :name, :with => /^(A-Z*)$/, :message=> "format 'single' and \"double\" quotes"
d = Developer.new
d.name = d.name_confirmation = "John 32"
assert !d.valid?
assert_equal "format 'single' and \"double\" quotes", d.errors.on(:name)
repair_validations(Developer) do
Developer.validates_format_of :name, :with => /^(A-Z*)$/, :message=> "format 'single' and \"double\" quotes"
d = Developer.new
d.name = d.name_confirmation = "John 32"
assert !d.valid?
assert_equal "format 'single' and \"double\" quotes", d.errors.on(:name)
end
end
def test_validates_inclusion_of_with_custom_error_using_quotes
Developer.validates_inclusion_of :salary, :in => 1000..80000, :message=> "This string contains 'single' and \"double\" quotes"
d = Developer.new
d.salary = "90,000"
assert !d.valid?
assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:salary).last
repair_validations(Developer) do
Developer.validates_inclusion_of :salary, :in => 1000..80000, :message=> "This string contains 'single' and \"double\" quotes"
d = Developer.new
d.salary = "90,000"
assert !d.valid?
assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:salary).last
end
end
def test_validates_length_of_with_custom_too_long_using_quotes
Developer.validates_length_of :name, :maximum => 4, :too_long=> "This string contains 'single' and \"double\" quotes"
d = Developer.new
d.name = "Jeffrey"
assert !d.valid?
assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:name).last
repair_validations(Developer) do
Developer.validates_length_of :name, :maximum => 4, :too_long=> "This string contains 'single' and \"double\" quotes"
d = Developer.new
d.name = "Jeffrey"
assert !d.valid?
assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:name)
end
end
def test_validates_length_of_with_custom_too_short_using_quotes
Developer.validates_length_of :name, :minimum => 4, :too_short=> "This string contains 'single' and \"double\" quotes"
d = Developer.new
d.name = "Joe"
assert !d.valid?
assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:name).last
repair_validations(Developer) do
Developer.validates_length_of :name, :minimum => 4, :too_short=> "This string contains 'single' and \"double\" quotes"
d = Developer.new
d.name = "Joe"
assert !d.valid?
assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:name)
end
end
def test_validates_length_of_with_custom_message_using_quotes
Developer.validates_length_of :name, :minimum => 4, :message=> "This string contains 'single' and \"double\" quotes"
d = Developer.new
d.name = "Joe"
assert !d.valid?
assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:name).last
repair_validations(Developer) do
Developer.validates_length_of :name, :minimum => 4, :message=> "This string contains 'single' and \"double\" quotes"
d = Developer.new
d.name = "Joe"
assert !d.valid?
assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:name)
end
end
def test_validates_presence_of_with_custom_message_using_quotes
Developer.validates_presence_of :non_existent, :message=> "This string contains 'single' and \"double\" quotes"
d = Developer.new
d.name = "Joe"
assert !d.valid?
assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:non_existent)
repair_validations(Developer) do
Developer.validates_presence_of :non_existent, :message=> "This string contains 'single' and \"double\" quotes"
d = Developer.new
d.name = "Joe"
assert !d.valid?
assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:non_existent)
end
end
def test_validates_uniqueness_of_with_custom_message_using_quotes
Developer.validates_uniqueness_of :name, :message=> "This string contains 'single' and \"double\" quotes"
d = Developer.new
d.name = "David"
assert !d.valid?
assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:name).last
repair_validations(Developer) do
Developer.validates_uniqueness_of :name, :message=> "This string contains 'single' and \"double\" quotes"
d = Developer.new
d.name = "David"
assert !d.valid?
assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:name)
end
end
def test_validates_associated_with_custom_message_using_quotes
Reply.validates_associated :topic, :message=> "This string contains 'single' and \"double\" quotes"
Topic.validates_presence_of :content
r = Reply.create("title" => "A reply", "content" => "with content!")
r.topic = Topic.create("title" => "uhohuhoh")
assert !r.valid?
assert_equal "This string contains 'single' and \"double\" quotes", r.errors.on(:topic).last
repair_validations(Reply) do
Reply.validates_associated :topic, :message=> "This string contains 'single' and \"double\" quotes"
Topic.validates_presence_of :content
r = Reply.create("title" => "A reply", "content" => "with content!")
r.topic = Topic.create("title" => "uhohuhoh")
assert !r.valid?
assert_equal "This string contains 'single' and \"double\" quotes", r.errors.on(:topic)
end
end
def test_if_validation_using_method_true
@@ -1346,13 +1379,15 @@ class ValidationsTest < ActiveRecord::TestCase
end
def test_validates_associated_missing
Reply.validates_presence_of(:topic)
r = Reply.create("title" => "A reply", "content" => "with content!")
assert !r.valid?
assert r.errors.on(:topic)
repair_validations(Reply) do
Reply.validates_presence_of(:topic)
r = Reply.create("title" => "A reply", "content" => "with content!")
assert !r.valid?
assert r.errors.on(:topic)
r.topic = Topic.find :first
assert r.valid?
r.topic = Topic.find :first
assert r.valid?
end
end
def test_errors_to_xml
@@ -1364,14 +1399,14 @@ class ValidationsTest < ActiveRecord::TestCase
assert xml.include?("<error>Content Empty</error>")
end
def test_validation_order
Topic.validates_presence_of :title
Topic.validates_length_of :title, :minimum => 2
def test_validation_order
Topic.validates_presence_of :title
Topic.validates_length_of :title, :minimum => 2
t = Topic.new("title" => "")
assert !t.valid?
assert_equal "can't be blank", t.errors.on("title").first
end
t = Topic.new("title" => "")
assert !t.valid?
assert_equal "can't be blank", t.errors.on("title").first
end
# previous implementation of validates_presence_of eval'd the
# string with the wrong binding, this regression test is to
@@ -1423,11 +1458,7 @@ class ValidatesNumericalityTest < ActiveRecord::TestCase
JUNK = ["not a number", "42 not a number", "0xdeadbeef", "00-1", "--3", "+-3", "+3-1", "-+019.0", "12.12.13.12", "123\nnot a number"]
INFINITY = [1.0/0.0]
def setup
Topic.instance_variable_set("@validate_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
Topic.instance_variable_set("@validate_on_create_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
Topic.instance_variable_set("@validate_on_update_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
end
repair_validations(Topic)
def test_default_validates_numericality_of
Topic.validates_numericality_of :approved

View File

@@ -0,0 +1,6 @@
founding:
id: 1
name: Founding
provisional:
id: 2
name: Provisional

View File

@@ -1,4 +1,6 @@
groucho:
name: Groucho Marx
member_type_id: 1
some_other_guy:
name: Englebert Humperdink
name: Englebert Humperdink
member_type_id: 2

View File

@@ -8,4 +8,5 @@ class Member < ActiveRecord::Base
has_one :sponsor_club, :through => :sponsor
has_one :member_detail
has_one :organization, :through => :member_detail
belongs_to :member_type
end

View File

@@ -1,4 +1,5 @@
class MemberDetail < ActiveRecord::Base
belongs_to :member
belongs_to :organization
has_one :member_type, :through => :member
end

View File

@@ -0,0 +1,3 @@
class MemberType < ActiveRecord::Base
has_many :members
end

View File

@@ -195,6 +195,7 @@ ActiveRecord::Schema.define do
create_table :members, :force => true do |t|
t.string :name
t.integer :member_type_id
end
create_table :member_details, :force => true do |t|
@@ -210,6 +211,10 @@ ActiveRecord::Schema.define do
t.string :type
end
create_table :member_types, :force => true do |t|
t.string :name
end
create_table :references, :force => true do |t|
t.integer :person_id
t.integer :job_id

View File

@@ -20,15 +20,15 @@
* Fixed that to_param should be used and honored instead of hardcoding the id #11406 [gspiers]
* Improve documentation. [Radar, Jan De Poorter, chuyeow, xaviershay, danger, miloops, Xavier Noria, Sunny Ripert]
* Improve documentation. [Ryan Bigg, Jan De Poorter, Cheah Chu Yeow, Xavier Shay, Jack Danger Canty, Emilio Tagua, Xavier Noria, Sunny Ripert]
* Use HEAD instead of GET in exists? [bscofield]
* Fix small documentation typo. Closes #10670 [l.guidi]
* Fix small documentation typo. Closes #10670 [Luca Guidi]
* find_or_create_resource_for handles module nesting. #10646 [xavier]
* Allow setting ActiveResource::Base#format before #site. [rick]
* Allow setting ActiveResource::Base#format before #site. [Rick Olson]
* Support agnostic formats when calling custom methods. Closes #10635 [joerichsen]
@@ -48,9 +48,9 @@
* Don't cache net/http object so that ActiveResource is more thread-safe. Closes #10142 [kou]
* Update XML documentation examples to include explicit type attributes. Closes #9754 [hasmanyjosh]
* Update XML documentation examples to include explicit type attributes. Closes #9754 [Josh Susser]
* Added one-off declarations of mock behavior [DHH]. Example:
* Added one-off declarations of mock behavior [David Heinemeier Hansson]. Example:
Before:
ActiveResource::HttpMock.respond_to do |mock|
@@ -60,7 +60,7 @@
Now:
ActiveResource::HttpMock.respond_to.get "/people/1.xml", {}, "<person><name>David</name></person>"
* Added ActiveResource.format= which defaults to :xml but can also be set to :json [DHH]. Example:
* Added ActiveResource.format= which defaults to :xml but can also be set to :json [David Heinemeier Hansson]. Example:
class Person < ActiveResource::Base
self.site = "http://app/"
@@ -81,7 +81,7 @@
* Fix query methods on resources. [Cody Fauser]
* pass the prefix_options to the instantiated record when using find without a specific id. Closes #8544 [alloy]
* pass the prefix_options to the instantiated record when using find without a specific id. Closes #8544 [Eloy Duran]
* Recognize and raise an exception on 405 Method Not Allowed responses. #7692 [Josh Peek]
@@ -89,15 +89,15 @@
Comment.find(:all, :params => { :article_id => 5, :page => 2 }) or Comment.find(:all, :params => { 'article_id' => 5, :page => 2 })
* Added find-one with symbol [DHH]. Example: Person.find(:one, :from => :leader) # => GET /people/leader.xml
* Added find-one with symbol [David Heinemeier Hansson]. Example: Person.find(:one, :from => :leader) # => GET /people/leader.xml
* BACKWARDS INCOMPATIBLE: Changed the finder API to be more extensible with :params and more strict usage of scopes [DHH]. Changes:
* BACKWARDS INCOMPATIBLE: Changed the finder API to be more extensible with :params and more strict usage of scopes [David Heinemeier Hansson]. Changes:
Person.find(:all, :title => "CEO") ...becomes: Person.find(:all, :params => { :title => "CEO" })
Person.find(:managers) ...becomes: Person.find(:all, :from => :managers)
Person.find("/companies/1/manager.xml") ...becomes: Person.find(:one, :from => "/companies/1/manager.xml")
* Add support for setting custom headers per Active Resource model [Rick]
* Add support for setting custom headers per Active Resource model [Rick Olson]
class Project
headers['X-Token'] = 'foo'
@@ -106,13 +106,13 @@
# makes the GET request with the custom X-Token header
Project.find(:all)
* Added find-by-path options to ActiveResource::Base.find [DHH]. Examples:
* Added find-by-path options to ActiveResource::Base.find [David Heinemeier Hansson]. Examples:
employees = Person.find(:all, :from => "/companies/1/people.xml") # => GET /companies/1/people.xml
manager = Person.find("/companies/1/manager.xml") # => GET /companies/1/manager.xml
* Added support for using classes from within a single nested module [DHH]. Example:
* Added support for using classes from within a single nested module [David Heinemeier Hansson]. Example:
module Highrise
class Note < ActiveResource::Base
@@ -127,7 +127,7 @@
assert_kind_of Highrise::Comment, Note.find(1).comments.first
* Added load_attributes_from_response as a way of loading attributes from other responses than just create [DHH]
* Added load_attributes_from_response as a way of loading attributes from other responses than just create [David Heinemeier Hansson]
class Highrise::Task < ActiveResource::Base
def complete
@@ -143,18 +143,18 @@
Person.find(:managers) # => GET /people/managers.xml
Kase.find(1).post(:close) # => POST /kases/1/close.xml
* Remove explicit prefix_options parameter for ActiveResource::Base#initialize. [Rick]
* Remove explicit prefix_options parameter for ActiveResource::Base#initialize. [Rick Olson]
ActiveResource splits the prefix_options from it automatically.
* Allow ActiveResource::Base.delete with custom prefix. [Rick]
* Allow ActiveResource::Base.delete with custom prefix. [Rick Olson]
* Add ActiveResource::Base#dup [Rick]
* Add ActiveResource::Base#dup [Rick Olson]
* Fixed constant warning when fetching the same object multiple times [DHH]
* Fixed constant warning when fetching the same object multiple times [David Heinemeier Hansson]
* Added that saves which get a body response (and not just a 201) will use that response to update themselves [DHH]
* Added that saves which get a body response (and not just a 201) will use that response to update themselves [David Heinemeier Hansson]
* Disregard namespaces from the default element name, so Highrise::Person will just try to fetch from "/people", not "/highrise/people" [DHH]
* Disregard namespaces from the default element name, so Highrise::Person will just try to fetch from "/people", not "/highrise/people" [David Heinemeier Hansson]
* Allow array and hash query parameters. #7756 [Greg Spurrier]
@@ -176,7 +176,7 @@
* Base#==, eql?, and hash methods. == returns true if its argument is identical to self or if it's an instance of the same class, is not new?, and has the same id. eql? is an alias for ==. hash delegates to id. [Jeremy Kemper]
* Allow subclassed resources to share the site info [Rick, Jeremy Kemper]
* Allow subclassed resources to share the site info [Rick Olson, Jeremy Kemper]
d
class BeastResource < ActiveResource::Base
self.site = 'http://beast.caboo.se'
@@ -259,4 +259,4 @@ d
* Base.site= accepts URIs. 200...400 are valid response codes. PUT and POST request bodies default to ''. [Jeremy Kemper]
* Initial checkin: object-oriented client for restful HTTP resources which follow the Rails convention. [DHH]
* Initial checkin: object-oriented client for restful HTTP resources which follow the Rails convention. [David Heinemeier Hansson]

View File

@@ -10,9 +10,9 @@
* Added ActiveSupport::OrderedHash#each_key and ActiveSupport::OrderedHash#each_value #1410 [Christoffer Sawicki]
* Added ActiveSupport::MessageVerifier and MessageEncryptor to aid users who need to store signed and/or encrypted messages. [Koz]
* Added ActiveSupport::MessageVerifier and MessageEncryptor to aid users who need to store signed and/or encrypted messages. [Michael Koziarski]
* Added ActiveSupport::BacktraceCleaner to cut down on backtrace noise according to filters and silencers [DHH]
* Added ActiveSupport::BacktraceCleaner to cut down on backtrace noise according to filters and silencers [David Heinemeier Hansson]
* Added Object#try. ( Taken from http://ozmm.org/posts/try.html ) [Chris Wanstrath]
@@ -31,7 +31,7 @@
* Fixed the option merging in Array#to_xml #1126 [Rudolf Gavlas]
* Make I18n::Backend::Simple reload its translations in development mode [DHH/Sven Fuchs]
* Make I18n::Backend::Simple reload its translations in development mode [David Heinemeier Hansson/Sven Fuchs]
*2.2.0 [RC1] (October 24th, 2008)*
@@ -42,7 +42,7 @@
* Time#advance recognizes fractional days and weeks. Deprecate Durations of fractional months and years #970 [Tom Lea]
* Add ActiveSupport::Rescuable module abstracting ActionController::Base rescue_from features. [Norbert Crombach, Pratik]
* Add ActiveSupport::Rescuable module abstracting ActionController::Base rescue_from features. [Norbert Crombach, Pratik Naik]
* Switch from String#chars to String#mb_chars for the unicode proxy. [Manfred Stienstra]
@@ -56,7 +56,7 @@
* Added Inflector#parameterize for easy slug generation ("Donald E. Knuth".parameterize => "donald-e-knuth") #713 [Matt Darby]
* Changed cache benchmarking to be reported in milliseconds [DHH]
* Changed cache benchmarking to be reported in milliseconds [David Heinemeier Hansson]
* Fix Ruby's Time marshaling bug in pre-1.9 versions of Ruby: utc instances are now correctly unmarshaled with a utc zone instead of the system local zone [#900 state:resolved] [Luca Guidi, Geoff Buesing]
@@ -72,7 +72,7 @@
* Move the test related core_ext stuff out of core_ext so it's only loaded by the test helpers. [Michael Koziarski]
* Add Inflection rules for String#humanize. #535 [dcmanges]
* Add Inflection rules for String#humanize. #535 [Dan Manges]
ActiveSupport::Inflector.inflections do |inflect|
inflect.human(/_cnt$/i, '\1_count')
@@ -84,19 +84,19 @@
* Added TimeZone #=~, to support matching zones by regex in time_zone_select. #195 [Ernie Miller]
* Added Array#second through Array#fifth as aliases for Array#[1] through Array#[4] + Array#forty_two as alias for Array[41] [DHH]
* Added Array#second through Array#fifth as aliases for Array#[1] through Array#[4] + Array#forty_two as alias for Array[41] [David Heinemeier Hansson]
* Added test/do declaration style testing to ActiveSupport::TestCase [DHH via Jay Fields]
* Added Object#present? which is equivalent to !Object#blank? [DHH]
* Added Object#present? which is equivalent to !Object#blank? [David Heinemeier Hansson]
* Added Enumberable#many? to encapsulate collection.size > 1 [DHH/Damian Janowski]
* Added Enumberable#many? to encapsulate collection.size > 1 [David Heinemeier Hansson/Damian Janowski]
* Add more standard Hash methods to ActiveSupport::OrderedHash [Steve Purcell]
* Namespace Inflector, Dependencies, OrderedOptions, and TimeZone under ActiveSupport [Josh Peek]
* Added StringInquirer for doing things like StringInquirer.new("production").production? # => true and StringInquirer.new("production").development? # => false [DHH]
* Added StringInquirer for doing things like StringInquirer.new("production").production? # => true and StringInquirer.new("production").development? # => false [David Heinemeier Hansson]
* Fixed Date#end_of_quarter to not blow up on May 31st [#289 state:resolved] (Danger)
@@ -133,7 +133,7 @@
* TimeWithZone respects config.active_support.use_standard_json_time_format [Geoff Buesing]
* Add config.active_support.escape_html_entities_in_json to allow disabling of html entity escaping. [rick]
* Add config.active_support.escape_html_entities_in_json to allow disabling of html entity escaping. [Rick Olson]
* Improve documentation. [Xavier Noria]
@@ -145,7 +145,7 @@
* TimeWithZone#method_missing: send to utc to advance with dst correctness, otherwise send to time. Adding tests for time calculations methods [Geoff Buesing]
* Add config.active_support.use_standard_json_time_format setting so that Times and Dates export to ISO 8601 dates. [rick]
* Add config.active_support.use_standard_json_time_format setting so that Times and Dates export to ISO 8601 dates. [Rick Olson]
* TZInfo: Removing unneeded TimezoneProxy class [Geoff Buesing]
@@ -163,9 +163,9 @@
* TimeWithZone#usec returns 0 instead of error when DateTime is wrapped [Geoff Buesing]
* Improve documentation. [Radar, Jan De Poorter, chuyeow, xaviershay, danger, miloops, Xavier Noria, Sunny Ripert]
* Improve documentation. [Ryan Bigg, Jan De Poorter, Cheah Chu Yeow, Xavier Shay, Jack Danger Canty, Emilio Tagua, Xavier Noria, Sunny Ripert]
* Ensure that TimeWithZone#to_yaml works when passed a YAML::Emitter. [rick]
* Ensure that TimeWithZone#to_yaml works when passed a YAML::Emitter. [Rick Olson]
* Ensure correct TimeWithZone#to_date [Geoff Buesing]
@@ -197,7 +197,7 @@
* Adding TimeWithZone #marshal_dump and #marshal_load [Geoff Buesing]
* Add OrderedHash#to_hash [josh]
* Add OrderedHash#to_hash [Josh Peek]
* Adding Time#end_of_day, _quarter, _week, and _year. #9312 [Juanjo Bazan, Tarmo Tänav, BigTitus]
@@ -207,9 +207,9 @@
* TimeWithZone #+ and #- behave consistently with numeric arguments regardless of whether wrapped time is a Time or DateTime; consistenty answers false to #acts_like?(:date) [Geoff Buesing]
* Add String#squish and String#squish! to remove consecutive chunks of whitespace. #11123 [jordi, Henrik N]
* Add String#squish and String#squish! to remove consecutive chunks of whitespace. #11123 [Jordi Bunster, Henrik N]
* Serialize BigDecimals as Floats when using to_yaml. #8746 [ernesto.jimenez]
* Serialize BigDecimals as Floats when using to_yaml. #8746 [Ernesto Jimenez]
* Adding TimeWithZone #to_yaml, #to_datetime, #eql? and method aliases for duck-typing compatibility with Time [Geoff Buesing]
@@ -227,7 +227,7 @@
* TimeZone#new method renamed #local; when used with Time.zone, constructor now reads: Time.zone.local() [Geoff Buesing]
* Added Base64.encode64s to encode values in base64 without the newlines. This makes the values immediately usable as URL parameters or memcache keys without further processing [DHH]
* Added Base64.encode64s to encode values in base64 without the newlines. This makes the values immediately usable as URL parameters or memcache keys without further processing [David Heinemeier Hansson]
* Remove :nodoc: entries around the ActiveSupport test/unit assertions. #10946 [dancroak, jamesh]
@@ -235,7 +235,7 @@
* cache.fetch(key, :force => true) to force a cache miss. [Jeremy Kemper]
* Support retrieving TimeZones with a Duration. TimeZone[-28800] == TimeZone[-480.minutes]. [rick]
* Support retrieving TimeZones with a Duration. TimeZone[-28800] == TimeZone[-480.minutes]. [Rick Olson]
* TimeWithZone#- added, so that #- can handle a Time or TimeWithZone argument correctly [Geoff Buesing]
@@ -283,21 +283,21 @@
* TestCase: introduce declared setup and teardown callbacks. Pass a list of methods and an optional block to call before setup or after teardown. Setup callbacks are run in the order declared; teardown callbacks are run in reverse. [Jeremy Kemper]
* Added ActiveSupport::Gzip.decompress/compress(source) as an easy wrapper for Zlib [Tobias Luetke]
* Added ActiveSupport::Gzip.decompress/compress(source) as an easy wrapper for Zlib [Tobias Lütke]
* Included MemCache-Client to make the improved ActiveSupport::Cache::MemCacheStore work out of the box [Bob Cottrell, Eric Hodel]
* Added ActiveSupport::Cache::* framework as an extraction from ActionController::Caching::Fragments::* [DHH]
* Added ActiveSupport::Cache::* framework as an extraction from ActionController::Caching::Fragments::* [David Heinemeier Hansson]
* Fixed String#titleize to work for strings with 's too #10571 [trek]
* Changed the implementation of Enumerable#group_by to use a double array approach instead of a hash such that the insert order is honored [DHH/Marcel]
* Changed the implementation of Enumerable#group_by to use a double array approach instead of a hash such that the insert order is honored [David Heinemeier Hansson/Marcel Molina Jr.]
* remove multiple enumerations from ActiveSupport::JSON#convert_json_to_yaml when dealing with date/time values. [rick]
* remove multiple enumerations from ActiveSupport::JSON#convert_json_to_yaml when dealing with date/time values. [Rick Olson]
* Hash#symbolize_keys skips keys that can't be symbolized. #10500 [Brad Greenlee]
* Ruby 1.9 compatibility. #1689, #10466, #10468, #10554, #10594, #10632 [Cheah Chu Yeow, Pratik Naik, Jeremy Kemper, Dirkjan Bussink, fxn]
* Ruby 1.9 compatibility. #1689, #10466, #10468, #10554, #10594, #10632 [Cheah Chu Yeow, Pratik Naik, Jeremy Kemper, Dirkjan Bussink, Xavier Noria]
* TimeZone#to_s uses UTC rather than GMT. #1689 [Cheah Chu Yeow]
@@ -308,7 +308,7 @@
*2.0.1* (December 7th, 2007)
* Added Array#from and Array#to that behaves just from String#from and String#to [DHH]
* Added Array#from and Array#to that behaves just from String#from and String#to [David Heinemeier Hansson]
* Fix that empty collections should be treated as empty arrays regardless of whitespace for Hash#from_xml #10255 [adamj]
@@ -316,17 +316,17 @@
* Change Time and DateTime #end_of_month to return last second of month instead of beginning of last day of month. Closes #10200 [Geoff Buesing]
* Speedup String#blank? [Jeremy Kemper, Koz]
* Speedup String#blank? [Jeremy Kemper, Michael Koziarski]
* Add documentation for Hash#diff. Closes #9306 [Tarmo Tänav]
* Add new superclass_delegating_accessors. Similar to class inheritable attributes but with subtly different semantics. [Koz, Tarmo Tänav]
* Add new superclass_delegating_accessors. Similar to class inheritable attributes but with subtly different semantics. [Michael Koziarski, Tarmo Tänav]
* Change JSON to encode %w(< > &) as 4 digit hex codes to be in compliance with the JSON spec. Closes #9975 [Josh Peek, Cheah Chu Yeow, Tim Pope]
* Fix JSON encoding/decoding bugs dealing with /'s. Closes #9990 [Rick, theamazingrando]
* Fix JSON encoding/decoding bugs dealing with /'s. Closes #9990 [Rick Olson, theamazingrando]
* Introduce a base class for all test cases used by rails applications. ActiveSupport::TestCase [Koz]
* Introduce a base class for all test cases used by rails applications. ActiveSupport::TestCase [Michael Koziarski]
The intention is to use this to reduce the amount of monkeypatching / overriding that
is done to test/unit's classes.
@@ -379,13 +379,13 @@
* Backport Object#instance_variable_defined? for Ruby < 1.8.6. [Jeremy Kemper]
* BufferedLogger#add converts the message to a string. #9702, #9724 [eigentone, DrMark, tomafro]
* BufferedLogger#add converts the message to a string. #9702, #9724 [eigentone, DrMark, Tom Ward]
* Added ActiveSupport::BufferedLogger as a duck-typing alternative (albeit with no formatter) to the Ruby Logger, which provides a very nice speed bump (inspired by Ezra's buffered logger) [DHH]
* Added ActiveSupport::BufferedLogger as a duck-typing alternative (albeit with no formatter) to the Ruby Logger, which provides a very nice speed bump (inspired by Ezra's buffered logger) [David Heinemeier Hansson]
* Object#instance_exec produces fewer garbage methods. [Mauricio Fernandez]
* Decode json strings as Dates/Times if they're using a YAML-compatible format. Closes #9614 [Rick]
* Decode json strings as Dates/Times if they're using a YAML-compatible format. Closes #9614 [Rick Olson]
* Fixed cache_page to use the request url instead of the routing options when picking a save path. #8614 [Josh Peek]
@@ -393,11 +393,11 @@
* Fixed that pluralizing an empty string should return the same empty string, not "s". #7720 [Josh Peek]
* Added call to inspect on non-string classes for the logger #8533 [codahale]
* Added call to inspect on non-string classes for the logger #8533 [Coda Hale]
* Deprecation: remove deprecated :mday option from Time, Date, and DateTime#change. [Jeremy Kemper]
* Fix JSON decoder with nested quotes and commas. #9579 [zdennis]
* Fix JSON decoder with nested quotes and commas. #9579 [Zach Dennis]
* Hash#to_xml doesn't double-unescape. #8806 [Ezran]
@@ -409,27 +409,27 @@
* Deprecation: removed Reloadable. [Jeremy Kemper]
* Make the utf-handler return the correct value for non-matching regular expressions. Closes #9049 [manfred]
* Make the utf-handler return the correct value for non-matching regular expressions. Closes #9049 [Manfred Stienstra]
* Add ljust, rjust and center to utf8-handler. Closes #9165 [manfred]
* Add ljust, rjust and center to utf8-handler. Closes #9165 [Manfred Stienstra]
* Fix Time#advance bug when trying to advance a year from leap day. Closes #8655 [gbuesing]
* Fix Time#advance bug when trying to advance a year from leap day. Closes #8655 [Geoff Buesing]
* Add support for []= on ActiveSupport::Multibyte::Chars. Closes #9142. [ewan, manfred]
* Add support for []= on ActiveSupport::Multibyte::Chars. Closes #9142. [ewan, Manfred Stienstra]
* Added Array#extract_options! to encapsulate the pattern of getting an options hash out of a variable number of parameters. #8759 [Norbert Crombach]
* Let alias_attribute work with attributes with initial capital letters (legacy columns etc). Closes #8596 [mpalmer]
* Added Hash#except which is the inverse of Hash#slice -- return the hash except the keys that are specified [DHH]
* Added Hash#except which is the inverse of Hash#slice -- return the hash except the keys that are specified [David Heinemeier Hansson]
* Added support for pluralization with a different starting letter than the singular version (cow/kine) #4929 [norri_b/hasmanyjosh]
* Added support for pluralization with a different starting letter than the singular version (cow/kine) #4929 [norri_b/Josh Susser]
* Demote Hash#to_xml to use XmlSimple#xml_in_string so it can't read files or stdin. #8453 [candlerb, Jeremy Kemper]
* Backport clean_logger changes to support ruby 1.8.2 [mislav]
* Backport clean_logger changes to support ruby 1.8.2 [Mislav Marohnić]
* Added proper handling of arrays #8537 [hasmanyjosh]
* Added proper handling of arrays #8537 [Josh Susser]
Before:
Hash.from_xml '<images></images>'
@@ -463,7 +463,7 @@
* Move common DateTime calculations to Date. #8536 [Geoff Buesing]
* Added Date#change (like Time#change) [DHH]
* Added Date#change (like Time#change) [David Heinemeier Hansson]
* DateTime#to_time converts to Time unless out of range. #8512 [Geoff Buesing]
@@ -471,7 +471,7 @@
* Time durations use since instead of + for accuracy. #8513 [Geoff Buesing]
* escape <'s and >'s in JSON strings. #8371 [Rick]
* escape <'s and >'s in JSON strings. #8371 [Rick Olson]
* Inflections: MatrixTest -> MatrixTests instead of MatricesTest. #8496 [jbwiv]
@@ -487,11 +487,11 @@
* Simplify API of assert_difference by passing in an expression that is evaluated before and after the passed in block. See documenation for examples of new API. [Marcel Molina Jr.]
* Added assert_difference and assert_no_difference to test/unit assertions [Tobias Luetke]
* Added assert_difference and assert_no_difference to test/unit assertions [Tobias Lütke]
* Removed breakpointer and Binding.of_caller in favor of relying on ruby-debug by Kent Sibilev since the breakpointer has been broken since Ruby 1.8.4 and will not be coming back [DHH]
* Removed breakpointer and Binding.of_caller in favor of relying on ruby-debug by Kent Sibilev since the breakpointer has been broken since Ruby 1.8.4 and will not be coming back [David Heinemeier Hansson]
* Added parsing of file type in Hash.xml_in so you can easily do file uploads with base64 from an API [DHH]
* Added parsing of file type in Hash.xml_in so you can easily do file uploads with base64 from an API [David Heinemeier Hansson]
<person>
<name>David</name>
@@ -511,9 +511,9 @@
* Use XSD-compatible type names for Hash#to_xml and make the converters extendable #8047 [Tim Pope]
* Added yielding of builder in Hash#to_xml [DHH]
* Added yielding of builder in Hash#to_xml [David Heinemeier Hansson]
* Hash#with_indifferent_access now also converts hashes kept in arrays to indifferent access (makes it easier to treat HTML and XML parameters the same) [DHH]
* Hash#with_indifferent_access now also converts hashes kept in arrays to indifferent access (makes it easier to treat HTML and XML parameters the same) [David Heinemeier Hansson]
* Hash#to_xml supports YAML attributes. #7502 [jonathan]
@@ -531,9 +531,9 @@
* Give DateTime correct .to_s implementations, lets it play nice with ActiveRecord quoting. #7649 [Geoff Buesing]
* Add File.atomic_write, allows you to write large files in an atomic manner, preventing users from seeing half written files. [Koz]
* Add File.atomic_write, allows you to write large files in an atomic manner, preventing users from seeing half written files. [Michael Koziarski]
* Allow users to provide custom formatters to Logger. [aeden]
* Allow users to provide custom formatters to Logger. [Anthony Eden]
* Hash#to_query CGI-escapes its keys. [Jeremy Kemper]
@@ -542,7 +542,7 @@
* :db format for Date#to_s [Jeremy Kemper]
Date.new(2007, 1, 27).to_s(:db) # => '2007-01-27'
* Added :instance_writer option to #mattr_writer/accessor, #cattr_writer/accessor, and #class_inheritable_writer to skip the creation of the instance writer. [Rick]
* Added :instance_writer option to #mattr_writer/accessor, #cattr_writer/accessor, and #class_inheritable_writer to skip the creation of the instance writer. [Rick Olson]
* Added Hash#to_query to turn a hash of values into a form-encoded query string [Nicholas Seckar]
@@ -577,7 +577,7 @@ public for compatibility. [Jeremy Kemper]
* Optimize Class Inheritable Attributes so that unnecessary hashes are not created. Closes #7472 [Bruce Perens]
* Added :instance_writer option to #mattr_writer/accessor, #cattr_writer/accessor, and #class_inheritable_writer to skip the creation of the instance writer. [Rick]
* Added :instance_writer option to #mattr_writer/accessor, #cattr_writer/accessor, and #class_inheritable_writer to skip the creation of the instance writer. [Rick Olson]
* Full test coverage for Inflector. #7228 [Dan Kubb]
@@ -602,13 +602,13 @@ public for compatibility. [Jeremy Kemper]
* Fixed Array#to_xml when it contains a series of hashes (each piece would get its own XML declaration) #6610 [thkarcher/cyu]
* Added Time#to_s(:time) which will just return H:M, like 17:44 [DHH]
* Added Time#to_s(:time) which will just return H:M, like 17:44 [David Heinemeier Hansson]
* Add Module#attr_accessor_with_default to initialize value of attribute before setting it. Closes #6538. [Stuart Halloway, Marcel Molina Jr.]
* Hash#to_xml handles keys with the same name as Kernel methods. #6613 [Catfish]
* Hash#to_xml handles keys with the same name as Kernel methods. #6613 [Jonathan del Strother]
* Added Time#end_of_day to get 23:59:59 of that day [DHH]
* Added Time#end_of_day to get 23:59:59 of that day [David Heinemeier Hansson]
* Don't quote hash keys in Hash#to_json if they're valid JavaScript identifiers. Disable this with ActiveSupport::JSON.unquote_hash_key_identifiers = false if you need strict JSON compliance. [Sam Stephenson]
@@ -618,11 +618,11 @@ public for compatibility. [Jeremy Kemper]
* Fix unicode JSON regexp for Onigurama compatibility. #6494 [whitley]
* update XmlSimple to 1.0.10. Closes #6532. [nicksieger]
* update XmlSimple to 1.0.10. Closes #6532. [Nick Sieger]
* Update dependencies to allow constants to be defined alongside their siblings. A common case for this is AR model classes with STI; user.rb might define User, Administrator and Guest for example. [Nicholas Seckar]
* next_week respects DST changes. #6483, #5617, #2353, #2509, #4551 [marclove, rabiedenharn, rails@roetzel.de, jsolson@damogran.org, drbrain@segment7.net]
* next_week respects DST changes. #6483, #5617, #2353, #2509, #4551 [marclove, Rob Biedenharn, rails@roetzel.de, jsolson@damogran.org, drbrain@segment7.net]
* Expose methods added to Enumerable in the documentation, such as group_by. Closes #6170. [sergeykojin@gmail.com, Marcel Molina Jr.]
@@ -642,9 +642,9 @@ public for compatibility. [Jeremy Kemper]
* Hash#to_xml supports Bignum and BigDecimal. #6313 [edibiase]
* Don't undefine #class in OptionMerger [Rick]
* Don't undefine #class in OptionMerger [Rick Olson]
* Hash.create_from_xml has been renamed to Hash.from_xml, alias will exist until Rails 2.0 [DHH]
* Hash.create_from_xml has been renamed to Hash.from_xml, alias will exist until Rails 2.0 [David Heinemeier Hansson]
* alias_method_chain works with accessor= methods also. #6153 [Caio Chassot]
@@ -696,13 +696,13 @@ public for compatibility. [Jeremy Kemper]
self.attr_internal_naming_format = '@%s__rofl'
attr_internal :foo
* Raise fully qualified names upon name errors. #5533 [lars@pinds.com, Nicholas Seckar]
* Raise fully qualified names upon name errors. #5533 [Lars Pind, Nicholas Seckar]
* Add extention to obtain the missing constant from NameError instances. [Nicholas Seckar]
* Thoroughly document inflections. #5700 [petermichaux@gmail.com]
* Added Module#alias_attribute [Jamis/DHH]. Example:
* Added Module#alias_attribute [Jamis/David Heinemeier Hansson]. Example:
class Content < ActiveRecord::Base
# has a title attribute
@@ -724,23 +724,23 @@ public for compatibility. [Jeremy Kemper]
Provide your own per-environment in e.g. config/environments/development.rb:
ActiveSupport::Deprecation.behavior = Proc.new { |message| raise message }
* First cut of the Rails Deprecation system. [Koz]
* First cut of the Rails Deprecation system. [Michael Koziarski]
* Strip boolean XML content before checking for 'true' [Rick Olson]
* Customize default BigDecimal formatting. References #5672 [dave@pragprog.com]
* Customize default BigDecimal formatting. References #5672 [Dave Thomas]
* Correctly convert <foo nil="true"> to nil when using Hash.create_from_xml. [Rick]
* Correctly convert <foo nil="true"> to nil when using Hash.create_from_xml. [Rick Olson]
* Optional identity for Enumerable#sum defaults to zero. #5657 [gensym@mac.com]
* HashWithIndifferentAccess shouldn't confuse false and nil. #5601 [shugo@ruby-lang.org]
* HashWithIndifferentAccess shouldn't confuse false and nil. #5601 [Shugo Maeda]
* Fixed HashWithIndifferentAccess#default #5586 [chris@seagul.co.uk]
* More compatible Hash.create_from_xml. #5523 [nunemaker@gmail.com]
* Added Enumerable#sum for calculating a sum from the elements [DHH, jonathan@daikini.com]. Examples:
* Added Enumerable#sum for calculating a sum from the elements [David Heinemeier Hansson, jonathan@daikini.com]. Examples:
[1, 2, 3].sum
payments.sum { |p| p.price * p.tax_rate }
@@ -762,7 +762,7 @@ public for compatibility. [Jeremy Kemper]
{1 => "one", 2 => "two", 3 => "three"}.sort_by(&:first).map(&:last)
#=> ["one", "two", "three"]
* Added Hash.create_from_xml(string) which will create a hash from a XML string and even typecast if possible [DHH]. Example:
* Added Hash.create_from_xml(string) which will create a hash from a XML string and even typecast if possible [David Heinemeier Hansson]. Example:
Hash.create_from_xml <<-EOT
<note>
@@ -775,7 +775,7 @@ public for compatibility. [Jeremy Kemper]
{ :note => { :title => "This is a note", :created_at => Date.new(2004, 10, 10) } }
* Added Jim Weirich's excellent FlexMock class to vendor (Copyright 2003, 2004 by Jim Weirich (jim@weriichhouse.org)) -- it's not automatically required, though, so require 'flexmock' is still necessary [DHH]
* Added Jim Weirich's excellent FlexMock class to vendor (Copyright 2003, 2004 by Jim Weirich (jim@weriichhouse.org)) -- it's not automatically required, though, so require 'flexmock' is still necessary [David Heinemeier Hansson]
* Fixed that Module#alias_method_chain should work with both foo? foo! and foo at the same time #4954 [anna@wota.jp]
@@ -783,13 +783,13 @@ public for compatibility. [Jeremy Kemper]
* Add OrderedHash#values. [Sam Stephenson]
* Added Array#to_s(:db) that'll produce a comma-separated list of ids [DHH]. Example:
* Added Array#to_s(:db) that'll produce a comma-separated list of ids [David Heinemeier Hansson]. Example:
Purchase.find(:all, :conditions => "product_id IN (#{shops.products.to_s(:db)})"
* Normalize classify's argument to a String so that it plays nice with Symbols. [Marcel Molina Jr.]
* Strip out leading schema name in classify. References #5139. [schoenm@earthlink.net]
* Strip out leading schema name in classify. References #5139. [Michael Schoen]
* Remove Enumerable#first_match since break(value) handles the use case well enough. [Nicholas Seckar]
@@ -821,7 +821,7 @@ public for compatibility. [Jeremy Kemper]
* Added Module#alias_method_chain [Jamis Buck]
* Updated to Builder 2.0 [DHH]
* Updated to Builder 2.0 [David Heinemeier Hansson]
* Add Array#split for dividing arrays into one or more subarrays by value or block. [Sam Stephenson]
@@ -852,7 +852,7 @@ public for compatibility. [Jeremy Kemper]
* Added Fixnum#seconds for consistency, so you can say 5.minutes + 30.seconds instead of 5.minutes + 30 #4389 [François Beausoleil]
* Added option to String#camelize to generate lower-cased camel case by passing in :lower, like "super_man".camelize(:lower) # => "superMan" [DHH]
* Added option to String#camelize to generate lower-cased camel case by passing in :lower, like "super_man".camelize(:lower) # => "superMan" [David Heinemeier Hansson]
* Added Hash#diff to show the difference between two hashes [Chris McGrath]
@@ -860,12 +860,12 @@ public for compatibility. [Jeremy Kemper]
* Enhance Inflector.underscore to convert '-' into '_' (as the inverse of Inflector.dasherize) [Jamis Buck]
* Switched to_xml to use the xml schema format for datetimes. This allows the encoding of time zones and should improve operability. [Koz]
* Switched to_xml to use the xml schema format for datetimes. This allows the encoding of time zones and should improve operability. [Michael Koziarski]
* Added a note to the documentation for the Date related Numeric extensions to indicate that they're
approximations and shouldn't be used for critical calculations. [Koz]
approximations and shouldn't be used for critical calculations. [Michael Koziarski]
* Added Hash#to_xml and Array#to_xml that makes it much easier to produce XML from basic structures [DHH]. Examples:
* Added Hash#to_xml and Array#to_xml that makes it much easier to produce XML from basic structures [David Heinemeier Hansson]. Examples:
{ :name => "David", :street_name => "Paulina", :age => 26, :moved_on => Date.new(2005, 11, 15) }.to_xml
@@ -878,7 +878,7 @@ approximations and shouldn't be used for critical calculations. [Koz]
<moved-on type="date">2005-11-15</moved-on>
</person>
* Moved Jim Weirich's wonderful Builder from Action Pack to Active Support (it's simply too useful to be stuck in AP) [DHH]
* Moved Jim Weirich's wonderful Builder from Action Pack to Active Support (it's simply too useful to be stuck in AP) [David Heinemeier Hansson]
* Fixed that Array#to_sentence will return "" on an empty array instead of ", and" #3842, #4031 [rubyonrails@beautifulpixel.com]
@@ -907,7 +907,7 @@ approximations and shouldn't be used for critical calculations. [Koz]
[Marcel Molina Jr., Sam Stephenson]
* Added Kernel#daemonize to turn the current process into a daemon that can be killed with a TERM signal [DHH]
* Added Kernel#daemonize to turn the current process into a daemon that can be killed with a TERM signal [David Heinemeier Hansson]
* Add 'around' methods to Logger, to make it easy to log before and after messages for a given block as requested in #3809. [Michael Koziarski] Example:
@@ -922,7 +922,7 @@ approximations and shouldn't be used for critical calculations. [Koz]
* Make String#last return the string instead of nil when it is shorter than the limit [Scott Barron].
* Added delegation support to Module that allows multiple delegations at once (unlike Forwardable in the stdlib) [DHH]. Example:
* Added delegation support to Module that allows multiple delegations at once (unlike Forwardable in the stdlib) [David Heinemeier Hansson]. Example:
class Account < ActiveRecord::Base
has_one :subscription
@@ -957,7 +957,7 @@ approximations and shouldn't be used for critical calculations. [Koz]
include Reloadable
end
Reloading a class is done by removing its constant which will cause it to be loaded again on the next reference. [DHH]
Reloading a class is done by removing its constant which will cause it to be loaded again on the next reference. [David Heinemeier Hansson]
* Added auto-loading support for classes in modules, so Conductor::Migration will look for conductor/migration.rb and Conductor::Database::Settings will look for conductor/database/settings.rb [Nicholas Seckar]
@@ -965,7 +965,7 @@ approximations and shouldn't be used for critical calculations. [Koz]
* Add Proc#bind(object) for changing a proc or block's self by returning a Method bound to the given object. Based on why the lucky stiff's "cloaker" method. [Sam Stephenson]
* Fix merge and dup for hashes with indifferent access #3404 [kenneth.miller@bitfield.net]
* Fix merge and dup for hashes with indifferent access #3404 [Ken Miller]
* Fix the requires in option_merger_test to unbreak AS tests. [Sam Stephenson]
@@ -1072,7 +1072,7 @@ approximations and shouldn't be used for critical calculations. [Koz]
* Fixed clean logger to work with Ruby 1.8.3 Logger class #2245
* Fixed memory leak with Active Record classes when Dependencies.mechanism = :load #1704 [c.r.mcgrath@gmail.com]
* Fixed memory leak with Active Record classes when Dependencies.mechanism = :load #1704 [Chris McGrath]
* Fixed Inflector.underscore for use with acronyms, so HTML becomes html instead of htm_l #2173 [k@v2studio.com]
@@ -1082,11 +1082,11 @@ approximations and shouldn't be used for critical calculations. [Koz]
* Added Hash#reverse_merge, Hash#reverse_merge!, and Hash#reverse_update to ease the use of default options
* Added Array#to_sentence that'll turn ['one', 'two', 'three'] into "one, two, and three" #2157 [m.stienstra@fngtps.com]
* Added Array#to_sentence that'll turn ['one', 'two', 'three'] into "one, two, and three" #2157 [Manfred Stienstra]
* Added Kernel#silence_warnings to turn off warnings temporarily for the passed block
* Added String#starts_with? and String#ends_with? #2118 [thijs@vandervossen.net]
* Added String#starts_with? and String#ends_with? #2118 [Thijs van der Vossen]
* Added easy extendability to the inflector through Inflector.inflections (using the Inflector::Inflections singleton class). Examples:
@@ -1129,7 +1129,7 @@ approximations and shouldn't be used for critical calculations. [Koz]
* Added new rules to the Inflector to deal with more unusual plurals mouse/louse => mice/lice, information => information, ox => oxen, virus => viri, archive => archives #1571, #1583, #1490, #1599, #1608 [foamdino@gmail.com/others]
* Fixed memory leak with Object#remove_subclasses_of, which inflicted a Rails application running in development mode with a ~20KB leak per request #1289 [c.r.mcgrath@gmail.com]
* Fixed memory leak with Object#remove_subclasses_of, which inflicted a Rails application running in development mode with a ~20KB leak per request #1289 [Chris McGrath]
* Made 1.year == 365.25.days to account for leap years. This allows you to do User.find(:all, :conditions => ['birthday > ?', 50.years.ago]) without losing a lot of days. #1488 [tuxie@dekadance.se]
@@ -1164,7 +1164,7 @@ approximations and shouldn't be used for critical calculations. [Koz]
* Fixed that in some circumstances controllers outside of modules may have hidden ones inside modules. For example, admin/content might have been hidden by /content. #1075 [Nicholas Seckar]
* Fixed inflection of perspectives and similar words #1045 [thijs@vandervossen.net]
* Fixed inflection of perspectives and similar words #1045 [Thijs van der Vossen]
* Added Fixnum#even? and Fixnum#odd?
@@ -1206,7 +1206,7 @@ approximations and shouldn't be used for critical calculations. [Koz]
* Added inflection rules for "sh" words, like "wish" and "fish" #755 [phillip@pjbsoftware.com]
* Fixed an exception when using Ajax based requests from Safari because Safari appends a \000 to the post body. Symbols can't have \000 in them so indifferent access would throw an exception in the constructor. Indifferent hashes now use strings internally instead. #746 [Tobias Luetke]
* Fixed an exception when using Ajax based requests from Safari because Safari appends a \000 to the post body. Symbols can't have \000 in them so indifferent access would throw an exception in the constructor. Indifferent hashes now use strings internally instead. #746 [Tobias Lütke]
* Added String#to_time and String#to_date for wrapping ParseDate
@@ -1272,7 +1272,7 @@ approximations and shouldn't be used for critical calculations. [Koz]
* Added Inflections as an extension on String, so Inflector.pluralize(Inflector.classify(name)) becomes name.classify.pluralize #476 [Jeremy Kemper]
* Added Byte operations to Numeric, so 5.5.megabytes + 200.kilobytes #461 [Marcel Molina]
* Added Byte operations to Numeric, so 5.5.megabytes + 200.kilobytes #461 [Marcel Molina Jr.]
* Fixed that Dependencies.reload can't load the same file twice #420 [Kent Sibilev]

File diff suppressed because it is too large Load Diff

View File

@@ -84,7 +84,7 @@ else
end
app = Rack::Builder.new {
use Rails::Rack::Logger
use Rails::Rack::LogTailer unless options[:detach]
use Rails::Rack::Static
use Rails::Rack::Debugger if options[:debugger]
run inner_app

View File

@@ -155,6 +155,8 @@ module Rails
initialize_framework_settings
initialize_framework_views
initialize_metal
add_support_load_paths
load_gems
@@ -533,6 +535,10 @@ Run `rake gems:install` to install the missing gems.
end
end
def initialize_metal
configuration.middleware.use Rails::Rack::Metal
end
# Initializes framework-specific settings for each of the loaded frameworks
# (Configuration#frameworks). The available settings map to the accessors
# on each of the corresponding Base classes.
@@ -915,6 +921,7 @@ Run `rake gems:install` to install the missing gems.
# Followed by the standard includes.
paths.concat %w(
app
app/metal
app/models
app/controllers
app/helpers
@@ -933,6 +940,7 @@ Run `rake gems:install` to install the missing gems.
def default_eager_load_paths
%w(
app/metal
app/models
app/controllers
app/helpers

View File

@@ -1,7 +1,8 @@
module Rails
module Rack
autoload :Debugger, "rails/rack/debugger"
autoload :Logger, "rails/rack/logger"
autoload :LogTailer, "rails/rack/log_tailer"
autoload :Metal, "rails/rack/metal"
autoload :Static, "rails/rack/static"
end
end

View File

@@ -0,0 +1,31 @@
require 'active_support/ordered_hash'
module Rails
module Rack
# Try a request on several apps; return the first non-404 response.
class Cascade
attr_reader :apps
def initialize(apps)
@apps = ActiveSupport::OrderedHash.new
apps.each { |app| add app }
end
def call(env)
@apps.keys.each do |app|
result = app.call(env)
return result unless result[0].to_i == 404
end
Metal::NotFoundResponse
end
def add(app)
@apps[app] = true
end
def include?(app)
@apps.include?(app)
end
end
end
end

View File

@@ -0,0 +1,35 @@
module Rails
module Rack
class LogTailer
EnvironmentLog = "#{File.expand_path(Rails.root)}/log/#{Rails.env}.log"
def initialize(app, log = nil)
@app = app
path = Pathname.new(log || EnvironmentLog).cleanpath
@cursor = ::File.size(path)
@last_checked = Time.now.to_f
@file = ::File.open(path, 'r')
end
def call(env)
response = @app.call(env)
tail_log
response
end
def tail_log
@file.seek @cursor
mod = @file.mtime.to_f
if mod > @last_checked
contents = @file.read
@last_checked = mod
@cursor += contents.size
$stdout.print contents
end
end
end
end
end

View File

@@ -1,28 +0,0 @@
module Rails
module Rack
class Logger
EnvironmentLog = "#{File.expand_path(Rails.root)}/log/#{Rails.env}.log"
def initialize(app, log = nil)
@app = app
@path = Pathname.new(log || EnvironmentLog).cleanpath
@cursor = ::File.size(@path)
@last_checked = Time.now
end
def call(env)
response = @app.call(env)
::File.open(@path, 'r') do |f|
f.seek @cursor
if f.mtime > @last_checked
contents = f.read
@last_checked = f.mtime
@cursor += contents.length
print contents
end
end
response
end
end
end
end

View File

@@ -0,0 +1,27 @@
require 'rails/rack/cascade'
module Rails
module Rack
module Metal
NotFoundResponse = [404, {}, []].freeze
NotFound = lambda { NotFoundResponse }
class << self
def new(app)
Cascade.new(builtins + [app])
end
def builtins
base = "#{Rails.root}/app/metal"
matcher = /\A#{Regexp.escape(base)}\/(.*)\.rb\Z/
Dir["#{base}/**/*.rb"].sort.map do |file|
file.sub!(matcher, '\1')
require file
file.classify.constantize
end
end
end
end
end
end

View File

@@ -0,0 +1,8 @@
Description:
Cast some metal!
Examples:
`./script/generate metal poller`
This will create:
Metal: app/metal/poller.rb

View File

@@ -0,0 +1,8 @@
class MetalGenerator < Rails::Generator::NamedBase
def manifest
record do |m|
m.directory 'app/metal'
m.template 'metal.rb', File.join('app/metal', "#{file_name}.rb")
end
end
end

View File

@@ -0,0 +1,12 @@
# Allow the metal piece to run in isolation
require(File.dirname(__FILE__) + "/../../config/environment") unless defined?(Rails)
class <%= class_name %>
def self.call(env)
if env["PATH_INFO"] =~ /^\/<%= file_name %>/
[200, {"Content-Type" => "text/html"}, ["Hello, World!"]]
else
[404, {"Content-Type" => "text/html"}, ["Not Found"]]
end
end
end

View File

@@ -1,6 +1,6 @@
desc 'Prints out your Rack middleware stack'
task :middleware => :environment do
ActionController::Dispatcher.middleware.each do |middleware|
ActionController::Dispatcher.middleware.active.each do |middleware|
puts "use #{middleware.inspect}"
end
puts "run ActionController::Dispatcher.new"