mirror of
https://github.com/github/rails.git
synced 2026-04-26 03:00:59 -04:00
Merge branch 'master' of git@github.com:lifo/docrails
This commit is contained in:
@@ -17,7 +17,6 @@ $:.unshift "#{File.dirname(__FILE__)}/fixtures/helpers"
|
||||
|
||||
FIXTURE_LOAD_PATH = File.join(File.dirname(__FILE__), 'fixtures')
|
||||
ActionMailer::Base.template_root = FIXTURE_LOAD_PATH
|
||||
ActionMailer::Base.template_root.load
|
||||
|
||||
class MockSMTP
|
||||
def self.deliveries
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
*2.3.0 [Edge]*
|
||||
|
||||
* Added :silence option to BenchmarkHelper#benchmark and turned log_level into a hash parameter and deprecated the old use [DHH]
|
||||
|
||||
* Fixed the AssetTagHelper cache to use the computed asset host as part of the cache key instead of just assuming the its a string #1299 [DHH]
|
||||
|
||||
* Make ActionController#render(string) work as a shortcut for render :file/:template/:action => string. [#1435] [Pratik Naik] Examples:
|
||||
|
||||
# Instead of render(:action => 'other_action')
|
||||
|
||||
@@ -81,7 +81,7 @@ spec = Gem::Specification.new do |s|
|
||||
s.requirements << 'none'
|
||||
|
||||
s.add_dependency('activesupport', '= 2.3.0' + PKG_BUILD)
|
||||
s.add_dependency('rack', '= 0.4.0')
|
||||
s.add_dependency('rack', '>= 0.9.0')
|
||||
|
||||
s.require_path = 'lib'
|
||||
s.autorequire = 'action_controller'
|
||||
|
||||
@@ -31,7 +31,7 @@ rescue LoadError
|
||||
end
|
||||
end
|
||||
|
||||
gem 'rack', '~> 0.4.0'
|
||||
gem 'rack', '>= 0.9.0'
|
||||
require 'rack'
|
||||
|
||||
module ActionController
|
||||
|
||||
@@ -382,6 +382,13 @@ module ActionController #:nodoc:
|
||||
attr_accessor :action_name
|
||||
|
||||
class << self
|
||||
def call(env)
|
||||
# HACK: For global rescue to have access to the original request and response
|
||||
request = env["action_controller.rescue.request"] ||= Request.new(env)
|
||||
response = env["action_controller.rescue.response"] ||= Response.new
|
||||
process(request, response)
|
||||
end
|
||||
|
||||
# Factory for the standard create, process loop where the controller is discarded after processing.
|
||||
def process(request, response) #:nodoc:
|
||||
new.process(request, response)
|
||||
@@ -862,7 +869,7 @@ module ActionController #:nodoc:
|
||||
validate_render_arguments(options, extra_options, block_given?)
|
||||
|
||||
if options.nil?
|
||||
options = { :template => default_template.filename, :layout => true }
|
||||
options = { :template => default_template, :layout => true }
|
||||
elsif options == :update
|
||||
options = extra_options.merge({ :update => true })
|
||||
elsif options.is_a?(String) || options.is_a?(Symbol)
|
||||
@@ -1118,7 +1125,7 @@ module ActionController #:nodoc:
|
||||
end
|
||||
|
||||
# Sets the etag, last_modified, or both on the response and renders a
|
||||
# "304 Not Modified" response if the request is already fresh.
|
||||
# "304 Not Modified" response if the request is already fresh.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
@@ -1126,8 +1133,8 @@ module ActionController #:nodoc:
|
||||
# @article = Article.find(params[:id])
|
||||
# fresh_when(:etag => @article, :last_modified => @article.created_at.utc)
|
||||
# end
|
||||
#
|
||||
# This will render the show template if the request isn't sending a matching etag or
|
||||
#
|
||||
# This will render the show template if the request isn't sending a matching etag or
|
||||
# If-Modified-Since header and just a "304 Not Modified" response if there's a match.
|
||||
def fresh_when(options)
|
||||
options.assert_valid_keys(:etag, :last_modified)
|
||||
@@ -1232,7 +1239,7 @@ module ActionController #:nodoc:
|
||||
log_processing_for_parameters
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def log_processing_for_request_id
|
||||
request_id = "\n\nProcessing #{self.class.name}\##{action_name} "
|
||||
request_id << "to #{params[:format]} " if params[:format]
|
||||
@@ -1244,7 +1251,7 @@ module ActionController #:nodoc:
|
||||
def log_processing_for_parameters
|
||||
parameters = respond_to?(:filter_parameters) ? filter_parameters(params) : params.dup
|
||||
parameters = parameters.except!(:controller, :action, :format, :_method)
|
||||
|
||||
|
||||
logger.info " Parameters: #{parameters.inspect}" unless parameters.empty?
|
||||
end
|
||||
|
||||
@@ -1343,9 +1350,12 @@ module ActionController #:nodoc:
|
||||
end
|
||||
|
||||
Base.class_eval do
|
||||
include Flash, Filters, Layout, Benchmarking, Rescue, MimeResponds, Helpers
|
||||
include Cookies, Caching, Verification, Streaming
|
||||
include SessionManagement, HttpAuthentication::Basic::ControllerMethods
|
||||
include RecordIdentifier, RequestForgeryProtection, Translation
|
||||
[ Filters, Layout, Benchmarking, Rescue, Flash, MimeResponds, Helpers,
|
||||
Cookies, Caching, Verification, Streaming, SessionManagement,
|
||||
HttpAuthentication::Basic::ControllerMethods, RecordIdentifier,
|
||||
RequestForgeryProtection, Translation
|
||||
].each do |mod|
|
||||
include mod
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,6 +8,8 @@ module ActionController
|
||||
# Development mode callbacks
|
||||
before_dispatch :reload_application
|
||||
after_dispatch :cleanup_application
|
||||
|
||||
ActionView::Helpers::AssetTagHelper.cache_asset_timestamps = false
|
||||
end
|
||||
|
||||
if defined?(ActiveRecord)
|
||||
@@ -60,11 +62,10 @@ module ActionController
|
||||
def dispatch
|
||||
begin
|
||||
run_callbacks :before_dispatch
|
||||
controller = Routing::Routes.recognize(@request)
|
||||
controller.process(@request, @response).to_a
|
||||
Routing::Routes.call(@env)
|
||||
rescue Exception => exception
|
||||
if controller ||= (::ApplicationController rescue Base)
|
||||
controller.process_with_exception(@request, @response, exception).to_a
|
||||
controller.call_with_exception(@env, exception).to_a
|
||||
else
|
||||
raise exception
|
||||
end
|
||||
@@ -83,8 +84,7 @@ module ActionController
|
||||
end
|
||||
|
||||
def _call(env)
|
||||
@request = Request.new(env)
|
||||
@response = Response.new
|
||||
@env = env
|
||||
dispatch
|
||||
end
|
||||
|
||||
@@ -93,7 +93,6 @@ module ActionController
|
||||
run_callbacks :prepare_dispatch
|
||||
|
||||
Routing::Routes.reload
|
||||
ActionView::Helpers::AssetTagHelper::AssetTag::Cache.clear
|
||||
end
|
||||
|
||||
# Cleanup the application by clearing out loaded classes so they can
|
||||
@@ -110,8 +109,7 @@ 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?("rack.test")
|
||||
return if @env.key?("rack.test")
|
||||
ActiveRecord::Base.clear_active_connections!
|
||||
end
|
||||
end
|
||||
|
||||
@@ -55,7 +55,31 @@ module ActionController
|
||||
# end
|
||||
# end
|
||||
#
|
||||
#
|
||||
# Simple Digest example. Note the block must return the user's password so the framework
|
||||
# can appropriately hash it to check the user's credentials. Returning nil will cause authentication to fail.
|
||||
#
|
||||
# class PostsController < ApplicationController
|
||||
# Users = {"dhh" => "secret"}
|
||||
#
|
||||
# before_filter :authenticate, :except => [ :index ]
|
||||
#
|
||||
# def index
|
||||
# render :text => "Everyone can see me!"
|
||||
# end
|
||||
#
|
||||
# def edit
|
||||
# render :text => "I'm only accessible if you know the password"
|
||||
# end
|
||||
#
|
||||
# private
|
||||
# def authenticate
|
||||
# authenticate_or_request_with_http_digest(realm) do |user_name|
|
||||
# Users[user_name]
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#
|
||||
#
|
||||
# In your integration tests, you can do something like this:
|
||||
#
|
||||
# def test_access_granted_from_xml
|
||||
@@ -108,7 +132,10 @@ module ActionController
|
||||
end
|
||||
|
||||
def decode_credentials(request)
|
||||
ActiveSupport::Base64.decode64(authorization(request).split.last || '')
|
||||
# Properly decode credentials spanning a new-line
|
||||
auth = authorization(request)
|
||||
auth.slice!('Basic ')
|
||||
ActiveSupport::Base64.decode64(auth || '')
|
||||
end
|
||||
|
||||
def encode_credentials(user_name, password)
|
||||
@@ -120,5 +147,165 @@ module ActionController
|
||||
controller.__send__ :render, :text => "HTTP Basic: Access denied.\n", :status => :unauthorized
|
||||
end
|
||||
end
|
||||
|
||||
module Digest
|
||||
extend self
|
||||
|
||||
module ControllerMethods
|
||||
def authenticate_or_request_with_http_digest(realm = "Application", &password_procedure)
|
||||
begin
|
||||
authenticate_with_http_digest!(realm, &password_procedure)
|
||||
rescue ActionController::HttpAuthentication::Error => e
|
||||
msg = e.message
|
||||
msg = "#{msg} expected '#{e.expected}' was '#{e.was}'" unless e.expected.nil?
|
||||
raise msg if e.fatal?
|
||||
request_http_digest_authentication(realm, msg)
|
||||
end
|
||||
end
|
||||
|
||||
# Authenticate using HTTP Digest, throwing ActionController::HttpAuthentication::Error on failure.
|
||||
# This allows more detailed analysis of authentication failures
|
||||
# to be relayed to the client.
|
||||
def authenticate_with_http_digest!(realm = "Application", &login_procedure)
|
||||
HttpAuthentication::Digest.authenticate(self, realm, &login_procedure)
|
||||
end
|
||||
|
||||
# Authenticate with HTTP Digest, returns true or false
|
||||
def authenticate_with_http_digest(realm = "Application", &login_procedure)
|
||||
HttpAuthentication::Digest.authenticate(self, realm, &login_procedure) rescue false
|
||||
end
|
||||
|
||||
# Render output including the HTTP Digest authentication header
|
||||
def request_http_digest_authentication(realm = "Application", message = nil)
|
||||
HttpAuthentication::Digest.authentication_request(self, realm, message)
|
||||
end
|
||||
|
||||
# Add HTTP Digest authentication header to result headers
|
||||
def http_digest_authentication_header(realm = "Application")
|
||||
HttpAuthentication::Digest.authentication_header(self, realm)
|
||||
end
|
||||
end
|
||||
|
||||
# Raises error unless authentictaion succeeds, returns true otherwise
|
||||
def authenticate(controller, realm, &password_procedure)
|
||||
raise Error.new(false), "No authorization header found" unless authorization(controller.request)
|
||||
validate_digest_response(controller, realm, &password_procedure)
|
||||
true
|
||||
end
|
||||
|
||||
def authorization(request)
|
||||
request.env['HTTP_AUTHORIZATION'] ||
|
||||
request.env['X-HTTP_AUTHORIZATION'] ||
|
||||
request.env['X_HTTP_AUTHORIZATION'] ||
|
||||
request.env['REDIRECT_X_HTTP_AUTHORIZATION']
|
||||
end
|
||||
|
||||
# Raises error unless the request credentials response value matches the expected value.
|
||||
def validate_digest_response(controller, realm, &password_procedure)
|
||||
credentials = decode_credentials(controller.request)
|
||||
|
||||
# Check the nonce, opaque and realm.
|
||||
# Ignore nc, as we have no way to validate the number of times this nonce has been used
|
||||
validate_nonce(controller.request, credentials[:nonce])
|
||||
raise Error.new(false, realm, credentials[:realm]), "Realm doesn't match" unless realm == credentials[:realm]
|
||||
raise Error.new(true, opaque(controller.request), credentials[:opaque]),"Opaque doesn't match" unless opaque(controller.request) == credentials[:opaque]
|
||||
|
||||
password = password_procedure.call(credentials[:username])
|
||||
raise Error.new(false), "No password" if password.nil?
|
||||
expected = expected_response(controller.request.env['REQUEST_METHOD'], controller.request.url, credentials, password)
|
||||
raise Error.new(false, expected, credentials[:response]), "Invalid response" unless expected == credentials[:response]
|
||||
end
|
||||
|
||||
# Returns the expected response for a request of +http_method+ to +uri+ with the decoded +credentials+ and the expected +password+
|
||||
def expected_response(http_method, uri, credentials, password)
|
||||
ha1 = ::Digest::MD5.hexdigest([credentials[:username], credentials[:realm], password].join(':'))
|
||||
ha2 = ::Digest::MD5.hexdigest([http_method.to_s.upcase,uri].join(':'))
|
||||
::Digest::MD5.hexdigest([ha1,credentials[:nonce], credentials[:nc], credentials[:cnonce],credentials[:qop],ha2].join(':'))
|
||||
end
|
||||
|
||||
def encode_credentials(http_method, credentials, password)
|
||||
credentials[:response] = expected_response(http_method, credentials[:uri], credentials, password)
|
||||
"Digest " + credentials.sort_by {|x| x[0].to_s }.inject([]) {|a, v| a << "#{v[0]}='#{v[1]}'" }.join(', ')
|
||||
end
|
||||
|
||||
def decode_credentials(request)
|
||||
authorization(request).to_s.gsub(/^Digest\s+/,'').split(',').inject({}) do |hash, pair|
|
||||
key, value = pair.split('=', 2)
|
||||
hash[key.strip.to_sym] = value.to_s.gsub(/^"|"$/,'').gsub(/'/, '')
|
||||
hash
|
||||
end
|
||||
end
|
||||
|
||||
def authentication_header(controller, realm)
|
||||
controller.headers["WWW-Authenticate"] = %(Digest realm="#{realm}", qop="auth", algorithm=MD5, nonce="#{nonce(controller.request)}", opaque="#{opaque(controller.request)}")
|
||||
end
|
||||
|
||||
def authentication_request(controller, realm, message = "HTTP Digest: Access denied")
|
||||
authentication_header(controller, realm)
|
||||
controller.send! :render, :text => message, :status => :unauthorized
|
||||
end
|
||||
|
||||
# Uses an MD5 digest based on time to generate a value to be used only once.
|
||||
#
|
||||
# A server-specified data string which should be uniquely generated each time a 401 response is made.
|
||||
# It is recommended that this string be base64 or hexadecimal data.
|
||||
# Specifically, since the string is passed in the header lines as a quoted string, the double-quote character is not allowed.
|
||||
#
|
||||
# The contents of the nonce are implementation dependent.
|
||||
# The quality of the implementation depends on a good choice.
|
||||
# A nonce might, for example, be constructed as the base 64 encoding of
|
||||
#
|
||||
# => time-stamp H(time-stamp ":" ETag ":" private-key)
|
||||
#
|
||||
# where time-stamp is a server-generated time or other non-repeating value,
|
||||
# ETag is the value of the HTTP ETag header associated with the requested entity,
|
||||
# and private-key is data known only to the server.
|
||||
# With a nonce of this form a server would recalculate the hash portion after receiving the client authentication header and
|
||||
# reject the request if it did not match the nonce from that header or
|
||||
# if the time-stamp value is not recent enough. In this way the server can limit the time of the nonce's validity.
|
||||
# The inclusion of the ETag prevents a replay request for an updated version of the resource.
|
||||
# (Note: including the IP address of the client in the nonce would appear to offer the server the ability
|
||||
# to limit the reuse of the nonce to the same client that originally got it.
|
||||
# However, that would break proxy farms, where requests from a single user often go through different proxies in the farm.
|
||||
# Also, IP address spoofing is not that hard.)
|
||||
#
|
||||
# An implementation might choose not to accept a previously used nonce or a previously used digest, in order to
|
||||
# protect against a replay attack. Or, an implementation might choose to use one-time nonces or digests for
|
||||
# POST or PUT requests and a time-stamp for GET requests. For more details on the issues involved see Section 4
|
||||
# of this document.
|
||||
#
|
||||
# The nonce is opaque to the client.
|
||||
def nonce(request, time = Time.now)
|
||||
session_id = request.is_a?(String) ? request : request.session.session_id
|
||||
t = time.to_i
|
||||
hashed = [t, session_id]
|
||||
digest = ::Digest::MD5.hexdigest(hashed.join(":"))
|
||||
Base64.encode64("#{t}:#{digest}").gsub("\n", '')
|
||||
end
|
||||
|
||||
def validate_nonce(request, value)
|
||||
t = Base64.decode64(value).split(":").first.to_i
|
||||
raise Error.new(true), "Stale Nonce" if (t - Time.now.to_i).abs > 10 * 60
|
||||
n = nonce(request, t)
|
||||
raise Error.new(true, value, n), "Bad Nonce" unless n == value
|
||||
end
|
||||
|
||||
# Opaque based on digest of session_id
|
||||
def opaque(request)
|
||||
session_id = request.is_a?(String) ? request : request.session.session_id
|
||||
@opaque ||= Base64.encode64(::Digest::MD5::hexdigest(session_id)).gsub("\n", '')
|
||||
end
|
||||
end
|
||||
|
||||
class Error < RuntimeError
|
||||
attr_accessor :expected, :was
|
||||
def initialize(fatal = false, expected = nil, was = nil)
|
||||
@fatal = fatal
|
||||
@expected = expected
|
||||
@was = was
|
||||
end
|
||||
|
||||
def fatal?; @fatal; end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -68,12 +68,21 @@ module ActionController
|
||||
# A running counter of the number of requests processed.
|
||||
attr_accessor :request_count
|
||||
|
||||
# Nonce value for Digest Authentication, implicitly set on response with WWW-Authentication
|
||||
attr_accessor :nonce
|
||||
|
||||
# Opaque value for Digest Authentication, implicitly set on response with WWW-Authentication
|
||||
attr_accessor :opaque
|
||||
|
||||
# Opaque value for Authentication, implicitly set on response with WWW-Authentication
|
||||
attr_accessor :realm
|
||||
|
||||
class MultiPartNeededException < Exception
|
||||
end
|
||||
|
||||
# Create and initialize a new Session instance.
|
||||
def initialize(app)
|
||||
@application = app
|
||||
def initialize(app = nil)
|
||||
@application = app || ActionController::Dispatcher.new
|
||||
reset!
|
||||
end
|
||||
|
||||
@@ -243,6 +252,53 @@ module ActionController
|
||||
end
|
||||
alias xhr :xml_http_request
|
||||
|
||||
def request_with_noauth(http_method, uri, parameters, headers)
|
||||
process_with_auth http_method, uri, parameters, headers
|
||||
end
|
||||
|
||||
# Performs a request with the given http_method and parameters, including HTTP Basic authorization headers.
|
||||
# See get() for more details on paramters and headers.
|
||||
#
|
||||
# You can perform GET, POST, PUT, DELETE, and HEAD requests with #get_with_basic, #post_with_basic,
|
||||
# #put_with_basic, #delete_with_basic, and #head_with_basic.
|
||||
def request_with_basic(http_method, uri, parameters, headers, user_name, password)
|
||||
process_with_auth http_method, uri, parameters, headers.merge(:authorization => ActionController::HttpAuthentication::Basic.encode_credentials(user_name, password))
|
||||
end
|
||||
|
||||
# Performs a request with the given http_method and parameters, including HTTP Digest authorization headers.
|
||||
# See get() for more details on paramters and headers.
|
||||
#
|
||||
# You can perform GET, POST, PUT, DELETE, and HEAD requests with #get_with_digest, #post_with_digest,
|
||||
# #put_with_digest, #delete_with_digest, and #head_with_digest.
|
||||
def request_with_digest(http_method, uri, parameters, headers, user_name, password)
|
||||
# Realm, Nonce, and Opaque taken from previoius 401 response
|
||||
|
||||
credentials = {
|
||||
:username => user_name,
|
||||
:realm => @realm,
|
||||
:nonce => @nonce,
|
||||
:qop => "auth",
|
||||
:nc => "00000001",
|
||||
:cnonce => "0a4f113b",
|
||||
:opaque => @opaque,
|
||||
:uri => uri
|
||||
}
|
||||
|
||||
raise "Digest request without previous 401 response" if @opaque.nil?
|
||||
|
||||
process_with_auth http_method, uri, parameters, headers.merge(:authorization => ActionController::HttpAuthentication::Digest.encode_credentials(http_method, credentials, password))
|
||||
end
|
||||
|
||||
# def get_with_basic, def post_with_basic, def put_with_basic, def delete_with_basic, def head_with_basic
|
||||
# def get_with_digest, def post_with_digest, def put_with_digest, def delete_with_digest, def head_with_digest
|
||||
[:get, :post, :put, :delete, :head].each do |method|
|
||||
[:noauth, :basic, :digest].each do |auth_type|
|
||||
define_method("#{method}_with_#{auth_type}") do |uri, parameters, headers, *auth|
|
||||
send("request_with_#{auth_type}", method, uri, parameters, headers, *auth)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the URL for the given options, according to the rules specified
|
||||
# in the application's routes.
|
||||
def url_for(options)
|
||||
@@ -311,8 +367,10 @@ module ActionController
|
||||
env[key] = value
|
||||
end
|
||||
|
||||
unless ActionController::Base.respond_to?(:clear_last_instantiation!)
|
||||
ActionController::Base.module_eval { include ControllerCapture }
|
||||
[ControllerCapture, ActionController::ProcessWithTest].each do |mod|
|
||||
unless ActionController::Base < mod
|
||||
ActionController::Base.class_eval { include mod }
|
||||
end
|
||||
end
|
||||
|
||||
ActionController::Base.clear_last_instantiation!
|
||||
@@ -340,6 +398,7 @@ module ActionController
|
||||
if @controller = ActionController::Base.last_instantiation
|
||||
@request = @controller.request
|
||||
@response = @controller.response
|
||||
@controller.send(:set_test_assigns)
|
||||
else
|
||||
# Decorate responses from Rack Middleware and Rails Metal
|
||||
# as an Response for the purposes of integration testing
|
||||
@@ -364,6 +423,32 @@ module ActionController
|
||||
return status
|
||||
end
|
||||
|
||||
# Same as process, but handles authentication returns to perform
|
||||
# Basic or Digest authentication
|
||||
def process_with_auth(method, path, parameters = nil, headers = nil)
|
||||
status = process(method, path, parameters, headers)
|
||||
|
||||
if status == 401
|
||||
# Extract authentication information from response
|
||||
auth_data = @response.headers['WWW-Authenticate']
|
||||
if /^Basic /.match(auth_data)
|
||||
# extract realm, to be used in subsequent request
|
||||
@realm = auth_header.split(' ')[1]
|
||||
elsif /^Digest/.match(auth_data)
|
||||
creds = auth_data.to_s.gsub(/^Digest\s+/,'').split(',').inject({}) do |hash, pair|
|
||||
key, value = pair.split('=', 2)
|
||||
hash[key.strip.to_sym] = value.to_s.gsub(/^"|"$/,'').gsub(/'/, '')
|
||||
hash
|
||||
end
|
||||
@realm = creds[:realm]
|
||||
@nonce = creds[:nonce]
|
||||
@opaque = creds[:opaque]
|
||||
end
|
||||
end
|
||||
|
||||
return status
|
||||
end
|
||||
|
||||
# Encode the cookies hash in a format suitable for passing to a
|
||||
# request.
|
||||
def encode_cookies
|
||||
@@ -509,7 +594,6 @@ EOF
|
||||
# can use this method to open multiple sessions that ought to be tested
|
||||
# simultaneously.
|
||||
def open_session(application = nil)
|
||||
application ||= ActionController::Dispatcher.new
|
||||
session = Integration::Session.new(application)
|
||||
|
||||
# delegate the fixture accessors back to the test instance
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
module ActionController
|
||||
class MiddlewareStack < Array
|
||||
class Middleware
|
||||
def self.new(klass, *args, &block)
|
||||
if klass.is_a?(self)
|
||||
klass
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :args, :block
|
||||
|
||||
def initialize(klass, *args, &block)
|
||||
@@ -65,6 +73,19 @@ module ActionController
|
||||
block.call(self) if block_given?
|
||||
end
|
||||
|
||||
def insert(index, *objs)
|
||||
index = self.index(index) unless index.is_a?(Integer)
|
||||
objs = objs.map { |obj| Middleware.new(obj) }
|
||||
super(index, *objs)
|
||||
end
|
||||
|
||||
alias_method :insert_before, :insert
|
||||
|
||||
def insert_after(index, *objs)
|
||||
index = self.index(index) unless index.is_a?(Integer)
|
||||
insert(index + 1, *objs)
|
||||
end
|
||||
|
||||
def use(*args, &block)
|
||||
middleware = Middleware.new(*args, &block)
|
||||
push(middleware)
|
||||
|
||||
@@ -6,24 +6,17 @@ require 'active_support/memoizable'
|
||||
require 'action_controller/cgi_ext'
|
||||
|
||||
module ActionController
|
||||
# CgiRequest and TestRequest provide concrete implementations.
|
||||
class Request
|
||||
class Request < Rack::Request
|
||||
extend ActiveSupport::Memoizable
|
||||
|
||||
class SessionFixationAttempt < StandardError #:nodoc:
|
||||
end
|
||||
|
||||
# The hash of environment variables for this request,
|
||||
# such as { 'RAILS_ENV' => 'production' }.
|
||||
attr_reader :env
|
||||
|
||||
def initialize(env)
|
||||
@env = env
|
||||
super
|
||||
@parser = ActionController::RequestParser.new(env)
|
||||
end
|
||||
|
||||
%w[ AUTH_TYPE GATEWAY_INTERFACE PATH_INFO
|
||||
%w[ AUTH_TYPE GATEWAY_INTERFACE
|
||||
PATH_TRANSLATED REMOTE_HOST
|
||||
REMOTE_IDENT REMOTE_USER SCRIPT_NAME
|
||||
REMOTE_IDENT REMOTE_USER REMOTE_ADDR
|
||||
SERVER_NAME SERVER_PROTOCOL
|
||||
|
||||
HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING
|
||||
@@ -45,8 +38,7 @@ module ActionController
|
||||
# <tt>:get</tt>. If the request \method is not listed in the HTTP_METHODS
|
||||
# constant above, an UnknownHttpMethod exception is raised.
|
||||
def request_method
|
||||
method = @env['REQUEST_METHOD']
|
||||
HTTP_METHOD_LOOKUP[method] || raise(UnknownHttpMethod, "#{method}, accepted HTTP methods are #{HTTP_METHODS.to_sentence}")
|
||||
HTTP_METHOD_LOOKUP[super] || raise(UnknownHttpMethod, "#{super}, accepted HTTP methods are #{HTTP_METHODS.to_sentence}")
|
||||
end
|
||||
memoize :request_method
|
||||
|
||||
@@ -94,16 +86,15 @@ module ActionController
|
||||
|
||||
# Returns the content length of the request as an integer.
|
||||
def content_length
|
||||
@env['CONTENT_LENGTH'].to_i
|
||||
super.to_i
|
||||
end
|
||||
memoize :content_length
|
||||
|
||||
# The MIME type of the HTTP request, such as Mime::XML.
|
||||
#
|
||||
# For backward compatibility, the post \format is extracted from the
|
||||
# X-Post-Data-Format HTTP header if present.
|
||||
def content_type
|
||||
Mime::Type.lookup(parser.content_type_without_parameters)
|
||||
Mime::Type.lookup(@parser.content_type_without_parameters)
|
||||
end
|
||||
memoize :content_type
|
||||
|
||||
@@ -391,16 +382,17 @@ EOM
|
||||
# Read the request \body. This is useful for web services that need to
|
||||
# work with raw requests directly.
|
||||
def raw_post
|
||||
parser.raw_post
|
||||
@parser.raw_post
|
||||
end
|
||||
|
||||
# Returns both GET and POST \parameters in a single hash.
|
||||
def parameters
|
||||
@parameters ||= request_parameters.merge(query_parameters).update(path_parameters).with_indifferent_access
|
||||
end
|
||||
alias_method :params, :parameters
|
||||
|
||||
def path_parameters=(parameters) #:nodoc:
|
||||
@path_parameters = parameters
|
||||
@env["rack.routing_args"] = parameters
|
||||
@symbolized_path_parameters = @parameters = nil
|
||||
end
|
||||
|
||||
@@ -416,44 +408,35 @@ EOM
|
||||
#
|
||||
# See <tt>symbolized_path_parameters</tt> for symbolized keys.
|
||||
def path_parameters
|
||||
@path_parameters ||= {}
|
||||
@env["rack.routing_args"] ||= {}
|
||||
end
|
||||
|
||||
def body
|
||||
parser.body
|
||||
@parser.body
|
||||
end
|
||||
|
||||
def remote_addr
|
||||
@env['REMOTE_ADDR']
|
||||
# Override Rack's GET method to support nested query strings
|
||||
def GET
|
||||
@parser.query_parameters
|
||||
end
|
||||
alias_method :query_parameters, :GET
|
||||
|
||||
def referrer
|
||||
@env['HTTP_REFERER']
|
||||
end
|
||||
alias referer referrer
|
||||
|
||||
def query_parameters
|
||||
@query_parameters ||= parser.query_parameters
|
||||
end
|
||||
|
||||
def request_parameters
|
||||
@request_parameters ||= parser.request_parameters
|
||||
# Override Rack's POST method to support nested query strings
|
||||
def POST
|
||||
@parser.request_parameters
|
||||
end
|
||||
alias_method :request_parameters, :POST
|
||||
|
||||
def body_stream #:nodoc:
|
||||
@env['rack.input']
|
||||
end
|
||||
|
||||
def cookies
|
||||
Rack::Request.new(@env).cookies
|
||||
end
|
||||
|
||||
def session
|
||||
@env['rack.session'] ||= {}
|
||||
end
|
||||
|
||||
def session=(session) #:nodoc:
|
||||
@session = session
|
||||
@env['rack.session'] = session
|
||||
end
|
||||
|
||||
def reset_session
|
||||
@@ -476,9 +459,5 @@ EOM
|
||||
def named_host?(host)
|
||||
!(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host))
|
||||
end
|
||||
|
||||
def parser
|
||||
@parser ||= ActionController::RequestParser.new(@env)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,14 +2,15 @@ module ActionController
|
||||
class RequestParser
|
||||
def initialize(env)
|
||||
@env = env
|
||||
freeze
|
||||
end
|
||||
|
||||
def request_parameters
|
||||
@request_parameters ||= parse_formatted_request_parameters
|
||||
@env["action_controller.request_parser.request_parameters"] ||= parse_formatted_request_parameters
|
||||
end
|
||||
|
||||
def query_parameters
|
||||
@query_parameters ||= self.class.parse_query_parameters(query_string)
|
||||
@env["action_controller.request_parser.query_parameters"] ||= self.class.parse_query_parameters(query_string)
|
||||
end
|
||||
|
||||
# Returns the query string, accounting for server idiosyncrasies.
|
||||
@@ -90,7 +91,7 @@ module ActionController
|
||||
end
|
||||
|
||||
def content_length
|
||||
@content_length ||= @env['CONTENT_LENGTH'].to_i
|
||||
@env['CONTENT_LENGTH'].to_i
|
||||
end
|
||||
|
||||
# The raw content type string. Use when you need parameters such as
|
||||
|
||||
@@ -38,8 +38,8 @@ module ActionController #:nodoc:
|
||||
'ActionView::TemplateError' => 'template_error'
|
||||
}
|
||||
|
||||
RESCUES_TEMPLATE_PATH = ActionView::PathSet::Path.new(
|
||||
File.join(File.dirname(__FILE__), "templates"), true)
|
||||
RESCUES_TEMPLATE_PATH = ActionView::Template::EagerPath.new(
|
||||
File.join(File.dirname(__FILE__), "templates"))
|
||||
|
||||
def self.included(base) #:nodoc:
|
||||
base.cattr_accessor :rescue_responses
|
||||
@@ -59,7 +59,9 @@ module ActionController #:nodoc:
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
def process_with_exception(request, response, exception) #:nodoc:
|
||||
def call_with_exception(env, exception) #:nodoc:
|
||||
request = env["action_controller.rescue.request"] ||= Request.new(env)
|
||||
response = env["action_controller.rescue.response"] ||= Response.new
|
||||
new.process(request, response, :rescue_action, exception)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -231,10 +231,13 @@ module ActionController # :nodoc:
|
||||
# Don't set the Content-Length for block-based bodies as that would mean
|
||||
# reading it all into memory. Not nice for, say, a 2GB streaming file.
|
||||
def set_content_length!
|
||||
unless body.respond_to?(:call) || (status && status.to_s[0..2] == '304')
|
||||
self.headers["Content-Length"] ||= body.size
|
||||
if status && status.to_s[0..2] == '204'
|
||||
headers.delete('Content-Length')
|
||||
elsif length = headers['Content-Length']
|
||||
headers['Content-Length'] = length.to_s
|
||||
elsif !body.respond_to?(:call) && (!status || status.to_s[0..2] != '304')
|
||||
headers["Content-Length"] = body.size.to_s
|
||||
end
|
||||
headers["Content-Length"] = headers["Content-Length"].to_s if headers["Content-Length"]
|
||||
end
|
||||
|
||||
def convert_language!
|
||||
|
||||
@@ -195,8 +195,8 @@ module ActionController
|
||||
def formatted_#{selector}(*args) # def formatted_users_url(*args)
|
||||
ActiveSupport::Deprecation.warn( # ActiveSupport::Deprecation.warn(
|
||||
"formatted_#{selector}() has been deprecated. " + # "formatted_users_url() has been deprecated. " +
|
||||
"please pass format to the standard" + # "please pass format to the standard" +
|
||||
"#{selector}() method instead.", caller) # "users_url() method instead.", caller)
|
||||
"Please pass format to the standard " + # "Please pass format to the standard " +
|
||||
"#{selector} method instead.", caller) # "users_url method instead.", caller)
|
||||
#{selector}(*args) # users_url(*args)
|
||||
end # end
|
||||
protected :#{selector} # protected :users_url
|
||||
@@ -427,6 +427,12 @@ module ActionController
|
||||
end
|
||||
end
|
||||
|
||||
def call(env)
|
||||
request = Request.new(env)
|
||||
app = Routing::Routes.recognize(request)
|
||||
app.call(env).to_a
|
||||
end
|
||||
|
||||
def recognize(request)
|
||||
params = recognize_path(request.path, extract_request_environment(request))
|
||||
request.path_parameters = params.with_indifferent_access
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
require 'active_support/test_case'
|
||||
require 'action_controller/test_process'
|
||||
|
||||
module ActionController
|
||||
# Superclass for ActionController functional tests. Functional tests allow you to
|
||||
@@ -102,6 +103,8 @@ module ActionController
|
||||
#
|
||||
# assert_redirected_to page_url(:title => 'foo')
|
||||
class TestCase < ActiveSupport::TestCase
|
||||
include TestProcess
|
||||
|
||||
module Assertions
|
||||
%w(response selector tag dom routing model).each do |kind|
|
||||
include ActionController::Assertions.const_get("#{kind.camelize}Assertions")
|
||||
@@ -124,17 +127,18 @@ module ActionController
|
||||
#
|
||||
# The exception is stored in the exception accessor for further inspection.
|
||||
module RaiseActionExceptions
|
||||
attr_accessor :exception
|
||||
protected
|
||||
attr_accessor :exception
|
||||
|
||||
def rescue_action_without_handler(e)
|
||||
self.exception = e
|
||||
|
||||
if request.remote_addr == "0.0.0.0"
|
||||
raise(e)
|
||||
else
|
||||
super(e)
|
||||
def rescue_action_without_handler(e)
|
||||
self.exception = e
|
||||
|
||||
if request.remote_addr == "0.0.0.0"
|
||||
raise(e)
|
||||
else
|
||||
super(e)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
setup :setup_controller_request_and_response
|
||||
|
||||
@@ -1,32 +1,4 @@
|
||||
require 'action_controller/test_case'
|
||||
|
||||
module ActionController #:nodoc:
|
||||
class Base
|
||||
attr_reader :assigns
|
||||
|
||||
# Process a test request called with a TestRequest object.
|
||||
def self.process_test(request)
|
||||
new.process_test(request)
|
||||
end
|
||||
|
||||
def process_test(request) #:nodoc:
|
||||
process(request, TestResponse.new)
|
||||
end
|
||||
|
||||
def process_with_test(*args)
|
||||
returning process_without_test(*args) do
|
||||
@assigns = {}
|
||||
(instance_variable_names - @@protected_instance_variables).each do |var|
|
||||
value = instance_variable_get(var)
|
||||
@assigns[var[1..-1]] = value
|
||||
response.template.assigns[var[1..-1]] = value if response
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
alias_method_chain :process, :test
|
||||
end
|
||||
|
||||
class TestRequest < Request #:nodoc:
|
||||
attr_accessor :cookies, :session_options
|
||||
attr_accessor :query_parameters, :path, :session
|
||||
@@ -433,7 +405,9 @@ module ActionController #:nodoc:
|
||||
@request.session = ActionController::TestSession.new(session) unless session.nil?
|
||||
@request.session["flash"] = ActionController::Flash::FlashHash.new.update(flash) if flash
|
||||
build_request_uri(action, parameters)
|
||||
@controller.process(@request, @response)
|
||||
|
||||
Base.class_eval { include ProcessWithTest } unless Base < ProcessWithTest
|
||||
@controller.process_with_test(@request, @response)
|
||||
end
|
||||
|
||||
def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil)
|
||||
@@ -545,12 +519,24 @@ module ActionController #:nodoc:
|
||||
ActionController::Routing.const_set(:Routes, real_routes) if real_routes
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module Test
|
||||
module Unit
|
||||
class TestCase #:nodoc:
|
||||
include ActionController::TestProcess
|
||||
module ProcessWithTest #:nodoc:
|
||||
def self.included(base)
|
||||
base.class_eval { attr_reader :assigns }
|
||||
end
|
||||
|
||||
def process_with_test(*args)
|
||||
process(*args).tap { set_test_assigns }
|
||||
end
|
||||
|
||||
private
|
||||
def set_test_assigns
|
||||
@assigns = {}
|
||||
(instance_variable_names - self.class.protected_instance_variables).each do |var|
|
||||
name, value = var[1..-1], instance_variable_get(var)
|
||||
@assigns[name] = value
|
||||
response.template.assigns[name] = value if response
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -70,11 +70,12 @@ module ActionController
|
||||
top[-1][key] = value
|
||||
else
|
||||
top << {key => value}.with_indifferent_access
|
||||
push top.last
|
||||
value = top[key]
|
||||
end
|
||||
push top.last
|
||||
return top[key]
|
||||
else
|
||||
top << value
|
||||
return value
|
||||
end
|
||||
elsif top.is_a? Hash
|
||||
key = CGI.unescape(key)
|
||||
@@ -84,12 +85,10 @@ module ActionController
|
||||
else
|
||||
raise ArgumentError, "Don't know what to do: top is #{top.inspect}"
|
||||
end
|
||||
|
||||
return value
|
||||
end
|
||||
|
||||
def type_conflict!(klass, value)
|
||||
raise TypeError, "Conflicting types for parameter containers. Expected an instance of #{klass} but found an instance of #{value.class}. This can be caused by colliding Array and Hash parameters like qs[]=value&qs[key]=value. (The parameters received were #{value.inspect}.)"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -225,7 +225,7 @@ module ActionView #:nodoc:
|
||||
end
|
||||
|
||||
# Returns the result of a render that's dictated by the options hash. The primary options are:
|
||||
#
|
||||
#
|
||||
# * <tt>:partial</tt> - See ActionView::Partials.
|
||||
# * <tt>:update</tt> - Calls update_page with the block given.
|
||||
# * <tt>:file</tt> - Renders an explicit template file (this used to be the old default), add :locals to pass in those.
|
||||
|
||||
@@ -157,7 +157,7 @@ module ActionView
|
||||
# javascript_path "http://www.railsapplication.com/js/xmlhr" # => http://www.railsapplication.com/js/xmlhr.js
|
||||
# javascript_path "http://www.railsapplication.com/js/xmlhr.js" # => http://www.railsapplication.com/js/xmlhr.js
|
||||
def javascript_path(source)
|
||||
JavaScriptTag.new(self, @controller, source).public_path
|
||||
compute_public_path(source, 'javascripts', 'js')
|
||||
end
|
||||
alias_method :path_to_javascript, :javascript_path # aliased to avoid conflicts with a javascript_path named route
|
||||
|
||||
@@ -255,17 +255,15 @@ module ActionView
|
||||
joined_javascript_name = (cache == true ? "all" : cache) + ".js"
|
||||
joined_javascript_path = File.join(JAVASCRIPTS_DIR, joined_javascript_name)
|
||||
|
||||
unless File.exists?(joined_javascript_path)
|
||||
JavaScriptSources.create(self, @controller, sources, recursive).write_asset_file_contents(joined_javascript_path)
|
||||
end
|
||||
write_asset_file_contents(joined_javascript_path, compute_javascript_paths(sources, recursive)) unless File.exists?(joined_javascript_path)
|
||||
javascript_src_tag(joined_javascript_name, options)
|
||||
else
|
||||
JavaScriptSources.create(self, @controller, sources, recursive).expand_sources.collect { |source|
|
||||
javascript_src_tag(source, options)
|
||||
}.join("\n")
|
||||
expand_javascript_sources(sources, recursive).collect { |source| javascript_src_tag(source, options) }.join("\n")
|
||||
end
|
||||
end
|
||||
|
||||
@@javascript_expansions = { :defaults => JAVASCRIPT_DEFAULT_SOURCES.dup }
|
||||
|
||||
# Register one or more javascript files to be included when <tt>symbol</tt>
|
||||
# is passed to <tt>javascript_include_tag</tt>. This method is typically intended
|
||||
# to be called from plugin initialization to register javascript files
|
||||
@@ -278,9 +276,11 @@ module ActionView
|
||||
# <script type="text/javascript" src="/javascripts/body.js"></script>
|
||||
# <script type="text/javascript" src="/javascripts/tail.js"></script>
|
||||
def self.register_javascript_expansion(expansions)
|
||||
JavaScriptSources.expansions.merge!(expansions)
|
||||
@@javascript_expansions.merge!(expansions)
|
||||
end
|
||||
|
||||
@@stylesheet_expansions = {}
|
||||
|
||||
# Register one or more stylesheet files to be included when <tt>symbol</tt>
|
||||
# is passed to <tt>stylesheet_link_tag</tt>. This method is typically intended
|
||||
# to be called from plugin initialization to register stylesheet files
|
||||
@@ -293,7 +293,7 @@ module ActionView
|
||||
# <link href="/stylesheets/body.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
# <link href="/stylesheets/tail.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
def self.register_stylesheet_expansion(expansions)
|
||||
StylesheetSources.expansions.merge!(expansions)
|
||||
@@stylesheet_expansions.merge!(expansions)
|
||||
end
|
||||
|
||||
# Register one or more additional JavaScript files to be included when
|
||||
@@ -301,11 +301,11 @@ module ActionView
|
||||
# typically intended to be called from plugin initialization to register additional
|
||||
# .js files that the plugin installed in <tt>public/javascripts</tt>.
|
||||
def self.register_javascript_include_default(*sources)
|
||||
JavaScriptSources.expansions[:defaults].concat(sources)
|
||||
@@javascript_expansions[:defaults].concat(sources)
|
||||
end
|
||||
|
||||
def self.reset_javascript_include_default #:nodoc:
|
||||
JavaScriptSources.expansions[:defaults] = JAVASCRIPT_DEFAULT_SOURCES.dup
|
||||
@@javascript_expansions[:defaults] = JAVASCRIPT_DEFAULT_SOURCES.dup
|
||||
end
|
||||
|
||||
# Computes the path to a stylesheet asset in the public stylesheets directory.
|
||||
@@ -320,7 +320,7 @@ module ActionView
|
||||
# stylesheet_path "http://www.railsapplication.com/css/style" # => http://www.railsapplication.com/css/style.css
|
||||
# stylesheet_path "http://www.railsapplication.com/css/style.js" # => http://www.railsapplication.com/css/style.css
|
||||
def stylesheet_path(source)
|
||||
StylesheetTag.new(self, @controller, source).public_path
|
||||
compute_public_path(source, 'stylesheets', 'css')
|
||||
end
|
||||
alias_method :path_to_stylesheet, :stylesheet_path # aliased to avoid conflicts with a stylesheet_path named route
|
||||
|
||||
@@ -365,7 +365,6 @@ module ActionView
|
||||
# compressed by gzip (leading to faster transfers). Caching will only happen if ActionController::Base.perform_caching
|
||||
# is set to true (which is the case by default for the Rails production environment, but not for the development
|
||||
# environment). Examples:
|
||||
|
||||
#
|
||||
# ==== Examples
|
||||
# stylesheet_link_tag :all, :cache => true # when ActionController::Base.perform_caching is false =>
|
||||
@@ -396,14 +395,10 @@ module ActionView
|
||||
joined_stylesheet_name = (cache == true ? "all" : cache) + ".css"
|
||||
joined_stylesheet_path = File.join(STYLESHEETS_DIR, joined_stylesheet_name)
|
||||
|
||||
unless File.exists?(joined_stylesheet_path)
|
||||
StylesheetSources.create(self, @controller, sources, recursive).write_asset_file_contents(joined_stylesheet_path)
|
||||
end
|
||||
write_asset_file_contents(joined_stylesheet_path, compute_stylesheet_paths(sources, recursive)) unless File.exists?(joined_stylesheet_path)
|
||||
stylesheet_tag(joined_stylesheet_name, options)
|
||||
else
|
||||
StylesheetSources.create(self, @controller, sources, recursive).expand_sources.collect { |source|
|
||||
stylesheet_tag(source, options)
|
||||
}.join("\n")
|
||||
expand_stylesheet_sources(sources, recursive).collect { |source| stylesheet_tag(source, options) }.join("\n")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -418,7 +413,7 @@ module ActionView
|
||||
# image_path("/icons/edit.png") # => /icons/edit.png
|
||||
# image_path("http://www.railsapplication.com/img/edit.png") # => http://www.railsapplication.com/img/edit.png
|
||||
def image_path(source)
|
||||
ImageTag.new(self, @controller, source).public_path
|
||||
compute_public_path(source, 'images')
|
||||
end
|
||||
alias_method :path_to_image, :image_path # aliased to avoid conflicts with an image_path named route
|
||||
|
||||
@@ -473,7 +468,117 @@ module ActionView
|
||||
tag("img", options)
|
||||
end
|
||||
|
||||
def self.cache_asset_timestamps
|
||||
@@cache_asset_timestamps
|
||||
end
|
||||
|
||||
# You can enable or disable the asset tag timestamps cache.
|
||||
# With the cache enabled, the asset tag helper methods will make fewer
|
||||
# expense file system calls. However this prevents you from modifying
|
||||
# any asset files while the server is running.
|
||||
#
|
||||
# ActionView::Helpers::AssetTagHelper.cache_asset_timestamps = false
|
||||
def self.cache_asset_timestamps=(value)
|
||||
@@cache_asset_timestamps = value
|
||||
end
|
||||
|
||||
@@cache_asset_timestamps = true
|
||||
|
||||
private
|
||||
# Add the the extension +ext+ if not present. Return full URLs otherwise untouched.
|
||||
# Prefix with <tt>/dir/</tt> if lacking a leading +/+. Account for relative URL
|
||||
# roots. Rewrite the asset path for cache-busting asset ids. Include
|
||||
# asset host, if configured, with the correct request protocol.
|
||||
def compute_public_path(source, dir, ext = nil, include_host = true)
|
||||
has_request = @controller.respond_to?(:request)
|
||||
|
||||
if ext && (File.extname(source).blank? || File.exist?(File.join(ASSETS_DIR, dir, "#{source}.#{ext}")))
|
||||
source += ".#{ext}"
|
||||
end
|
||||
|
||||
unless source =~ %r{^[-a-z]+://}
|
||||
source = "/#{dir}/#{source}" unless source[0] == ?/
|
||||
|
||||
source = rewrite_asset_path(source)
|
||||
|
||||
if has_request && include_host
|
||||
unless source =~ %r{^#{ActionController::Base.relative_url_root}/}
|
||||
source = "#{ActionController::Base.relative_url_root}#{source}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if include_host && source !~ %r{^[-a-z]+://}
|
||||
host = compute_asset_host(source)
|
||||
|
||||
if has_request && !host.blank? && host !~ %r{^[-a-z]+://}
|
||||
host = "#{@controller.request.protocol}#{host}"
|
||||
end
|
||||
|
||||
"#{host}#{source}"
|
||||
else
|
||||
source
|
||||
end
|
||||
end
|
||||
|
||||
# Pick an asset host for this source. Returns +nil+ if no host is set,
|
||||
# the host if no wildcard is set, the host interpolated with the
|
||||
# numbers 0-3 if it contains <tt>%d</tt> (the number is the source hash mod 4),
|
||||
# or the value returned from invoking the proc if it's a proc or the value from
|
||||
# invoking call if it's an object responding to call.
|
||||
def compute_asset_host(source)
|
||||
if host = ActionController::Base.asset_host
|
||||
if host.is_a?(Proc) || host.respond_to?(:call)
|
||||
case host.is_a?(Proc) ? host.arity : host.method(:call).arity
|
||||
when 2
|
||||
request = @controller.respond_to?(:request) && @controller.request
|
||||
host.call(source, request)
|
||||
else
|
||||
host.call(source)
|
||||
end
|
||||
else
|
||||
(host =~ /%d/) ? host % (source.hash % 4) : host
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@asset_timestamps_cache = {}
|
||||
@@asset_timestamps_cache_guard = Mutex.new
|
||||
|
||||
# Use the RAILS_ASSET_ID environment variable or the source's
|
||||
# modification time as its cache-busting asset id.
|
||||
def rails_asset_id(source)
|
||||
if asset_id = ENV["RAILS_ASSET_ID"]
|
||||
asset_id
|
||||
else
|
||||
if @@cache_asset_timestamps && (asset_id = @@asset_timestamps_cache[source])
|
||||
asset_id
|
||||
else
|
||||
path = File.join(ASSETS_DIR, source)
|
||||
asset_id = File.exist?(path) ? File.mtime(path).to_i.to_s : ''
|
||||
|
||||
if @@cache_asset_timestamps
|
||||
@@asset_timestamps_cache_guard.synchronize do
|
||||
@@asset_timestamps_cache[source] = asset_id
|
||||
end
|
||||
end
|
||||
|
||||
asset_id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Break out the asset path rewrite in case plugins wish to put the asset id
|
||||
# someplace other than the query string.
|
||||
def rewrite_asset_path(source)
|
||||
asset_id = rails_asset_id(source)
|
||||
if asset_id.blank?
|
||||
source
|
||||
else
|
||||
source + "?#{asset_id}"
|
||||
end
|
||||
end
|
||||
|
||||
def javascript_src_tag(source, options)
|
||||
content_tag("script", "", { "type" => Mime::JS, "src" => path_to_javascript(source) }.merge(options))
|
||||
end
|
||||
@@ -482,344 +587,71 @@ module ActionView
|
||||
tag("link", { "rel" => "stylesheet", "type" => Mime::CSS, "media" => "screen", "href" => html_escape(path_to_stylesheet(source)) }.merge(options), false, false)
|
||||
end
|
||||
|
||||
module ImageAsset
|
||||
DIRECTORY = 'images'.freeze
|
||||
def compute_javascript_paths(*args)
|
||||
expand_javascript_sources(*args).collect { |source| compute_public_path(source, 'javascripts', 'js', false) }
|
||||
end
|
||||
|
||||
def directory
|
||||
DIRECTORY
|
||||
end
|
||||
def compute_stylesheet_paths(*args)
|
||||
expand_stylesheet_sources(*args).collect { |source| compute_public_path(source, 'stylesheets', 'css', false) }
|
||||
end
|
||||
|
||||
def extension
|
||||
nil
|
||||
def expand_javascript_sources(sources, recursive = false)
|
||||
if sources.include?(:all)
|
||||
all_javascript_files = collect_asset_files(JAVASCRIPTS_DIR, ('**' if recursive), '*.js')
|
||||
((determine_source(:defaults, @@javascript_expansions).dup & all_javascript_files) + all_javascript_files).uniq
|
||||
else
|
||||
expanded_sources = sources.collect do |source|
|
||||
determine_source(source, @@javascript_expansions)
|
||||
end.flatten
|
||||
expanded_sources << "application" if sources.include?(:defaults) && File.exist?(File.join(JAVASCRIPTS_DIR, "application.js"))
|
||||
expanded_sources
|
||||
end
|
||||
end
|
||||
|
||||
module JavaScriptAsset
|
||||
DIRECTORY = 'javascripts'.freeze
|
||||
EXTENSION = 'js'.freeze
|
||||
|
||||
def public_directory
|
||||
JAVASCRIPTS_DIR
|
||||
end
|
||||
|
||||
def directory
|
||||
DIRECTORY
|
||||
end
|
||||
|
||||
def extension
|
||||
EXTENSION
|
||||
def expand_stylesheet_sources(sources, recursive)
|
||||
if sources.first == :all
|
||||
collect_asset_files(STYLESHEETS_DIR, ('**' if recursive), '*.css')
|
||||
else
|
||||
sources.collect do |source|
|
||||
determine_source(source, @@stylesheet_expansions)
|
||||
end.flatten
|
||||
end
|
||||
end
|
||||
|
||||
module StylesheetAsset
|
||||
DIRECTORY = 'stylesheets'.freeze
|
||||
EXTENSION = 'css'.freeze
|
||||
|
||||
def public_directory
|
||||
STYLESHEETS_DIR
|
||||
end
|
||||
|
||||
def directory
|
||||
DIRECTORY
|
||||
end
|
||||
|
||||
def extension
|
||||
EXTENSION
|
||||
def determine_source(source, collection)
|
||||
case source
|
||||
when Symbol
|
||||
collection[source] || raise(ArgumentError, "No expansion found for #{source.inspect}")
|
||||
else
|
||||
source
|
||||
end
|
||||
end
|
||||
|
||||
class AssetTag
|
||||
extend ActiveSupport::Memoizable
|
||||
|
||||
Cache = {}
|
||||
CacheGuard = Mutex.new
|
||||
|
||||
ProtocolRegexp = %r{^[-a-z]+://}.freeze
|
||||
|
||||
def initialize(template, controller, source, include_host = true)
|
||||
# NOTE: The template arg is temporarily needed for a legacy plugin
|
||||
# hook that is expected to call rewrite_asset_path on the
|
||||
# template. This should eventually be removed.
|
||||
@template = template
|
||||
@controller = controller
|
||||
@source = source
|
||||
@include_host = include_host
|
||||
@cache_key = if controller.respond_to?(:request)
|
||||
[self.class.name,controller.request.protocol,
|
||||
ActionController::Base.asset_host,
|
||||
ActionController::Base.relative_url_root,
|
||||
source, include_host]
|
||||
else
|
||||
[self.class.name,ActionController::Base.asset_host, source, include_host]
|
||||
end
|
||||
end
|
||||
|
||||
def public_path
|
||||
compute_public_path(@source)
|
||||
end
|
||||
memoize :public_path
|
||||
|
||||
def asset_file_path
|
||||
File.join(ASSETS_DIR, public_path.split('?').first)
|
||||
end
|
||||
memoize :asset_file_path
|
||||
|
||||
def contents
|
||||
File.read(asset_file_path)
|
||||
end
|
||||
|
||||
def mtime
|
||||
File.mtime(asset_file_path)
|
||||
end
|
||||
|
||||
private
|
||||
def request
|
||||
request? && @controller.request
|
||||
end
|
||||
|
||||
def request?
|
||||
@controller.respond_to?(:request)
|
||||
end
|
||||
|
||||
# Add the the extension +ext+ if not present. Return full URLs otherwise untouched.
|
||||
# Prefix with <tt>/dir/</tt> if lacking a leading +/+. Account for relative URL
|
||||
# roots. Rewrite the asset path for cache-busting asset ids. Include
|
||||
# asset host, if configured, with the correct request protocol.
|
||||
def compute_public_path(source)
|
||||
if source =~ ProtocolRegexp
|
||||
source += ".#{extension}" if missing_extension?(source)
|
||||
source = prepend_asset_host(source)
|
||||
source
|
||||
else
|
||||
CacheGuard.synchronize do
|
||||
Cache[@cache_key + [source]] ||= begin
|
||||
source += ".#{extension}" if missing_extension?(source) || file_exists_with_extension?(source)
|
||||
source = "/#{directory}/#{source}" unless source[0] == ?/
|
||||
source = rewrite_asset_path(source)
|
||||
source = prepend_relative_url_root(source)
|
||||
source = prepend_asset_host(source)
|
||||
source
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def missing_extension?(source)
|
||||
extension && File.extname(source).blank?
|
||||
end
|
||||
|
||||
def file_exists_with_extension?(source)
|
||||
extension && File.exist?(File.join(ASSETS_DIR, directory, "#{source}.#{extension}"))
|
||||
end
|
||||
|
||||
def prepend_relative_url_root(source)
|
||||
relative_url_root = ActionController::Base.relative_url_root
|
||||
if request? && @include_host && source !~ %r{^#{relative_url_root}/}
|
||||
"#{relative_url_root}#{source}"
|
||||
else
|
||||
source
|
||||
end
|
||||
end
|
||||
|
||||
def prepend_asset_host(source)
|
||||
if @include_host && source !~ ProtocolRegexp
|
||||
host = compute_asset_host(source)
|
||||
if request? && !host.blank? && host !~ ProtocolRegexp
|
||||
host = "#{request.protocol}#{host}"
|
||||
end
|
||||
"#{host}#{source}"
|
||||
else
|
||||
source
|
||||
end
|
||||
end
|
||||
|
||||
# Pick an asset host for this source. Returns +nil+ if no host is set,
|
||||
# the host if no wildcard is set, the host interpolated with the
|
||||
# numbers 0-3 if it contains <tt>%d</tt> (the number is the source hash mod 4),
|
||||
# or the value returned from invoking the proc if it's a proc or the value from
|
||||
# invoking call if it's an object responding to call.
|
||||
def compute_asset_host(source)
|
||||
if host = ActionController::Base.asset_host
|
||||
if host.is_a?(Proc) || host.respond_to?(:call)
|
||||
case host.is_a?(Proc) ? host.arity : host.method(:call).arity
|
||||
when 2
|
||||
host.call(source, request)
|
||||
else
|
||||
host.call(source)
|
||||
end
|
||||
else
|
||||
(host =~ /%d/) ? host % (source.hash % 4) : host
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Use the RAILS_ASSET_ID environment variable or the source's
|
||||
# modification time as its cache-busting asset id.
|
||||
def rails_asset_id(source)
|
||||
if asset_id = ENV["RAILS_ASSET_ID"]
|
||||
asset_id
|
||||
else
|
||||
path = File.join(ASSETS_DIR, source)
|
||||
|
||||
if File.exist?(path)
|
||||
File.mtime(path).to_i.to_s
|
||||
else
|
||||
''
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Break out the asset path rewrite in case plugins wish to put the asset id
|
||||
# someplace other than the query string.
|
||||
def rewrite_asset_path(source)
|
||||
if @template.respond_to?(:rewrite_asset_path)
|
||||
# DEPRECATE: This way to override rewrite_asset_path
|
||||
@template.send(:rewrite_asset_path, source)
|
||||
else
|
||||
asset_id = rails_asset_id(source)
|
||||
if asset_id.blank?
|
||||
source
|
||||
else
|
||||
"#{source}?#{asset_id}"
|
||||
end
|
||||
end
|
||||
end
|
||||
def join_asset_file_contents(paths)
|
||||
paths.collect { |path| File.read(asset_file_path(path)) }.join("\n\n")
|
||||
end
|
||||
|
||||
class ImageTag < AssetTag
|
||||
include ImageAsset
|
||||
def write_asset_file_contents(joined_asset_path, asset_paths)
|
||||
FileUtils.mkdir_p(File.dirname(joined_asset_path))
|
||||
File.open(joined_asset_path, "w+") { |cache| cache.write(join_asset_file_contents(asset_paths)) }
|
||||
|
||||
# Set mtime to the latest of the combined files to allow for
|
||||
# consistent ETag without a shared filesystem.
|
||||
mt = asset_paths.map { |p| File.mtime(asset_file_path(p)) }.max
|
||||
File.utime(mt, mt, joined_asset_path)
|
||||
end
|
||||
|
||||
class JavaScriptTag < AssetTag
|
||||
include JavaScriptAsset
|
||||
def asset_file_path(path)
|
||||
File.join(ASSETS_DIR, path.split('?').first)
|
||||
end
|
||||
|
||||
class StylesheetTag < AssetTag
|
||||
include StylesheetAsset
|
||||
end
|
||||
def collect_asset_files(*path)
|
||||
dir = path.first
|
||||
|
||||
class AssetCollection
|
||||
extend ActiveSupport::Memoizable
|
||||
|
||||
Cache = {}
|
||||
CacheGuard = Mutex.new
|
||||
|
||||
def self.create(template, controller, sources, recursive)
|
||||
CacheGuard.synchronize do
|
||||
key = [self, sources, recursive]
|
||||
Cache[key] ||= new(template, controller, sources, recursive).freeze
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(template, controller, sources, recursive)
|
||||
# NOTE: The template arg is temporarily needed for a legacy plugin
|
||||
# hook. See NOTE under AssetTag#initialize for more details
|
||||
@template = template
|
||||
@controller = controller
|
||||
@sources = sources
|
||||
@recursive = recursive
|
||||
end
|
||||
|
||||
def write_asset_file_contents(joined_asset_path)
|
||||
FileUtils.mkdir_p(File.dirname(joined_asset_path))
|
||||
File.open(joined_asset_path, "w+") { |cache| cache.write(joined_contents) }
|
||||
mt = latest_mtime
|
||||
File.utime(mt, mt, joined_asset_path)
|
||||
end
|
||||
|
||||
private
|
||||
def determine_source(source, collection)
|
||||
case source
|
||||
when Symbol
|
||||
collection[source] || raise(ArgumentError, "No expansion found for #{source.inspect}")
|
||||
else
|
||||
source
|
||||
end
|
||||
end
|
||||
|
||||
def validate_sources!
|
||||
@sources.collect { |source| determine_source(source, self.class.expansions) }.flatten
|
||||
end
|
||||
|
||||
def all_asset_files
|
||||
path = [public_directory, ('**' if @recursive), "*.#{extension}"].compact
|
||||
Dir[File.join(*path)].collect { |file|
|
||||
file[-(file.size - public_directory.size - 1)..-1].sub(/\.\w+$/, '')
|
||||
}.sort
|
||||
end
|
||||
|
||||
def tag_sources
|
||||
expand_sources.collect { |source| tag_class.new(@template, @controller, source, false) }
|
||||
end
|
||||
|
||||
def joined_contents
|
||||
tag_sources.collect { |source| source.contents }.join("\n\n")
|
||||
end
|
||||
|
||||
# Set mtime to the latest of the combined files to allow for
|
||||
# consistent ETag without a shared filesystem.
|
||||
def latest_mtime
|
||||
tag_sources.map { |source| source.mtime }.max
|
||||
end
|
||||
end
|
||||
|
||||
class JavaScriptSources < AssetCollection
|
||||
include JavaScriptAsset
|
||||
|
||||
EXPANSIONS = { :defaults => JAVASCRIPT_DEFAULT_SOURCES.dup }
|
||||
|
||||
def self.expansions
|
||||
EXPANSIONS
|
||||
end
|
||||
|
||||
APPLICATION_JS = "application".freeze
|
||||
APPLICATION_FILE = "application.js".freeze
|
||||
|
||||
def expand_sources
|
||||
if @sources.include?(:all)
|
||||
assets = all_asset_files
|
||||
((defaults.dup & assets) + assets).uniq!
|
||||
else
|
||||
expanded_sources = validate_sources!
|
||||
expanded_sources << APPLICATION_JS if include_application?
|
||||
expanded_sources
|
||||
end
|
||||
end
|
||||
memoize :expand_sources
|
||||
|
||||
private
|
||||
def tag_class
|
||||
JavaScriptTag
|
||||
end
|
||||
|
||||
def defaults
|
||||
determine_source(:defaults, self.class.expansions)
|
||||
end
|
||||
|
||||
def include_application?
|
||||
@sources.include?(:defaults) && File.exist?(File.join(JAVASCRIPTS_DIR, APPLICATION_FILE))
|
||||
end
|
||||
end
|
||||
|
||||
class StylesheetSources < AssetCollection
|
||||
include StylesheetAsset
|
||||
|
||||
EXPANSIONS = {}
|
||||
|
||||
def self.expansions
|
||||
EXPANSIONS
|
||||
end
|
||||
|
||||
def expand_sources
|
||||
@sources.first == :all ? all_asset_files : validate_sources!
|
||||
end
|
||||
memoize :expand_sources
|
||||
|
||||
private
|
||||
def tag_class
|
||||
StylesheetTag
|
||||
end
|
||||
Dir[File.join(*path.compact)].collect do |file|
|
||||
file[-(file.size - dir.size - 1)..-1].sub(/\.\w+$/, '')
|
||||
end.sort
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -18,12 +18,33 @@ module ActionView
|
||||
# That would add something like "Process data files (345.2ms)" to the log,
|
||||
# which you can then use to compare timings when optimizing your code.
|
||||
#
|
||||
# You may give an optional logger level as the second argument
|
||||
# You may give an optional logger level as the :level option.
|
||||
# (:debug, :info, :warn, :error); the default value is :info.
|
||||
def benchmark(message = "Benchmarking", level = :info)
|
||||
#
|
||||
# <% benchmark "Low-level files", :level => :debug do %>
|
||||
# <%= lowlevel_files_operation %>
|
||||
# <% end %>
|
||||
#
|
||||
# Finally, you can pass true as the third argument to silence all log activity
|
||||
# inside the block. This is great for boiling down a noisy block to just a single statement:
|
||||
#
|
||||
# <% benchmark "Process data files", :level => :info, :silence => true do %>
|
||||
# <%= expensive_and_chatty_files_operation %>
|
||||
# <% end %>
|
||||
def benchmark(message = "Benchmarking", options = {})
|
||||
if controller.logger
|
||||
ms = Benchmark.ms { yield }
|
||||
controller.logger.send(level, '%s (%.1fms)' % [message, ms])
|
||||
if options.is_a?(Symbol)
|
||||
ActiveSupport::Deprecation.warn("use benchmark('#{message}', :level => :#{options}) instead", caller)
|
||||
options = { :level => options, :silence => false }
|
||||
else
|
||||
options.assert_valid_keys(:level, :silence)
|
||||
options[:level] ||= :info
|
||||
end
|
||||
|
||||
result = nil
|
||||
ms = Benchmark.ms { result = options[:silence] ? controller.logger.silence { yield } : yield }
|
||||
controller.logger.send(options[:level], '%s (%.1fms)' % [ message, ms ])
|
||||
result
|
||||
else
|
||||
yield
|
||||
end
|
||||
|
||||
@@ -12,7 +12,7 @@ module ActionView #:nodoc:
|
||||
|
||||
private
|
||||
# Always recompile inline templates
|
||||
def recompile?(local_assigns)
|
||||
def recompile?
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,7 +2,7 @@ module ActionView #:nodoc:
|
||||
class PathSet < Array #:nodoc:
|
||||
def self.type_cast(obj)
|
||||
if obj.is_a?(String)
|
||||
Path.new(obj)
|
||||
Template::EagerPath.new(obj)
|
||||
else
|
||||
obj
|
||||
end
|
||||
@@ -32,111 +32,6 @@ module ActionView #:nodoc:
|
||||
super(*objs.map { |obj| self.class.type_cast(obj) })
|
||||
end
|
||||
|
||||
class Path #:nodoc:
|
||||
attr_reader :path, :paths
|
||||
delegate :hash, :inspect, :to => :path
|
||||
|
||||
def initialize(path, load = false)
|
||||
raise ArgumentError, "path already is a Path class" if path.is_a?(Path)
|
||||
@path = path.freeze
|
||||
reload! if load
|
||||
end
|
||||
|
||||
def to_s
|
||||
if defined?(RAILS_ROOT)
|
||||
path.to_s.sub(/^#{Regexp.escape(File.expand_path(RAILS_ROOT))}\//, '')
|
||||
else
|
||||
path.to_s
|
||||
end
|
||||
end
|
||||
|
||||
def to_str
|
||||
path.to_str
|
||||
end
|
||||
|
||||
def ==(path)
|
||||
to_str == path.to_str
|
||||
end
|
||||
|
||||
def eql?(path)
|
||||
to_str == path.to_str
|
||||
end
|
||||
|
||||
# Returns a ActionView::Template object for the given path string. The
|
||||
# input path should be relative to the view path directory,
|
||||
# +hello/index.html.erb+. This method also has a special exception to
|
||||
# match partial file names without a handler extension. So
|
||||
# +hello/index.html+ will match the first template it finds with a
|
||||
# known template extension, +hello/index.html.erb+. Template extensions
|
||||
# should not be confused with format extensions +html+, +js+, +xml+,
|
||||
# etc. A format must be supplied to match a formated file. +hello/index+
|
||||
# will never match +hello/index.html.erb+.
|
||||
#
|
||||
# This method also has two different implementations, one that is "lazy"
|
||||
# and makes file system calls every time and the other is cached,
|
||||
# "eager" which looks up the template in an in memory index. The "lazy"
|
||||
# version is designed for development where you want to automatically
|
||||
# find new templates between requests. The "eager" version is designed
|
||||
# for production mode and it is much faster but requires more time
|
||||
# upfront to build the file index.
|
||||
def [](path)
|
||||
if loaded?
|
||||
@paths[path]
|
||||
else
|
||||
Dir.glob("#{@path}/#{path}*").each do |file|
|
||||
template = create_template(file)
|
||||
if template.accessible_paths.include?(path)
|
||||
return template
|
||||
end
|
||||
end
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def loaded?
|
||||
@loaded ? true : false
|
||||
end
|
||||
|
||||
def load
|
||||
reload! unless loaded?
|
||||
self
|
||||
end
|
||||
|
||||
# Rebuild load path directory cache
|
||||
def reload!
|
||||
@paths = {}
|
||||
|
||||
templates_in_path do |template|
|
||||
template.load!
|
||||
template.accessible_paths.each do |path|
|
||||
@paths[path] = template
|
||||
end
|
||||
end
|
||||
|
||||
@paths.freeze
|
||||
@loaded = true
|
||||
end
|
||||
|
||||
private
|
||||
def templates_in_path
|
||||
(Dir.glob("#{@path}/**/*/**") | Dir.glob("#{@path}/**")).each do |file|
|
||||
yield create_template(file) unless File.directory?(file)
|
||||
end
|
||||
end
|
||||
|
||||
def create_template(file)
|
||||
Template.new(file.split("#{self}/").last, self)
|
||||
end
|
||||
end
|
||||
|
||||
def load
|
||||
each { |path| path.load }
|
||||
end
|
||||
|
||||
def reload!
|
||||
each { |path| path.reload! }
|
||||
end
|
||||
|
||||
def find_template(original_template_path, format = nil)
|
||||
return original_template_path if original_template_path.respond_to?(:render)
|
||||
template_path = original_template_path.sub(/^\//, '')
|
||||
|
||||
@@ -60,7 +60,7 @@ module ActionView
|
||||
def compile(local_assigns)
|
||||
render_symbol = method_name(local_assigns)
|
||||
|
||||
if recompile?(render_symbol)
|
||||
if !Base::CompiledTemplates.method_defined?(render_symbol) || recompile?
|
||||
compile!(render_symbol, local_assigns)
|
||||
end
|
||||
end
|
||||
@@ -89,11 +89,8 @@ module ActionView
|
||||
end
|
||||
end
|
||||
|
||||
# Method to check whether template compilation is necessary.
|
||||
# The template will be compiled if the file has not been compiled yet, or
|
||||
# if local_assigns has a new key, which isn't supported by the compiled code yet.
|
||||
def recompile?(symbol)
|
||||
!Base::CompiledTemplates.method_defined?(symbol) || !loaded?
|
||||
def recompile?
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -25,12 +25,11 @@ module ActionView
|
||||
end
|
||||
|
||||
def render_partial(view, object = nil, local_assigns = {}, as = nil)
|
||||
object ||= local_assigns[:object] ||
|
||||
local_assigns[variable_name]
|
||||
object ||= local_assigns[:object] || local_assigns[variable_name]
|
||||
|
||||
if view.respond_to?(:controller)
|
||||
if object.nil? && view.respond_to?(:controller)
|
||||
ivar = :"@#{variable_name}"
|
||||
object ||=
|
||||
object =
|
||||
if view.controller.instance_variable_defined?(ivar)
|
||||
ActiveSupport::Deprecation::DeprecatedObjectProxy.new(
|
||||
view.controller.instance_variable_get(ivar),
|
||||
|
||||
@@ -1,5 +1,83 @@
|
||||
module ActionView #:nodoc:
|
||||
class Template
|
||||
class Path
|
||||
attr_reader :path, :paths
|
||||
delegate :hash, :inspect, :to => :path
|
||||
|
||||
def initialize(path)
|
||||
raise ArgumentError, "path already is a Path class" if path.is_a?(Path)
|
||||
@path = path.freeze
|
||||
end
|
||||
|
||||
def to_s
|
||||
if defined?(RAILS_ROOT)
|
||||
path.to_s.sub(/^#{Regexp.escape(File.expand_path(RAILS_ROOT))}\//, '')
|
||||
else
|
||||
path.to_s
|
||||
end
|
||||
end
|
||||
|
||||
def to_str
|
||||
path.to_str
|
||||
end
|
||||
|
||||
def ==(path)
|
||||
to_str == path.to_str
|
||||
end
|
||||
|
||||
def eql?(path)
|
||||
to_str == path.to_str
|
||||
end
|
||||
|
||||
# Returns a ActionView::Template object for the given path string. The
|
||||
# input path should be relative to the view path directory,
|
||||
# +hello/index.html.erb+. This method also has a special exception to
|
||||
# match partial file names without a handler extension. So
|
||||
# +hello/index.html+ will match the first template it finds with a
|
||||
# known template extension, +hello/index.html.erb+. Template extensions
|
||||
# should not be confused with format extensions +html+, +js+, +xml+,
|
||||
# etc. A format must be supplied to match a formated file. +hello/index+
|
||||
# will never match +hello/index.html.erb+.
|
||||
def [](path)
|
||||
templates_in_path do |template|
|
||||
if template.accessible_paths.include?(path)
|
||||
return template
|
||||
end
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
private
|
||||
def templates_in_path
|
||||
(Dir.glob("#{@path}/**/*/**") | Dir.glob("#{@path}/**")).each do |file|
|
||||
yield create_template(file) unless File.directory?(file)
|
||||
end
|
||||
end
|
||||
|
||||
def create_template(file)
|
||||
Template.new(file.split("#{self}/").last, self)
|
||||
end
|
||||
end
|
||||
|
||||
class EagerPath < Path
|
||||
def initialize(path)
|
||||
super
|
||||
|
||||
@paths = {}
|
||||
templates_in_path do |template|
|
||||
template.load!
|
||||
template.accessible_paths.each do |path|
|
||||
@paths[path] = template
|
||||
end
|
||||
end
|
||||
@paths.freeze
|
||||
end
|
||||
|
||||
def [](path)
|
||||
@paths[path]
|
||||
end
|
||||
end
|
||||
|
||||
extend TemplateHandlers
|
||||
extend ActiveSupport::Memoizable
|
||||
include Renderable
|
||||
@@ -115,13 +193,12 @@ module ActionView #:nodoc:
|
||||
File.mtime(filename) > mtime
|
||||
end
|
||||
|
||||
def loaded?
|
||||
@loaded
|
||||
def recompile?
|
||||
!@cached
|
||||
end
|
||||
|
||||
def load!
|
||||
@loaded = true
|
||||
compile({})
|
||||
@cached = true
|
||||
freeze
|
||||
end
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
require 'active_support/test_case'
|
||||
|
||||
module ActionView
|
||||
class Base
|
||||
alias_method :initialize_without_template_tracking, :initialize
|
||||
@@ -21,6 +23,7 @@ module ActionView
|
||||
|
||||
class TestCase < ActiveSupport::TestCase
|
||||
include ActionController::TestCase::Assertions
|
||||
include ActionController::TestProcess
|
||||
|
||||
class_inheritable_accessor :helper_class
|
||||
@@helper_class = nil
|
||||
|
||||
@@ -34,7 +34,6 @@ ActionController::Base.session_store = nil
|
||||
|
||||
FIXTURE_LOAD_PATH = File.join(File.dirname(__FILE__), 'fixtures')
|
||||
ActionController::Base.view_paths = FIXTURE_LOAD_PATH
|
||||
ActionController::Base.view_paths.load
|
||||
|
||||
def uses_mocha(test_name)
|
||||
yield
|
||||
|
||||
@@ -19,17 +19,14 @@ class AddressesTestController < ActionController::Base
|
||||
def self.controller_path; "addresses"; end
|
||||
end
|
||||
|
||||
class AddressesTest < Test::Unit::TestCase
|
||||
def setup
|
||||
@controller = AddressesTestController.new
|
||||
class AddressesTest < ActionController::TestCase
|
||||
tests AddressesTestController
|
||||
|
||||
def setup
|
||||
# enable a logger so that (e.g.) the benchmarking stuff runs, so we can get
|
||||
# a more accurate simulation of what happens in "real life".
|
||||
@controller.logger = Logger.new(nil)
|
||||
|
||||
@request = ActionController::TestRequest.new
|
||||
@response = ActionController::TestResponse.new
|
||||
|
||||
@request.host = "www.nextangle.com"
|
||||
end
|
||||
|
||||
|
||||
@@ -129,6 +129,8 @@ class PerformActionTest < ActionController::TestCase
|
||||
@response = ActionController::TestResponse.new
|
||||
|
||||
@request.host = "www.nextangle.com"
|
||||
|
||||
rescue_action_in_public!
|
||||
end
|
||||
|
||||
def test_get_on_priv_should_show_selector
|
||||
@@ -164,14 +166,12 @@ class PerformActionTest < ActionController::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
class DefaultUrlOptionsTest < Test::Unit::TestCase
|
||||
class DefaultUrlOptionsTest < ActionController::TestCase
|
||||
tests DefaultUrlOptionsController
|
||||
|
||||
def setup
|
||||
@controller = DefaultUrlOptionsController.new
|
||||
|
||||
@request = ActionController::TestRequest.new
|
||||
@response = ActionController::TestResponse.new
|
||||
|
||||
@request.host = 'www.example.com'
|
||||
rescue_action_in_public!
|
||||
end
|
||||
|
||||
def test_default_url_options_are_used_if_set
|
||||
@@ -189,14 +189,12 @@ class DefaultUrlOptionsTest < Test::Unit::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
class EmptyUrlOptionsTest < Test::Unit::TestCase
|
||||
class EmptyUrlOptionsTest < ActionController::TestCase
|
||||
tests NonEmptyController
|
||||
|
||||
def setup
|
||||
@controller = NonEmptyController.new
|
||||
|
||||
@request = ActionController::TestRequest.new
|
||||
@response = ActionController::TestResponse.new
|
||||
|
||||
@request.host = 'www.example.com'
|
||||
rescue_action_in_public!
|
||||
end
|
||||
|
||||
def test_ensure_url_for_works_as_expected_when_called_with_no_options_if_default_url_options_is_not_set
|
||||
|
||||
@@ -11,17 +11,17 @@ class BenchmarkedController < ActionController::Base
|
||||
end
|
||||
end
|
||||
|
||||
class BenchmarkTest < Test::Unit::TestCase
|
||||
class BenchmarkTest < ActionController::TestCase
|
||||
tests BenchmarkedController
|
||||
|
||||
class MockLogger
|
||||
def method_missing(*args)
|
||||
end
|
||||
end
|
||||
|
||||
def setup
|
||||
@controller = BenchmarkedController.new
|
||||
# benchmark doesn't do anything unless a logger is set
|
||||
@controller.logger = MockLogger.new
|
||||
@request, @response = ActionController::TestRequest.new, ActionController::TestResponse.new
|
||||
@request.host = "test.actioncontroller.i"
|
||||
end
|
||||
|
||||
|
||||
@@ -23,17 +23,14 @@ class CaptureController < ActionController::Base
|
||||
def rescue_action(e) raise end
|
||||
end
|
||||
|
||||
class CaptureTest < Test::Unit::TestCase
|
||||
def setup
|
||||
@controller = CaptureController.new
|
||||
class CaptureTest < ActionController::TestCase
|
||||
tests CaptureController
|
||||
|
||||
def setup
|
||||
# enable a logger so that (e.g.) the benchmarking stuff runs, so we can get
|
||||
# a more accurate simulation of what happens in "real life".
|
||||
@controller.logger = Logger.new(nil)
|
||||
|
||||
@request = ActionController::TestRequest.new
|
||||
@response = ActionController::TestResponse.new
|
||||
|
||||
@request.host = "www.nextangle.com"
|
||||
end
|
||||
|
||||
|
||||
@@ -50,16 +50,13 @@ class ContentTypeController < ActionController::Base
|
||||
def rescue_action(e) raise end
|
||||
end
|
||||
|
||||
class ContentTypeTest < Test::Unit::TestCase
|
||||
def setup
|
||||
@controller = ContentTypeController.new
|
||||
class ContentTypeTest < ActionController::TestCase
|
||||
tests ContentTypeController
|
||||
|
||||
def setup
|
||||
# enable a logger so that (e.g.) the benchmarking stuff runs, so we can get
|
||||
# a more accurate simulation of what happens in "real life".
|
||||
@controller.logger = Logger.new(nil)
|
||||
|
||||
@request = ActionController::TestRequest.new
|
||||
@response = ActionController::TestResponse.new
|
||||
end
|
||||
|
||||
def test_render_defaults
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
require 'abstract_unit'
|
||||
|
||||
class CookieTest < Test::Unit::TestCase
|
||||
class CookieTest < ActionController::TestCase
|
||||
class TestController < ActionController::Base
|
||||
def authenticate
|
||||
cookies["user_name"] = "david"
|
||||
@@ -41,11 +41,9 @@ class CookieTest < Test::Unit::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
def setup
|
||||
@request = ActionController::TestRequest.new
|
||||
@response = ActionController::TestResponse.new
|
||||
tests TestController
|
||||
|
||||
@controller = TestController.new
|
||||
def setup
|
||||
@request.host = "www.nextangle.com"
|
||||
end
|
||||
|
||||
|
||||
@@ -32,11 +32,6 @@ class DispatcherTest < Test::Unit::TestCase
|
||||
dispatch(false)
|
||||
end
|
||||
|
||||
def test_clears_asset_tag_cache_before_dispatch_if_in_loading_mode
|
||||
ActionView::Helpers::AssetTagHelper::AssetTag::Cache.expects(:clear).once
|
||||
dispatch(false)
|
||||
end
|
||||
|
||||
def test_leaves_dependencies_after_dispatch_if_not_in_loading_mode
|
||||
ActionController::Routing::Routes.expects(:reload).never
|
||||
ActiveSupport::Dependencies.expects(:clear).never
|
||||
@@ -96,9 +91,7 @@ class DispatcherTest < Test::Unit::TestCase
|
||||
|
||||
private
|
||||
def dispatch(cache_classes = true)
|
||||
controller = mock()
|
||||
controller.stubs(:process).returns([200, {}, 'response'])
|
||||
ActionController::Routing::Routes.stubs(:recognize).returns(controller)
|
||||
ActionController::Routing::RouteSet.any_instance.stubs(:call).returns([200, {}, 'response'])
|
||||
Dispatcher.define_dispatcher_callbacks(cache_classes)
|
||||
@dispatcher.call({})
|
||||
end
|
||||
|
||||
@@ -634,9 +634,11 @@ class FilterTest < Test::Unit::TestCase
|
||||
|
||||
private
|
||||
def test_process(controller, action = "show")
|
||||
ActionController::Base.class_eval { include ActionController::ProcessWithTest } unless ActionController::Base < ActionController::ProcessWithTest
|
||||
request = ActionController::TestRequest.new
|
||||
request.action = action
|
||||
controller.process(request, ActionController::TestResponse.new)
|
||||
controller = controller.new if controller.is_a?(Class)
|
||||
controller.process_with_test(request, ActionController::TestResponse.new)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -874,8 +876,10 @@ class YieldingAroundFiltersTest < Test::Unit::TestCase
|
||||
|
||||
protected
|
||||
def test_process(controller, action = "show")
|
||||
ActionController::Base.class_eval { include ActionController::ProcessWithTest } unless ActionController::Base < ActionController::ProcessWithTest
|
||||
request = ActionController::TestRequest.new
|
||||
request.action = action
|
||||
controller.process(request, ActionController::TestResponse.new)
|
||||
controller = controller.new if controller.is_a?(Class)
|
||||
controller.process_with_test(request, ActionController::TestResponse.new)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
require 'abstract_unit'
|
||||
|
||||
class FlashTest < Test::Unit::TestCase
|
||||
class FlashTest < ActionController::TestCase
|
||||
class TestController < ActionController::Base
|
||||
def set_flash
|
||||
flash["that"] = "hello"
|
||||
@@ -73,11 +73,7 @@ class FlashTest < Test::Unit::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
def setup
|
||||
@request = ActionController::TestRequest.new
|
||||
@response = ActionController::TestResponse.new
|
||||
@controller = TestController.new
|
||||
end
|
||||
tests TestController
|
||||
|
||||
def test_flash
|
||||
get :set_flash
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
require 'abstract_unit'
|
||||
|
||||
class HttpDigestAuthenticationTest < Test::Unit::TestCase
|
||||
include ActionController::HttpAuthentication::Digest
|
||||
|
||||
class DummyController
|
||||
attr_accessor :headers, :renders, :request, :response
|
||||
|
||||
def initialize
|
||||
@headers, @renders = {}, []
|
||||
@request = ActionController::TestRequest.new
|
||||
@response = ActionController::TestResponse.new
|
||||
request.session.session_id = "test_session"
|
||||
end
|
||||
|
||||
def render(options)
|
||||
self.renderers << options
|
||||
end
|
||||
end
|
||||
|
||||
def setup
|
||||
@controller = DummyController.new
|
||||
@credentials = {
|
||||
:username => "dhh",
|
||||
:realm => "testrealm@host.com",
|
||||
:nonce => ActionController::HttpAuthentication::Digest.nonce(@controller.request),
|
||||
:qop => "auth",
|
||||
:nc => "00000001",
|
||||
:cnonce => "0a4f113b",
|
||||
:opaque => ActionController::HttpAuthentication::Digest.opaque(@controller.request),
|
||||
:uri => "http://test.host/"
|
||||
}
|
||||
@encoded_credentials = ActionController::HttpAuthentication::Digest.encode_credentials("GET", @credentials, "secret")
|
||||
end
|
||||
|
||||
def test_decode_credentials
|
||||
set_headers
|
||||
assert_equal @credentials, decode_credentials(@controller.request)
|
||||
end
|
||||
|
||||
def test_nonce_format
|
||||
assert_nothing_thrown do
|
||||
validate_nonce(@controller.request, nonce(@controller.request))
|
||||
end
|
||||
end
|
||||
|
||||
def test_authenticate_should_raise_for_nil_password
|
||||
set_headers ActionController::HttpAuthentication::Digest.encode_credentials(:get, @credentials, nil)
|
||||
assert_raise ActionController::HttpAuthentication::Error do
|
||||
authenticate(@controller, @credentials[:realm]) { |user| user == "dhh" && "secret" }
|
||||
end
|
||||
end
|
||||
|
||||
def test_authenticate_should_raise_for_incorrect_password
|
||||
set_headers
|
||||
assert_raise ActionController::HttpAuthentication::Error do
|
||||
authenticate(@controller, @credentials[:realm]) { |user| user == "dhh" && "bad password" }
|
||||
end
|
||||
end
|
||||
|
||||
def test_authenticate_should_not_raise_for_correct_password
|
||||
set_headers
|
||||
assert_nothing_thrown do
|
||||
authenticate(@controller, @credentials[:realm]) { |user| user == "dhh" && "secret" }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def set_headers(value = @encoded_credentials, name = 'HTTP_AUTHORIZATION', method = "GET")
|
||||
@controller.request.env[name] = value
|
||||
@controller.request.env["REQUEST_METHOD"] = method
|
||||
end
|
||||
end
|
||||
@@ -4,11 +4,29 @@ uses_mocha 'integration' do
|
||||
|
||||
class SessionTest < Test::Unit::TestCase
|
||||
StubApp = lambda { |env|
|
||||
[200, {"Content-Type" => "text/html"}, "Hello, World!"]
|
||||
[200, {"Content-Type" => "text/html", "Content-Length" => "13"}, "Hello, World!"]
|
||||
}
|
||||
|
||||
def setup
|
||||
@credentials = {
|
||||
:username => "username",
|
||||
:realm => "MyApp",
|
||||
:nonce => ActionController::HttpAuthentication::Digest.nonce("session_id"),
|
||||
:qop => "auth",
|
||||
:nc => "00000001",
|
||||
:cnonce => "0a4f113b",
|
||||
:opaque => ActionController::HttpAuthentication::Digest.opaque("session_id"),
|
||||
:uri => "/index"
|
||||
}
|
||||
|
||||
@session = ActionController::Integration::Session.new(StubApp)
|
||||
@session.nonce = @credentials[:nonce]
|
||||
@session.opaque = @credentials[:opaque]
|
||||
@session.realm = @credentials[:realm]
|
||||
end
|
||||
|
||||
def encoded_credentials(method)
|
||||
ActionController::HttpAuthentication::Digest.encode_credentials(method, @credentials, "password")
|
||||
end
|
||||
|
||||
def test_https_bang_works_and_sets_truth_by_default
|
||||
@@ -132,6 +150,76 @@ class SessionTest < Test::Unit::TestCase
|
||||
@session.head(path,params,headers)
|
||||
end
|
||||
|
||||
def test_get_with_basic
|
||||
path = "/index"; params = "blah"; headers = {:location => 'blah'}
|
||||
expected_headers = headers.merge(:authorization => "Basic dXNlcm5hbWU6cGFzc3dvcmQ=\n")
|
||||
@session.expects(:process).with(:get,path,params,expected_headers)
|
||||
@session.get_with_basic(path,params,headers,'username','password')
|
||||
end
|
||||
|
||||
def test_post_with_basic
|
||||
path = "/index"; params = "blah"; headers = {:location => 'blah'}
|
||||
expected_headers = headers.merge(:authorization => "Basic dXNlcm5hbWU6cGFzc3dvcmQ=\n")
|
||||
@session.expects(:process).with(:post,path,params,expected_headers)
|
||||
@session.post_with_basic(path,params,headers,'username','password')
|
||||
end
|
||||
|
||||
def test_put_with_basic
|
||||
path = "/index"; params = "blah"; headers = {:location => 'blah'}
|
||||
expected_headers = headers.merge(:authorization => "Basic dXNlcm5hbWU6cGFzc3dvcmQ=\n")
|
||||
@session.expects(:process).with(:put,path,params,expected_headers)
|
||||
@session.put_with_basic(path,params,headers,'username','password')
|
||||
end
|
||||
|
||||
def test_delete_with_basic
|
||||
path = "/index"; params = "blah"; headers = {:location => 'blah'}
|
||||
expected_headers = headers.merge(:authorization => "Basic dXNlcm5hbWU6cGFzc3dvcmQ=\n")
|
||||
@session.expects(:process).with(:delete,path,params,expected_headers)
|
||||
@session.delete_with_basic(path,params,headers,'username','password')
|
||||
end
|
||||
|
||||
def test_head_with_basic
|
||||
path = "/index"; params = "blah"; headers = {:location => 'blah'}
|
||||
expected_headers = headers.merge(:authorization => "Basic dXNlcm5hbWU6cGFzc3dvcmQ=\n")
|
||||
@session.expects(:process).with(:head,path,params,expected_headers)
|
||||
@session.head_with_basic(path,params,headers,'username','password')
|
||||
end
|
||||
|
||||
def test_get_with_digest
|
||||
path = "/index"; params = "blah"; headers = {:location => 'blah'}
|
||||
expected_headers = headers.merge(:authorization => encoded_credentials(:get))
|
||||
@session.expects(:process).with(:get,path,params,expected_headers)
|
||||
@session.get_with_digest(path,params,headers,'username','password')
|
||||
end
|
||||
|
||||
def test_post_with_digest
|
||||
path = "/index"; params = "blah"; headers = {:location => 'blah'}
|
||||
expected_headers = headers.merge(:authorization => encoded_credentials(:post))
|
||||
@session.expects(:process).with(:post,path,params,expected_headers)
|
||||
@session.post_with_digest(path,params,headers,'username','password')
|
||||
end
|
||||
|
||||
def test_put_with_digest
|
||||
path = "/index"; params = "blah"; headers = {:location => 'blah'}
|
||||
expected_headers = headers.merge(:authorization => encoded_credentials(:put))
|
||||
@session.expects(:process).with(:put,path,params,expected_headers)
|
||||
@session.put_with_digest(path,params,headers,'username','password')
|
||||
end
|
||||
|
||||
def test_delete_with_digest
|
||||
path = "/index"; params = "blah"; headers = {:location => 'blah'}
|
||||
expected_headers = headers.merge(:authorization => encoded_credentials(:delete))
|
||||
@session.expects(:process).with(:delete,path,params,expected_headers)
|
||||
@session.delete_with_digest(path,params,headers,'username','password')
|
||||
end
|
||||
|
||||
def test_head_with_digest
|
||||
path = "/index"; params = "blah"; headers = {:location => 'blah'}
|
||||
expected_headers = headers.merge(:authorization => encoded_credentials(:head))
|
||||
@session.expects(:process).with(:head,path,params,expected_headers)
|
||||
@session.head_with_digest(path,params,headers,'username','password')
|
||||
end
|
||||
|
||||
def test_xml_http_request_get
|
||||
path = "/index"; params = "blah"; headers = {:location => 'blah'}
|
||||
headers_after_xhr = headers.merge(
|
||||
@@ -377,9 +465,9 @@ class MetalTest < ActionController::IntegrationTest
|
||||
class Poller
|
||||
def self.call(env)
|
||||
if env["PATH_INFO"] =~ /^\/success/
|
||||
[200, {"Content-Type" => "text/plain"}, "Hello World!"]
|
||||
[200, {"Content-Type" => "text/plain", "Content-Length" => "12"}, "Hello World!"]
|
||||
else
|
||||
[404, {"Content-Type" => "text/plain"}, '']
|
||||
[404, {"Content-Type" => "text/plain", "Content-Length" => "0"}, '']
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,6 +10,10 @@ class UploadTestController < ActionController::Base
|
||||
SessionUploadTest.last_request_type = ActionController::Base.param_parsers[request.content_type]
|
||||
render :text => "got here"
|
||||
end
|
||||
|
||||
def read
|
||||
render :text => "File: #{params[:uploaded_data].read}"
|
||||
end
|
||||
end
|
||||
|
||||
class SessionUploadTest < ActionController::IntegrationTest
|
||||
@@ -19,21 +23,43 @@ class SessionUploadTest < ActionController::IntegrationTest
|
||||
attr_accessor :last_request_type
|
||||
end
|
||||
|
||||
# def setup
|
||||
# @session = ActionController::Integration::Session.new
|
||||
# end
|
||||
def test_post_with_upload
|
||||
uses_mocha "test_post_with_upload" do
|
||||
ActiveSupport::Dependencies.stubs(:load?).returns(false)
|
||||
def test_upload_and_read_file
|
||||
with_test_routing do
|
||||
post '/read', :uploaded_data => fixture_file_upload(FILES_DIR + "/hello.txt", "text/plain")
|
||||
assert_equal "File: Hello", response.body
|
||||
end
|
||||
end
|
||||
|
||||
# The lint wrapper is used in integration tests
|
||||
# instead of a normal StringIO class
|
||||
InputWrapper = Rack::Lint::InputWrapper
|
||||
|
||||
def test_post_with_upload_with_unrewindable_input
|
||||
InputWrapper.any_instance.expects(:rewind).raises(Errno::ESPIPE)
|
||||
|
||||
with_test_routing do
|
||||
post '/read', :uploaded_data => fixture_file_upload(FILES_DIR + "/hello.txt", "text/plain")
|
||||
assert_equal "File: Hello", response.body
|
||||
end
|
||||
end
|
||||
|
||||
def test_post_with_upload_with_params_parsing
|
||||
with_test_routing do
|
||||
params = { :uploaded_data => fixture_file_upload(FILES_DIR + "/mona_lisa.jpg", "image/jpg") }
|
||||
post '/update', params, :location => 'blah'
|
||||
assert_equal(:multipart_form, SessionUploadTest.last_request_type)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def with_test_routing
|
||||
with_routing do |set|
|
||||
set.draw do |map|
|
||||
map.update 'update', :controller => "upload_test", :action => "update", :method => :post
|
||||
map.read 'read', :controller => "upload_test", :action => "read", :method => :post
|
||||
end
|
||||
|
||||
params = { :uploaded_data => fixture_file_upload(FILES_DIR + "/mona_lisa.jpg", "image/jpg") }
|
||||
post '/update', params, :location => 'blah'
|
||||
assert_equal(:multipart_form, SessionUploadTest.last_request_type)
|
||||
yield
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -146,8 +146,7 @@ class LayoutExceptionRaised < ActionController::TestCase
|
||||
def test_exception_raised_when_layout_file_not_found
|
||||
@controller = SetsNonExistentLayoutFile.new
|
||||
get :hello
|
||||
@response.template.class.module_eval { attr_accessor :exception }
|
||||
assert_equal ActionView::MissingTemplate, @response.template.exception.class
|
||||
assert_kind_of ActionView::MissingTemplate, @response.template.instance_eval { @exception }
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
70
actionpack/test/controller/middleware_stack_test.rb
Normal file
70
actionpack/test/controller/middleware_stack_test.rb
Normal file
@@ -0,0 +1,70 @@
|
||||
require 'abstract_unit'
|
||||
|
||||
class MiddlewareStackTest < ActiveSupport::TestCase
|
||||
class FooMiddleware; end
|
||||
class BarMiddleware; end
|
||||
class BazMiddleware; end
|
||||
|
||||
def setup
|
||||
@stack = ActionController::MiddlewareStack.new
|
||||
@stack.use FooMiddleware
|
||||
@stack.use BarMiddleware
|
||||
end
|
||||
|
||||
test "use should push middleware as class onto the stack" do
|
||||
assert_difference "@stack.size" do
|
||||
@stack.use BazMiddleware
|
||||
end
|
||||
assert_equal BazMiddleware, @stack.last.klass
|
||||
end
|
||||
|
||||
test "use should push middleware as a string onto the stack" do
|
||||
assert_difference "@stack.size" do
|
||||
@stack.use "MiddlewareStackTest::BazMiddleware"
|
||||
end
|
||||
assert_equal BazMiddleware, @stack.last.klass
|
||||
end
|
||||
|
||||
test "use should push middleware as a symbol onto the stack" do
|
||||
assert_difference "@stack.size" do
|
||||
@stack.use :"MiddlewareStackTest::BazMiddleware"
|
||||
end
|
||||
assert_equal BazMiddleware, @stack.last.klass
|
||||
end
|
||||
|
||||
test "use should push middleware class with arguments onto the stack" do
|
||||
assert_difference "@stack.size" do
|
||||
@stack.use BazMiddleware, true, :foo => "bar"
|
||||
end
|
||||
assert_equal BazMiddleware, @stack.last.klass
|
||||
assert_equal([true, {:foo => "bar"}], @stack.last.args)
|
||||
end
|
||||
|
||||
test "insert inserts middleware at the integer index" do
|
||||
@stack.insert(1, BazMiddleware)
|
||||
assert_equal BazMiddleware, @stack[1].klass
|
||||
end
|
||||
|
||||
test "insert_after inserts middleware after the integer index" do
|
||||
@stack.insert_after(1, BazMiddleware)
|
||||
assert_equal BazMiddleware, @stack[2].klass
|
||||
end
|
||||
|
||||
test "insert_before inserts middleware before another middleware class" do
|
||||
@stack.insert_before(BarMiddleware, BazMiddleware)
|
||||
assert_equal BazMiddleware, @stack[1].klass
|
||||
end
|
||||
|
||||
test "insert_after inserts middleware after another middleware class" do
|
||||
@stack.insert_after(BarMiddleware, BazMiddleware)
|
||||
assert_equal BazMiddleware, @stack[2].klass
|
||||
end
|
||||
|
||||
test "active returns all only enabled middleware" do
|
||||
assert_no_difference "@stack.active.size" do
|
||||
assert_difference "@stack.size" do
|
||||
@stack.use BazMiddleware, :if => lambda { false }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
120
actionpack/test/controller/query_string_parsing_test.rb
Normal file
120
actionpack/test/controller/query_string_parsing_test.rb
Normal file
@@ -0,0 +1,120 @@
|
||||
require 'abstract_unit'
|
||||
|
||||
class QueryStringParsingTest < ActionController::IntegrationTest
|
||||
class TestController < ActionController::Base
|
||||
class << self
|
||||
attr_accessor :last_query_parameters
|
||||
end
|
||||
|
||||
def parse
|
||||
self.class.last_query_parameters = request.query_parameters
|
||||
head :ok
|
||||
end
|
||||
end
|
||||
|
||||
def teardown
|
||||
TestController.last_query_parameters = nil
|
||||
end
|
||||
|
||||
test "query string" do
|
||||
assert_parses(
|
||||
{"action" => "create_customer", "full_name" => "David Heinemeier Hansson", "customerId" => "1"},
|
||||
"action=create_customer&full_name=David%20Heinemeier%20Hansson&customerId=1"
|
||||
)
|
||||
end
|
||||
|
||||
test "deep query string" do
|
||||
assert_parses(
|
||||
{'x' => {'y' => {'z' => '10'}}},
|
||||
"x[y][z]=10"
|
||||
)
|
||||
end
|
||||
|
||||
test "deep query string with array" do
|
||||
assert_parses({'x' => {'y' => {'z' => ['10']}}}, 'x[y][z][]=10')
|
||||
assert_parses({'x' => {'y' => {'z' => ['10', '5']}}}, 'x[y][z][]=10&x[y][z][]=5')
|
||||
end
|
||||
|
||||
test "deep query string with array of hash" do
|
||||
assert_parses({'x' => {'y' => [{'z' => '10'}]}}, 'x[y][][z]=10')
|
||||
assert_parses({'x' => {'y' => [{'z' => '10', 'w' => '10'}]}}, 'x[y][][z]=10&x[y][][w]=10')
|
||||
assert_parses({'x' => {'y' => [{'z' => '10', 'v' => {'w' => '10'}}]}}, 'x[y][][z]=10&x[y][][v][w]=10')
|
||||
end
|
||||
|
||||
test "deep query string with array of hashes with one pair" do
|
||||
assert_parses({'x' => {'y' => [{'z' => '10'}, {'z' => '20'}]}}, 'x[y][][z]=10&x[y][][z]=20')
|
||||
end
|
||||
|
||||
test "deep query string with array of hashes with multiple pairs" do
|
||||
assert_parses(
|
||||
{'x' => {'y' => [{'z' => '10', 'w' => 'a'}, {'z' => '20', 'w' => 'b'}]}},
|
||||
'x[y][][z]=10&x[y][][w]=a&x[y][][z]=20&x[y][][w]=b'
|
||||
)
|
||||
end
|
||||
|
||||
test "query string with nil" do
|
||||
assert_parses(
|
||||
{ "action" => "create_customer", "full_name" => ''},
|
||||
"action=create_customer&full_name="
|
||||
)
|
||||
end
|
||||
|
||||
test "query string with array" do
|
||||
assert_parses(
|
||||
{ "action" => "create_customer", "selected" => ["1", "2", "3"]},
|
||||
"action=create_customer&selected[]=1&selected[]=2&selected[]=3"
|
||||
)
|
||||
end
|
||||
|
||||
test "query string with amps" do
|
||||
assert_parses(
|
||||
{ "action" => "create_customer", "name" => "Don't & Does"},
|
||||
"action=create_customer&name=Don%27t+%26+Does"
|
||||
)
|
||||
end
|
||||
|
||||
test "query string with many equal" do
|
||||
assert_parses(
|
||||
{ "action" => "create_customer", "full_name" => "abc=def=ghi"},
|
||||
"action=create_customer&full_name=abc=def=ghi"
|
||||
)
|
||||
end
|
||||
|
||||
test "query string without equal" do
|
||||
assert_parses({ "action" => nil }, "action")
|
||||
end
|
||||
|
||||
test "query string with empty key" do
|
||||
assert_parses(
|
||||
{ "action" => "create_customer", "full_name" => "David Heinemeier Hansson" },
|
||||
"action=create_customer&full_name=David%20Heinemeier%20Hansson&=Save"
|
||||
)
|
||||
end
|
||||
|
||||
test "query string with many ampersands" do
|
||||
assert_parses(
|
||||
{ "action" => "create_customer", "full_name" => "David Heinemeier Hansson"},
|
||||
"&action=create_customer&&&full_name=David%20Heinemeier%20Hansson"
|
||||
)
|
||||
end
|
||||
|
||||
test "unbalanced query string with array" do
|
||||
assert_parses(
|
||||
{'location' => ["1", "2"], 'age_group' => ["2"]},
|
||||
"location[]=1&location[]=2&age_group[]=2"
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
def assert_parses(expected, actual)
|
||||
with_routing do |set|
|
||||
set.draw do |map|
|
||||
map.connect ':action', :controller => "query_string_parsing_test/test"
|
||||
end
|
||||
|
||||
get "/parse", actual
|
||||
assert_response :ok
|
||||
assert_equal(expected, TestController.last_query_parameters)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -236,7 +236,12 @@ class RackResponseTest < BaseRackTest
|
||||
|
||||
status, headers, body = @response.to_a
|
||||
assert_equal 200, status
|
||||
assert_equal({"Content-Type" => "text/html; charset=utf-8", "Cache-Control" => "no-cache", "Set-Cookie" => []}, headers)
|
||||
assert_equal({
|
||||
"Content-Type" => "text/html; charset=utf-8",
|
||||
"Content-Length" => "",
|
||||
"Cache-Control" => "no-cache",
|
||||
"Set-Cookie" => []
|
||||
}, headers)
|
||||
|
||||
parts = []
|
||||
body.each { |part| parts << part }
|
||||
|
||||
@@ -1218,6 +1218,11 @@ class RenderTest < ActionController::TestCase
|
||||
assert_equal "404 Not Found", @response.status
|
||||
assert_response :not_found
|
||||
|
||||
get :head_with_symbolic_status, :status => "no_content"
|
||||
assert_equal "204 No Content", @response.status
|
||||
assert !@response.headers.include?('Content-Length')
|
||||
assert_response :no_content
|
||||
|
||||
ActionController::StatusCodes::SYMBOL_TO_STATUS_CODE.each do |status, code|
|
||||
get :head_with_symbolic_status, :status => status.to_s
|
||||
assert_equal code, @response.response_code
|
||||
@@ -1470,7 +1475,7 @@ class EtagRenderTest < ActionController::TestCase
|
||||
def test_render_against_etag_request_should_have_no_content_length_when_match
|
||||
@request.if_none_match = etag_for("hello david")
|
||||
get :render_hello_world_from_variable
|
||||
assert !@response.headers.has_key?("Content-Length")
|
||||
assert !@response.headers.has_key?("Content-Length"), @response.headers['Content-Length']
|
||||
end
|
||||
|
||||
def test_render_against_etag_request_should_200_when_no_match
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
require 'abstract_unit'
|
||||
|
||||
class JsonParamsParsingTest < ActionController::IntegrationTest
|
||||
class TestController < ActionController::Base
|
||||
class << self
|
||||
attr_accessor :last_request_parameters
|
||||
end
|
||||
|
||||
def parse
|
||||
self.class.last_request_parameters = request.request_parameters
|
||||
head :ok
|
||||
end
|
||||
end
|
||||
|
||||
def teardown
|
||||
TestController.last_request_parameters = nil
|
||||
end
|
||||
|
||||
test "parses json params for application json" do
|
||||
assert_parses(
|
||||
{"person" => {"name" => "David"}},
|
||||
"{\"person\": {\"name\": \"David\"}}", { 'CONTENT_TYPE' => 'application/json' }
|
||||
)
|
||||
end
|
||||
|
||||
test "parses json params for application jsonrequest" do
|
||||
assert_parses(
|
||||
{"person" => {"name" => "David"}},
|
||||
"{\"person\": {\"name\": \"David\"}}", { 'CONTENT_TYPE' => 'application/jsonrequest' }
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
def assert_parses(expected, actual, headers = {})
|
||||
with_routing do |set|
|
||||
set.draw do |map|
|
||||
map.connect ':action', :controller => "json_params_parsing_test/test"
|
||||
end
|
||||
|
||||
post "/parse", actual, headers
|
||||
assert_response :ok
|
||||
assert_equal(expected, TestController.last_request_parameters)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,88 @@
|
||||
require 'abstract_unit'
|
||||
|
||||
class XmlParamsParsingTest < ActionController::IntegrationTest
|
||||
class TestController < ActionController::Base
|
||||
class << self
|
||||
attr_accessor :last_request_parameters
|
||||
end
|
||||
|
||||
def parse
|
||||
self.class.last_request_parameters = request.request_parameters
|
||||
head :ok
|
||||
end
|
||||
end
|
||||
|
||||
def teardown
|
||||
TestController.last_request_parameters = nil
|
||||
end
|
||||
|
||||
test "parses hash params" do
|
||||
with_test_routing do
|
||||
xml = "<person><name>David</name></person>"
|
||||
post "/parse", xml, default_headers
|
||||
assert_response :ok
|
||||
assert_equal({"person" => {"name" => "David"}}, TestController.last_request_parameters)
|
||||
end
|
||||
end
|
||||
|
||||
test "parses single file" do
|
||||
with_test_routing do
|
||||
xml = "<person><name>David</name><avatar type='file' name='me.jpg' content_type='image/jpg'>#{ActiveSupport::Base64.encode64('ABC')}</avatar></person>"
|
||||
post "/parse", xml, default_headers
|
||||
assert_response :ok
|
||||
|
||||
person = TestController.last_request_parameters
|
||||
assert_equal "image/jpg", person['person']['avatar'].content_type
|
||||
assert_equal "me.jpg", person['person']['avatar'].original_filename
|
||||
assert_equal "ABC", person['person']['avatar'].read
|
||||
end
|
||||
end
|
||||
|
||||
test "parses multiple files" do
|
||||
xml = <<-end_body
|
||||
<person>
|
||||
<name>David</name>
|
||||
<avatars>
|
||||
<avatar type='file' name='me.jpg' content_type='image/jpg'>#{ActiveSupport::Base64.encode64('ABC')}</avatar>
|
||||
<avatar type='file' name='you.gif' content_type='image/gif'>#{ActiveSupport::Base64.encode64('DEF')}</avatar>
|
||||
</avatars>
|
||||
</person>
|
||||
end_body
|
||||
|
||||
with_test_routing do
|
||||
post "/parse", xml, default_headers
|
||||
assert_response :ok
|
||||
end
|
||||
|
||||
person = TestController.last_request_parameters
|
||||
|
||||
assert_equal "image/jpg", person['person']['avatars']['avatar'].first.content_type
|
||||
assert_equal "me.jpg", person['person']['avatars']['avatar'].first.original_filename
|
||||
assert_equal "ABC", person['person']['avatars']['avatar'].first.read
|
||||
|
||||
assert_equal "image/gif", person['person']['avatars']['avatar'].last.content_type
|
||||
assert_equal "you.gif", person['person']['avatars']['avatar'].last.original_filename
|
||||
assert_equal "DEF", person['person']['avatars']['avatar'].last.read
|
||||
end
|
||||
|
||||
private
|
||||
def with_test_routing
|
||||
with_routing do |set|
|
||||
set.draw do |map|
|
||||
map.connect ':action', :controller => "xml_params_parsing_test/test"
|
||||
end
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
def default_headers
|
||||
{'CONTENT_TYPE' => 'application/xml'}
|
||||
end
|
||||
end
|
||||
|
||||
class LegacyXmlParamsParsingTest < XmlParamsParsingTest
|
||||
private
|
||||
def default_headers
|
||||
{'HTTP_X_POST_DATA_FORMAT' => 'xml'}
|
||||
end
|
||||
end
|
||||
@@ -391,8 +391,8 @@ class RequestTest < ActiveSupport::TestCase
|
||||
end
|
||||
|
||||
def test_parameters
|
||||
@request.instance_eval { @request_parameters = { "foo" => 1 } }
|
||||
@request.instance_eval { @query_parameters = { "bar" => 2 } }
|
||||
@request.stubs(:request_parameters).returns({ "foo" => 1 })
|
||||
@request.stubs(:query_parameters).returns({ "bar" => 2 })
|
||||
|
||||
assert_equal({"foo" => 1, "bar" => 2}, @request.parameters)
|
||||
assert_equal({"foo" => 1}, @request.request_parameters)
|
||||
@@ -407,113 +407,10 @@ class RequestTest < ActiveSupport::TestCase
|
||||
end
|
||||
|
||||
class UrlEncodedRequestParameterParsingTest < ActiveSupport::TestCase
|
||||
def setup
|
||||
@query_string = "action=create_customer&full_name=David%20Heinemeier%20Hansson&customerId=1"
|
||||
@query_string_with_empty = "action=create_customer&full_name="
|
||||
@query_string_with_array = "action=create_customer&selected[]=1&selected[]=2&selected[]=3"
|
||||
@query_string_with_amps = "action=create_customer&name=Don%27t+%26+Does"
|
||||
@query_string_with_multiple_of_same_name =
|
||||
"action=update_order&full_name=Lau%20Taarnskov&products=4&products=2&products=3"
|
||||
@query_string_with_many_equal = "action=create_customer&full_name=abc=def=ghi"
|
||||
@query_string_without_equal = "action"
|
||||
@query_string_with_many_ampersands =
|
||||
"&action=create_customer&&&full_name=David%20Heinemeier%20Hansson"
|
||||
@query_string_with_empty_key = "action=create_customer&full_name=David%20Heinemeier%20Hansson&=Save"
|
||||
end
|
||||
|
||||
def test_query_string
|
||||
assert_equal(
|
||||
{ "action" => "create_customer", "full_name" => "David Heinemeier Hansson", "customerId" => "1"},
|
||||
ActionController::RequestParser.parse_query_parameters(@query_string)
|
||||
)
|
||||
end
|
||||
|
||||
def test_deep_query_string
|
||||
expected = {'x' => {'y' => {'z' => '10'}}}
|
||||
assert_equal(expected, ActionController::RequestParser.parse_query_parameters('x[y][z]=10'))
|
||||
end
|
||||
|
||||
def test_deep_query_string_with_array
|
||||
assert_equal({'x' => {'y' => {'z' => ['10']}}}, ActionController::RequestParser.parse_query_parameters('x[y][z][]=10'))
|
||||
assert_equal({'x' => {'y' => {'z' => ['10', '5']}}}, ActionController::RequestParser.parse_query_parameters('x[y][z][]=10&x[y][z][]=5'))
|
||||
end
|
||||
|
||||
def test_deep_query_string_with_array_of_hash
|
||||
assert_equal({'x' => {'y' => [{'z' => '10'}]}}, ActionController::RequestParser.parse_query_parameters('x[y][][z]=10'))
|
||||
assert_equal({'x' => {'y' => [{'z' => '10', 'w' => '10'}]}}, ActionController::RequestParser.parse_query_parameters('x[y][][z]=10&x[y][][w]=10'))
|
||||
end
|
||||
|
||||
def test_deep_query_string_with_array_of_hashes_with_one_pair
|
||||
assert_equal({'x' => {'y' => [{'z' => '10'}, {'z' => '20'}]}}, ActionController::RequestParser.parse_query_parameters('x[y][][z]=10&x[y][][z]=20'))
|
||||
assert_equal("10", ActionController::RequestParser.parse_query_parameters('x[y][][z]=10&x[y][][z]=20')["x"]["y"].first["z"])
|
||||
assert_equal("10", ActionController::RequestParser.parse_query_parameters('x[y][][z]=10&x[y][][z]=20').with_indifferent_access[:x][:y].first[:z])
|
||||
end
|
||||
|
||||
def test_deep_query_string_with_array_of_hashes_with_multiple_pairs
|
||||
assert_equal(
|
||||
{'x' => {'y' => [{'z' => '10', 'w' => 'a'}, {'z' => '20', 'w' => 'b'}]}},
|
||||
ActionController::RequestParser.parse_query_parameters('x[y][][z]=10&x[y][][w]=a&x[y][][z]=20&x[y][][w]=b')
|
||||
)
|
||||
end
|
||||
|
||||
def test_query_string_with_nil
|
||||
assert_equal(
|
||||
{ "action" => "create_customer", "full_name" => ''},
|
||||
ActionController::RequestParser.parse_query_parameters(@query_string_with_empty)
|
||||
)
|
||||
end
|
||||
|
||||
def test_query_string_with_array
|
||||
assert_equal(
|
||||
{ "action" => "create_customer", "selected" => ["1", "2", "3"]},
|
||||
ActionController::RequestParser.parse_query_parameters(@query_string_with_array)
|
||||
)
|
||||
end
|
||||
|
||||
def test_query_string_with_amps
|
||||
assert_equal(
|
||||
{ "action" => "create_customer", "name" => "Don't & Does"},
|
||||
ActionController::RequestParser.parse_query_parameters(@query_string_with_amps)
|
||||
)
|
||||
end
|
||||
|
||||
def test_query_string_with_many_equal
|
||||
assert_equal(
|
||||
{ "action" => "create_customer", "full_name" => "abc=def=ghi"},
|
||||
ActionController::RequestParser.parse_query_parameters(@query_string_with_many_equal)
|
||||
)
|
||||
end
|
||||
|
||||
def test_query_string_without_equal
|
||||
assert_equal(
|
||||
{ "action" => nil },
|
||||
ActionController::RequestParser.parse_query_parameters(@query_string_without_equal)
|
||||
)
|
||||
end
|
||||
|
||||
def test_query_string_with_empty_key
|
||||
assert_equal(
|
||||
{ "action" => "create_customer", "full_name" => "David Heinemeier Hansson" },
|
||||
ActionController::RequestParser.parse_query_parameters(@query_string_with_empty_key)
|
||||
)
|
||||
end
|
||||
|
||||
def test_query_string_with_many_ampersands
|
||||
assert_equal(
|
||||
{ "action" => "create_customer", "full_name" => "David Heinemeier Hansson"},
|
||||
ActionController::RequestParser.parse_query_parameters(@query_string_with_many_ampersands)
|
||||
)
|
||||
end
|
||||
|
||||
def test_unbalanced_query_string_with_array
|
||||
assert_equal(
|
||||
{'location' => ["1", "2"], 'age_group' => ["2"]},
|
||||
ActionController::RequestParser.parse_query_parameters("location[]=1&location[]=2&age_group[]=2")
|
||||
)
|
||||
assert_equal(
|
||||
{'location' => ["1", "2"], 'age_group' => ["2"]},
|
||||
ActionController::RequestParser.parse_request_parameters({'location[]' => ["1", "2"],
|
||||
'age_group[]' => ["2"]})
|
||||
ActionController::RequestParser.parse_request_parameters({'location[]' => ["1", "2"], 'age_group[]' => ["2"]})
|
||||
)
|
||||
end
|
||||
|
||||
@@ -813,79 +710,3 @@ class MultipartRequestParameterParsingTest < ActiveSupport::TestCase
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class XmlParamsParsingTest < ActiveSupport::TestCase
|
||||
def test_hash_params
|
||||
person = parse_body("<person><name>David</name></person>")[:person]
|
||||
assert_kind_of Hash, person
|
||||
assert_equal 'David', person['name']
|
||||
end
|
||||
|
||||
def test_single_file
|
||||
person = parse_body("<person><name>David</name><avatar type='file' name='me.jpg' content_type='image/jpg'>#{ActiveSupport::Base64.encode64('ABC')}</avatar></person>")
|
||||
|
||||
assert_equal "image/jpg", person['person']['avatar'].content_type
|
||||
assert_equal "me.jpg", person['person']['avatar'].original_filename
|
||||
assert_equal "ABC", person['person']['avatar'].read
|
||||
end
|
||||
|
||||
def test_multiple_files
|
||||
person = parse_body(<<-end_body)
|
||||
<person>
|
||||
<name>David</name>
|
||||
<avatars>
|
||||
<avatar type='file' name='me.jpg' content_type='image/jpg'>#{ActiveSupport::Base64.encode64('ABC')}</avatar>
|
||||
<avatar type='file' name='you.gif' content_type='image/gif'>#{ActiveSupport::Base64.encode64('DEF')}</avatar>
|
||||
</avatars>
|
||||
</person>
|
||||
end_body
|
||||
|
||||
assert_equal "image/jpg", person['person']['avatars']['avatar'].first.content_type
|
||||
assert_equal "me.jpg", person['person']['avatars']['avatar'].first.original_filename
|
||||
assert_equal "ABC", person['person']['avatars']['avatar'].first.read
|
||||
|
||||
assert_equal "image/gif", person['person']['avatars']['avatar'].last.content_type
|
||||
assert_equal "you.gif", person['person']['avatars']['avatar'].last.original_filename
|
||||
assert_equal "DEF", person['person']['avatars']['avatar'].last.read
|
||||
end
|
||||
|
||||
private
|
||||
def parse_body(body)
|
||||
env = { 'rack.input' => StringIO.new(body),
|
||||
'CONTENT_TYPE' => 'application/xml',
|
||||
'CONTENT_LENGTH' => body.size.to_s }
|
||||
ActionController::Request.new(env).request_parameters
|
||||
end
|
||||
end
|
||||
|
||||
class LegacyXmlParamsParsingTest < XmlParamsParsingTest
|
||||
private
|
||||
def parse_body(body)
|
||||
env = { 'rack.input' => StringIO.new(body),
|
||||
'HTTP_X_POST_DATA_FORMAT' => 'xml',
|
||||
'CONTENT_LENGTH' => body.size.to_s }
|
||||
ActionController::Request.new(env).request_parameters
|
||||
end
|
||||
end
|
||||
|
||||
class JsonParamsParsingTest < ActiveSupport::TestCase
|
||||
def test_hash_params_for_application_json
|
||||
person = parse_body({:person => {:name => "David"}}.to_json,'application/json')[:person]
|
||||
assert_kind_of Hash, person
|
||||
assert_equal 'David', person['name']
|
||||
end
|
||||
|
||||
def test_hash_params_for_application_jsonrequest
|
||||
person = parse_body({:person => {:name => "David"}}.to_json,'application/jsonrequest')[:person]
|
||||
assert_kind_of Hash, person
|
||||
assert_equal 'David', person['name']
|
||||
end
|
||||
|
||||
private
|
||||
def parse_body(body,content_type)
|
||||
env = { 'rack.input' => StringIO.new(body),
|
||||
'CONTENT_TYPE' => content_type,
|
||||
'CONTENT_LENGTH' => body.size.to_s }
|
||||
ActionController::Request.new(env).request_parameters
|
||||
end
|
||||
end
|
||||
|
||||
@@ -67,6 +67,11 @@ class RescueController < ActionController::Base
|
||||
render :text => 'no way'
|
||||
end
|
||||
|
||||
before_filter(:only => :before_filter_raises) { raise 'umm nice' }
|
||||
|
||||
def before_filter_raises
|
||||
end
|
||||
|
||||
def raises
|
||||
render :text => 'already rendered'
|
||||
raise "don't panic!"
|
||||
@@ -154,6 +159,16 @@ class RescueControllerTest < ActionController::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
def test_rescue_exceptions_raised_by_filters
|
||||
with_rails_root FIXTURE_PUBLIC do
|
||||
with_all_requests_local false do
|
||||
get :before_filter_raises
|
||||
end
|
||||
end
|
||||
|
||||
assert_response :internal_server_error
|
||||
end
|
||||
|
||||
def test_rescue_action_locally_if_all_requests_local
|
||||
@controller.expects(:local_request?).never
|
||||
@controller.expects(:rescue_action_locally).with(@exception)
|
||||
@@ -367,10 +382,21 @@ class RescueControllerTest < ActionController::TestCase
|
||||
end
|
||||
|
||||
def test_rescue_dispatcher_exceptions
|
||||
RescueController.process_with_exception(@request, @response, ActionController::RoutingError.new("Route not found"))
|
||||
env = @request.env
|
||||
env["action_controller.rescue.request"] = @request
|
||||
env["action_controller.rescue.response"] = @response
|
||||
|
||||
RescueController.call_with_exception(env, ActionController::RoutingError.new("Route not found"))
|
||||
assert_equal "no way", @response.body
|
||||
end
|
||||
|
||||
def test_rescue_dispatcher_exceptions_without_request_set
|
||||
@request.env['REQUEST_URI'] = '/no_way'
|
||||
response = RescueController.call_with_exception(@request.env, ActionController::RoutingError.new("Route not found"))
|
||||
assert_kind_of ActionController::Response, response
|
||||
assert_equal "no way", response.body
|
||||
end
|
||||
|
||||
protected
|
||||
def with_all_requests_local(local = true)
|
||||
old_local, ActionController::Base.consider_all_requests_local =
|
||||
|
||||
@@ -706,7 +706,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
|
||||
port_string = port == 80 ? '' : ":#{port}"
|
||||
|
||||
protocol = options.delete(:protocol) || "http"
|
||||
host = options.delete(:host) || "named.route.test"
|
||||
host = options.delete(:host) || "test.host"
|
||||
anchor = "##{options.delete(:anchor)}" if options.key?(:anchor)
|
||||
|
||||
path = routes.generate(options)
|
||||
@@ -715,27 +715,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
|
||||
end
|
||||
|
||||
def request
|
||||
@request ||= MockRequest.new(:host => "named.route.test", :method => :get)
|
||||
end
|
||||
end
|
||||
|
||||
class MockRequest
|
||||
attr_accessor :path, :path_parameters, :host, :subdomains, :domain, :method
|
||||
|
||||
def initialize(values={})
|
||||
values.each { |key, value| send("#{key}=", value) }
|
||||
if values[:host]
|
||||
subdomain, self.domain = values[:host].split(/\./, 2)
|
||||
self.subdomains = [subdomain]
|
||||
end
|
||||
end
|
||||
|
||||
def protocol
|
||||
"http://"
|
||||
end
|
||||
|
||||
def host_with_port
|
||||
(subdomains * '.') + '.' + domain
|
||||
@request ||= ActionController::TestRequest.new
|
||||
end
|
||||
end
|
||||
|
||||
@@ -900,7 +880,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
|
||||
def test_basic_named_route
|
||||
rs.add_named_route :home, '', :controller => 'content', :action => 'list'
|
||||
x = setup_for_named_route
|
||||
assert_equal("http://named.route.test/",
|
||||
assert_equal("http://test.host/",
|
||||
x.send(:home_url))
|
||||
end
|
||||
|
||||
@@ -908,7 +888,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
|
||||
rs.add_named_route :home, '', :controller => 'content', :action => 'list'
|
||||
x = setup_for_named_route
|
||||
ActionController::Base.relative_url_root = "/foo"
|
||||
assert_equal("http://named.route.test/foo/",
|
||||
assert_equal("http://test.host/foo/",
|
||||
x.send(:home_url))
|
||||
assert_equal "/foo/", x.send(:home_path)
|
||||
ActionController::Base.relative_url_root = nil
|
||||
@@ -917,14 +897,14 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
|
||||
def test_named_route_with_option
|
||||
rs.add_named_route :page, 'page/:title', :controller => 'content', :action => 'show_page'
|
||||
x = setup_for_named_route
|
||||
assert_equal("http://named.route.test/page/new%20stuff",
|
||||
assert_equal("http://test.host/page/new%20stuff",
|
||||
x.send(:page_url, :title => 'new stuff'))
|
||||
end
|
||||
|
||||
def test_named_route_with_default
|
||||
rs.add_named_route :page, 'page/:title', :controller => 'content', :action => 'show_page', :title => 'AboutPage'
|
||||
x = setup_for_named_route
|
||||
assert_equal("http://named.route.test/page/AboutRails",
|
||||
assert_equal("http://test.host/page/AboutRails",
|
||||
x.send(:page_url, :title => "AboutRails"))
|
||||
|
||||
end
|
||||
@@ -932,21 +912,21 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
|
||||
def test_named_route_with_name_prefix
|
||||
rs.add_named_route :page, 'page', :controller => 'content', :action => 'show_page', :name_prefix => 'my_'
|
||||
x = setup_for_named_route
|
||||
assert_equal("http://named.route.test/page",
|
||||
assert_equal("http://test.host/page",
|
||||
x.send(:my_page_url))
|
||||
end
|
||||
|
||||
def test_named_route_with_path_prefix
|
||||
rs.add_named_route :page, 'page', :controller => 'content', :action => 'show_page', :path_prefix => 'my'
|
||||
x = setup_for_named_route
|
||||
assert_equal("http://named.route.test/my/page",
|
||||
assert_equal("http://test.host/my/page",
|
||||
x.send(:page_url))
|
||||
end
|
||||
|
||||
def test_named_route_with_nested_controller
|
||||
rs.add_named_route :users, 'admin/user', :controller => 'admin/user', :action => 'index'
|
||||
x = setup_for_named_route
|
||||
assert_equal("http://named.route.test/admin/user",
|
||||
assert_equal("http://test.host/admin/user",
|
||||
x.send(:users_url))
|
||||
end
|
||||
|
||||
@@ -985,7 +965,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
|
||||
map.root :controller => "hello"
|
||||
end
|
||||
x = setup_for_named_route
|
||||
assert_equal("http://named.route.test/", x.send(:root_url))
|
||||
assert_equal("http://test.host/", x.send(:root_url))
|
||||
assert_equal("/", x.send(:root_path))
|
||||
end
|
||||
|
||||
@@ -1001,7 +981,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
|
||||
# x.send(:article_url, :title => 'hi')
|
||||
# )
|
||||
assert_equal(
|
||||
"http://named.route.test/page/2005/6/10/hi",
|
||||
"http://test.host/page/2005/6/10/hi",
|
||||
x.send(:article_url, :title => 'hi', :day => 10, :year => 2005, :month => 6)
|
||||
)
|
||||
end
|
||||
@@ -1202,7 +1182,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
|
||||
assert_equal '/test', rs.generate(:controller => 'post', :action => 'show', :year => nil)
|
||||
|
||||
x = setup_for_named_route
|
||||
assert_equal("http://named.route.test/test",
|
||||
assert_equal("http://test.host/test",
|
||||
x.send(:blog_url))
|
||||
end
|
||||
|
||||
@@ -1249,7 +1229,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
|
||||
assert_equal '/', rs.generate(:controller => 'content')
|
||||
|
||||
x = setup_for_named_route
|
||||
assert_equal("http://named.route.test/",
|
||||
assert_equal("http://test.host/",
|
||||
x.send(:home_url))
|
||||
end
|
||||
|
||||
@@ -1591,7 +1571,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
|
||||
end
|
||||
|
||||
def request
|
||||
@request ||= MockRequest.new(:host => "named.routes.test", :method => :get)
|
||||
@request ||= ActionController::TestRequest.new
|
||||
end
|
||||
|
||||
def test_generate_extras
|
||||
@@ -1692,13 +1672,13 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
|
||||
def test_named_route_url_method
|
||||
controller = setup_named_route_test
|
||||
|
||||
assert_equal "http://named.route.test/people/5", controller.send(:show_url, :id => 5)
|
||||
assert_equal "http://test.host/people/5", controller.send(:show_url, :id => 5)
|
||||
assert_equal "/people/5", controller.send(:show_path, :id => 5)
|
||||
|
||||
assert_equal "http://named.route.test/people", controller.send(:index_url)
|
||||
assert_equal "http://test.host/people", controller.send(:index_url)
|
||||
assert_equal "/people", controller.send(:index_path)
|
||||
|
||||
assert_equal "http://named.route.test/admin/users", controller.send(:users_url)
|
||||
assert_equal "http://test.host/admin/users", controller.send(:users_url)
|
||||
assert_equal '/admin/users', controller.send(:users_path)
|
||||
assert_equal '/admin/users', set.generate(controller.send(:hash_for_users_url), {:controller => 'users', :action => 'index'})
|
||||
end
|
||||
@@ -1706,28 +1686,28 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
|
||||
def test_named_route_url_method_with_anchor
|
||||
controller = setup_named_route_test
|
||||
|
||||
assert_equal "http://named.route.test/people/5#location", controller.send(:show_url, :id => 5, :anchor => 'location')
|
||||
assert_equal "http://test.host/people/5#location", controller.send(:show_url, :id => 5, :anchor => 'location')
|
||||
assert_equal "/people/5#location", controller.send(:show_path, :id => 5, :anchor => 'location')
|
||||
|
||||
assert_equal "http://named.route.test/people#location", controller.send(:index_url, :anchor => 'location')
|
||||
assert_equal "http://test.host/people#location", controller.send(:index_url, :anchor => 'location')
|
||||
assert_equal "/people#location", controller.send(:index_path, :anchor => 'location')
|
||||
|
||||
assert_equal "http://named.route.test/admin/users#location", controller.send(:users_url, :anchor => 'location')
|
||||
assert_equal "http://test.host/admin/users#location", controller.send(:users_url, :anchor => 'location')
|
||||
assert_equal '/admin/users#location', controller.send(:users_path, :anchor => 'location')
|
||||
|
||||
assert_equal "http://named.route.test/people/go/7/hello/joe/5#location",
|
||||
assert_equal "http://test.host/people/go/7/hello/joe/5#location",
|
||||
controller.send(:multi_url, 7, "hello", 5, :anchor => 'location')
|
||||
|
||||
assert_equal "http://named.route.test/people/go/7/hello/joe/5?baz=bar#location",
|
||||
assert_equal "http://test.host/people/go/7/hello/joe/5?baz=bar#location",
|
||||
controller.send(:multi_url, 7, "hello", 5, :baz => "bar", :anchor => 'location')
|
||||
|
||||
assert_equal "http://named.route.test/people?baz=bar#location",
|
||||
assert_equal "http://test.host/people?baz=bar#location",
|
||||
controller.send(:index_url, :baz => "bar", :anchor => 'location')
|
||||
end
|
||||
|
||||
def test_named_route_url_method_with_port
|
||||
controller = setup_named_route_test
|
||||
assert_equal "http://named.route.test:8080/people/5", controller.send(:show_url, 5, :port=>8080)
|
||||
assert_equal "http://test.host:8080/people/5", controller.send(:show_url, 5, :port=>8080)
|
||||
end
|
||||
|
||||
def test_named_route_url_method_with_host
|
||||
@@ -1737,30 +1717,30 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
|
||||
|
||||
def test_named_route_url_method_with_protocol
|
||||
controller = setup_named_route_test
|
||||
assert_equal "https://named.route.test/people/5", controller.send(:show_url, 5, :protocol => "https")
|
||||
assert_equal "https://test.host/people/5", controller.send(:show_url, 5, :protocol => "https")
|
||||
end
|
||||
|
||||
def test_named_route_url_method_with_ordered_parameters
|
||||
controller = setup_named_route_test
|
||||
assert_equal "http://named.route.test/people/go/7/hello/joe/5",
|
||||
assert_equal "http://test.host/people/go/7/hello/joe/5",
|
||||
controller.send(:multi_url, 7, "hello", 5)
|
||||
end
|
||||
|
||||
def test_named_route_url_method_with_ordered_parameters_and_hash
|
||||
controller = setup_named_route_test
|
||||
assert_equal "http://named.route.test/people/go/7/hello/joe/5?baz=bar",
|
||||
assert_equal "http://test.host/people/go/7/hello/joe/5?baz=bar",
|
||||
controller.send(:multi_url, 7, "hello", 5, :baz => "bar")
|
||||
end
|
||||
|
||||
def test_named_route_url_method_with_ordered_parameters_and_empty_hash
|
||||
controller = setup_named_route_test
|
||||
assert_equal "http://named.route.test/people/go/7/hello/joe/5",
|
||||
assert_equal "http://test.host/people/go/7/hello/joe/5",
|
||||
controller.send(:multi_url, 7, "hello", 5, {})
|
||||
end
|
||||
|
||||
def test_named_route_url_method_with_no_positional_arguments
|
||||
controller = setup_named_route_test
|
||||
assert_equal "http://named.route.test/people?baz=bar",
|
||||
assert_equal "http://test.host/people?baz=bar",
|
||||
controller.send(:index_url, :baz => "bar")
|
||||
end
|
||||
|
||||
@@ -1896,49 +1876,54 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
|
||||
end
|
||||
|
||||
request.path = "/people"
|
||||
request.method = :get
|
||||
request.env["REQUEST_METHOD"] = "GET"
|
||||
assert_nothing_raised { set.recognize(request) }
|
||||
assert_equal("index", request.path_parameters[:action])
|
||||
request.recycle!
|
||||
|
||||
request.method = :post
|
||||
request.env["REQUEST_METHOD"] = "POST"
|
||||
assert_nothing_raised { set.recognize(request) }
|
||||
assert_equal("create", request.path_parameters[:action])
|
||||
request.recycle!
|
||||
|
||||
request.method = :put
|
||||
request.env["REQUEST_METHOD"] = "PUT"
|
||||
assert_nothing_raised { set.recognize(request) }
|
||||
assert_equal("update", request.path_parameters[:action])
|
||||
request.recycle!
|
||||
|
||||
begin
|
||||
request.method = :bacon
|
||||
assert_raises(ActionController::UnknownHttpMethod) {
|
||||
request.env["REQUEST_METHOD"] = "BACON"
|
||||
set.recognize(request)
|
||||
flunk 'Should have raised NotImplemented'
|
||||
rescue ActionController::NotImplemented => e
|
||||
assert_equal [:get, :post, :put, :delete], e.allowed_methods
|
||||
end
|
||||
}
|
||||
request.recycle!
|
||||
|
||||
request.path = "/people/5"
|
||||
request.method = :get
|
||||
request.env["REQUEST_METHOD"] = "GET"
|
||||
assert_nothing_raised { set.recognize(request) }
|
||||
assert_equal("show", request.path_parameters[:action])
|
||||
assert_equal("5", request.path_parameters[:id])
|
||||
request.recycle!
|
||||
|
||||
request.method = :put
|
||||
request.env["REQUEST_METHOD"] = "PUT"
|
||||
assert_nothing_raised { set.recognize(request) }
|
||||
assert_equal("update", request.path_parameters[:action])
|
||||
assert_equal("5", request.path_parameters[:id])
|
||||
request.recycle!
|
||||
|
||||
request.method = :delete
|
||||
request.env["REQUEST_METHOD"] = "DELETE"
|
||||
assert_nothing_raised { set.recognize(request) }
|
||||
assert_equal("destroy", request.path_parameters[:action])
|
||||
assert_equal("5", request.path_parameters[:id])
|
||||
request.recycle!
|
||||
|
||||
begin
|
||||
request.method = :post
|
||||
request.env["REQUEST_METHOD"] = "POST"
|
||||
set.recognize(request)
|
||||
flunk 'Should have raised MethodNotAllowed'
|
||||
rescue ActionController::MethodNotAllowed => e
|
||||
assert_equal [:get, :put, :delete], e.allowed_methods
|
||||
end
|
||||
request.recycle!
|
||||
|
||||
ensure
|
||||
Object.send(:remove_const, :PeopleController)
|
||||
@@ -1954,13 +1939,13 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
|
||||
end
|
||||
|
||||
request.path = "/people"
|
||||
request.method = :get
|
||||
request.env["REQUEST_METHOD"] = "GET"
|
||||
assert_nothing_raised { set.recognize(request) }
|
||||
assert_equal("people", request.path_parameters[:controller])
|
||||
assert_equal("index", request.path_parameters[:action])
|
||||
|
||||
request.path = "/"
|
||||
request.method = :get
|
||||
request.env["REQUEST_METHOD"] = "GET"
|
||||
assert_nothing_raised { set.recognize(request) }
|
||||
assert_equal("people", request.path_parameters[:controller])
|
||||
assert_equal("index", request.path_parameters[:action])
|
||||
@@ -1978,7 +1963,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
|
||||
end
|
||||
|
||||
request.path = "/articles/2005/11/05/a-very-interesting-article"
|
||||
request.method = :get
|
||||
request.env["REQUEST_METHOD"] = "GET"
|
||||
assert_nothing_raised { set.recognize(request) }
|
||||
assert_equal("permalink", request.path_parameters[:action])
|
||||
assert_equal("2005", request.path_parameters[:year])
|
||||
@@ -2015,17 +2000,19 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
|
||||
end
|
||||
|
||||
request.path = "/people/5"
|
||||
request.method = :get
|
||||
request.env["REQUEST_METHOD"] = "GET"
|
||||
assert_nothing_raised { set.recognize(request) }
|
||||
assert_equal("show", request.path_parameters[:action])
|
||||
assert_equal("5", request.path_parameters[:id])
|
||||
request.recycle!
|
||||
|
||||
request.method = :put
|
||||
request.env["REQUEST_METHOD"] = "PUT"
|
||||
assert_nothing_raised { set.recognize(request) }
|
||||
assert_equal("update", request.path_parameters[:action])
|
||||
request.recycle!
|
||||
|
||||
request.path = "/people/5.png"
|
||||
request.method = :get
|
||||
request.env["REQUEST_METHOD"] = "GET"
|
||||
assert_nothing_raised { set.recognize(request) }
|
||||
assert_equal("show", request.path_parameters[:action])
|
||||
assert_equal("5", request.path_parameters[:id])
|
||||
@@ -2050,7 +2037,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
|
||||
set.draw { |map| map.root :controller => "people" }
|
||||
|
||||
request.path = ""
|
||||
request.method = :get
|
||||
request.env["REQUEST_METHOD"] = "GET"
|
||||
assert_nothing_raised { set.recognize(request) }
|
||||
assert_equal("people", request.path_parameters[:controller])
|
||||
assert_equal("index", request.path_parameters[:action])
|
||||
@@ -2070,7 +2057,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
|
||||
end
|
||||
|
||||
request.path = "/api/inventory"
|
||||
request.method = :get
|
||||
request.env["REQUEST_METHOD"] = "GET"
|
||||
assert_nothing_raised { set.recognize(request) }
|
||||
assert_equal("api/products", request.path_parameters[:controller])
|
||||
assert_equal("inventory", request.path_parameters[:action])
|
||||
@@ -2090,7 +2077,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
|
||||
end
|
||||
|
||||
request.path = "/api"
|
||||
request.method = :get
|
||||
request.env["REQUEST_METHOD"] = "GET"
|
||||
assert_nothing_raised { set.recognize(request) }
|
||||
assert_equal("api/products", request.path_parameters[:controller])
|
||||
assert_equal("index", request.path_parameters[:action])
|
||||
@@ -2110,7 +2097,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
|
||||
end
|
||||
|
||||
request.path = "/prefix/inventory"
|
||||
request.method = :get
|
||||
request.env["REQUEST_METHOD"] = "GET"
|
||||
assert_nothing_raised { set.recognize(request) }
|
||||
assert_equal("api/products", request.path_parameters[:controller])
|
||||
assert_equal("inventory", request.path_parameters[:action])
|
||||
@@ -2246,7 +2233,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
|
||||
end
|
||||
|
||||
request.path = "/projects/1/milestones"
|
||||
request.method = :get
|
||||
request.env["REQUEST_METHOD"] = "GET"
|
||||
assert_nothing_raised { set.recognize(request) }
|
||||
assert_equal("milestones", request.path_parameters[:controller])
|
||||
assert_equal("index", request.path_parameters[:action])
|
||||
|
||||
@@ -19,7 +19,8 @@ class SendFileController < ActionController::Base
|
||||
def rescue_action(e) raise end
|
||||
end
|
||||
|
||||
class SendFileTest < Test::Unit::TestCase
|
||||
class SendFileTest < ActionController::TestCase
|
||||
tests SendFileController
|
||||
include TestFileUtils
|
||||
|
||||
Mime::Type.register "image/png", :png unless defined? Mime::PNG
|
||||
|
||||
1
actionpack/test/fixtures/multipart/hello.txt
vendored
Normal file
1
actionpack/test/fixtures/multipart/hello.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
Hello
|
||||
@@ -38,8 +38,6 @@ class AssetTagHelperTest < ActionView::TestCase
|
||||
@controller.request = @request
|
||||
|
||||
ActionView::Helpers::AssetTagHelper::reset_javascript_include_default
|
||||
AssetTag::Cache.clear
|
||||
AssetCollection::Cache.clear
|
||||
end
|
||||
|
||||
def teardown
|
||||
@@ -281,6 +279,26 @@ class AssetTagHelperTest < ActionView::TestCase
|
||||
assert_equal copy, source
|
||||
end
|
||||
|
||||
def test_caching_image_path_with_caching_and_proc_asset_host_using_request
|
||||
ENV['RAILS_ASSET_ID'] = ''
|
||||
ActionController::Base.asset_host = Proc.new do |source, request|
|
||||
if request.ssl?
|
||||
"#{request.protocol}#{request.host_with_port}"
|
||||
else
|
||||
"#{request.protocol}assets#{source.length}.example.com"
|
||||
end
|
||||
end
|
||||
|
||||
ActionController::Base.perform_caching = true
|
||||
|
||||
|
||||
@controller.request.stubs(:ssl?).returns(false)
|
||||
assert_equal "http://assets15.example.com/images/xml.png", image_path("xml.png")
|
||||
|
||||
@controller.request.stubs(:ssl?).returns(true)
|
||||
assert_equal "http://localhost/images/xml.png", image_path("xml.png")
|
||||
end
|
||||
|
||||
def test_caching_javascript_include_tag_when_caching_on
|
||||
ENV["RAILS_ASSET_ID"] = ""
|
||||
ActionController::Base.asset_host = 'http://a0.example.com'
|
||||
|
||||
@@ -4,32 +4,25 @@ require 'action_view/helpers/benchmark_helper'
|
||||
class BenchmarkHelperTest < ActionView::TestCase
|
||||
tests ActionView::Helpers::BenchmarkHelper
|
||||
|
||||
class MockLogger
|
||||
attr_reader :logged
|
||||
|
||||
def initialize
|
||||
@logged = []
|
||||
end
|
||||
|
||||
def method_missing(method, *args)
|
||||
@logged << [method, args]
|
||||
end
|
||||
def teardown
|
||||
controller.logger.send(:clear_buffer)
|
||||
end
|
||||
|
||||
def controller
|
||||
@controller ||= Struct.new(:logger).new(MockLogger.new)
|
||||
logger = ActiveSupport::BufferedLogger.new(StringIO.new)
|
||||
logger.auto_flushing = false
|
||||
@controller ||= Struct.new(:logger).new(logger)
|
||||
end
|
||||
|
||||
def test_without_block
|
||||
assert_raise(LocalJumpError) { benchmark }
|
||||
assert controller.logger.logged.empty?
|
||||
assert buffer.empty?
|
||||
end
|
||||
|
||||
def test_defaults
|
||||
i_was_run = false
|
||||
benchmark { i_was_run = true }
|
||||
assert i_was_run
|
||||
assert 1, controller.logger.logged.size
|
||||
assert_last_logged
|
||||
end
|
||||
|
||||
@@ -37,24 +30,57 @@ class BenchmarkHelperTest < ActionView::TestCase
|
||||
i_was_run = false
|
||||
benchmark('test_run') { i_was_run = true }
|
||||
assert i_was_run
|
||||
assert 1, controller.logger.logged.size
|
||||
assert_last_logged 'test_run'
|
||||
end
|
||||
|
||||
def test_with_message_and_level
|
||||
def test_with_message_and_deprecated_level
|
||||
i_was_run = false
|
||||
benchmark('debug_run', :debug) { i_was_run = true }
|
||||
|
||||
assert_deprecated do
|
||||
benchmark('debug_run', :debug) { i_was_run = true }
|
||||
end
|
||||
|
||||
assert i_was_run
|
||||
assert 1, controller.logger.logged.size
|
||||
assert_last_logged 'debug_run', :debug
|
||||
assert_last_logged 'debug_run'
|
||||
end
|
||||
|
||||
def test_within_level
|
||||
controller.logger.level = ActiveSupport::BufferedLogger::DEBUG
|
||||
benchmark('included_debug_run', :level => :debug) { }
|
||||
assert_last_logged 'included_debug_run'
|
||||
end
|
||||
|
||||
def test_outside_level
|
||||
controller.logger.level = ActiveSupport::BufferedLogger::ERROR
|
||||
benchmark('skipped_debug_run', :level => :debug) { }
|
||||
assert_no_match(/skipped_debug_run/, buffer.last)
|
||||
ensure
|
||||
controller.logger.level = ActiveSupport::BufferedLogger::DEBUG
|
||||
end
|
||||
|
||||
def test_without_silencing
|
||||
benchmark('debug_run', :silence => false) do
|
||||
controller.logger.info "not silenced!"
|
||||
end
|
||||
|
||||
assert_equal 2, buffer.size
|
||||
end
|
||||
|
||||
def test_with_silencing
|
||||
benchmark('debug_run', :silence => true) do
|
||||
controller.logger.info "silenced!"
|
||||
end
|
||||
|
||||
assert_equal 1, buffer.size
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
def assert_last_logged(message = 'Benchmarking', level = :info)
|
||||
last = controller.logger.logged.last
|
||||
assert 2, last.size
|
||||
assert_equal level, last.first
|
||||
assert 1, last[1].size
|
||||
assert last[1][0] =~ /^#{message} \(.*\)$/
|
||||
def buffer
|
||||
controller.logger.send(:buffer)
|
||||
end
|
||||
|
||||
def assert_last_logged(message = 'Benchmarking')
|
||||
assert_match(/^#{message} \(.*\)$/, buffer.last)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -31,7 +31,7 @@ uses_mocha 'TestTemplateRecompilation' do
|
||||
end
|
||||
|
||||
def test_compiled_template_will_always_be_recompiled_when_template_is_not_cached
|
||||
ActionView::Template.any_instance.expects(:loaded?).times(3).returns(false)
|
||||
ActionView::Template.any_instance.expects(:recompile?).times(3).returns(true)
|
||||
assert_equal 0, @compiled_templates.instance_methods.size
|
||||
assert_equal "Hello world!", render(:file => "#{FIXTURE_LOAD_PATH}/test/hello_world.erb")
|
||||
ActionView::Template.any_instance.expects(:compile!).times(3)
|
||||
@@ -62,13 +62,14 @@ uses_mocha 'TestTemplateRecompilation' do
|
||||
|
||||
def render_with_cache(*args)
|
||||
view_paths = ActionController::Base.view_paths
|
||||
assert view_paths.first.loaded?
|
||||
assert_equal ActionView::Template::EagerPath, view_paths.first.class
|
||||
ActionView::Base.new(view_paths, {}).render(*args)
|
||||
end
|
||||
|
||||
def render_without_cache(*args)
|
||||
view_paths = ActionView::Base.process_view_paths(FIXTURE_LOAD_PATH)
|
||||
assert !view_paths.first.loaded?
|
||||
path = ActionView::Template::Path.new(FIXTURE_LOAD_PATH)
|
||||
view_paths = ActionView::Base.process_view_paths(path)
|
||||
assert_equal ActionView::Template::Path, view_paths.first.class
|
||||
ActionView::Base.new(view_paths, {}).render(*args)
|
||||
end
|
||||
|
||||
|
||||
@@ -197,7 +197,7 @@ class CachedViewRenderTest < Test::Unit::TestCase
|
||||
# Ensure view path cache is primed
|
||||
def setup
|
||||
view_paths = ActionController::Base.view_paths
|
||||
assert view_paths.first.loaded?
|
||||
assert_equal ActionView::Template::EagerPath, view_paths.first.class
|
||||
setup_view(view_paths)
|
||||
end
|
||||
end
|
||||
@@ -208,8 +208,9 @@ class LazyViewRenderTest < Test::Unit::TestCase
|
||||
# Test the same thing as above, but make sure the view path
|
||||
# is not eager loaded
|
||||
def setup
|
||||
view_paths = ActionView::Base.process_view_paths(FIXTURE_LOAD_PATH)
|
||||
assert !view_paths.first.loaded?
|
||||
path = ActionView::Template::Path.new(FIXTURE_LOAD_PATH)
|
||||
view_paths = ActionView::Base.process_view_paths(path)
|
||||
assert_equal ActionView::Template::Path, view_paths.first.class
|
||||
setup_view(view_paths)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
*2.3.0/3.0*
|
||||
|
||||
* Added dynamic scopes ala dynamic finders #1648 [Yaroslav Markin]
|
||||
|
||||
* Fixed that ActiveRecord::Base#new_record? should return false (not nil) for existing records #1219 [Yaroslav Markin]
|
||||
|
||||
* I18n the word separator for error messages. Introduces the activerecord.errors.format.separator translation key. #1294 [Akira Matsuda]
|
||||
|
||||
@@ -51,6 +51,7 @@ module ActiveRecord
|
||||
autoload :Callbacks, 'active_record/callbacks'
|
||||
autoload :Dirty, 'active_record/dirty'
|
||||
autoload :DynamicFinderMatch, 'active_record/dynamic_finder_match'
|
||||
autoload :DynamicScopeMatch, 'active_record/dynamic_scope_match'
|
||||
autoload :Migration, 'active_record/migration'
|
||||
autoload :Migrator, 'active_record/migration'
|
||||
autoload :NamedScope, 'active_record/named_scope'
|
||||
|
||||
@@ -195,7 +195,7 @@ module ActiveRecord
|
||||
|
||||
def preload_has_one_association(records, reflection, preload_options={})
|
||||
return if records.first.send("loaded_#{reflection.name}?")
|
||||
id_to_record_map, ids = construct_id_map(records)
|
||||
id_to_record_map, ids = construct_id_map(records, reflection.options[:primary_key])
|
||||
options = reflection.options
|
||||
records.each {|record| record.send("set_#{reflection.name}_target", nil)}
|
||||
if options[:through]
|
||||
@@ -229,7 +229,7 @@ module ActiveRecord
|
||||
options = reflection.options
|
||||
|
||||
primary_key_name = reflection.through_reflection_primary_key_name
|
||||
id_to_record_map, ids = construct_id_map(records, primary_key_name)
|
||||
id_to_record_map, ids = construct_id_map(records, primary_key_name || reflection.options[:primary_key])
|
||||
records.each {|record| record.send(reflection.name).loaded}
|
||||
|
||||
if options[:through]
|
||||
|
||||
@@ -22,7 +22,7 @@ module ActiveRecord
|
||||
through_reflection = reflection.through_reflection
|
||||
source_reflection_names = reflection.source_reflection_names
|
||||
source_associations = reflection.through_reflection.klass.reflect_on_all_associations.collect { |a| a.name.inspect }
|
||||
super("Could not find the source association(s) #{source_reflection_names.collect(&:inspect).to_sentence :connector => 'or'} in model #{through_reflection.klass}. Try 'has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}, :source => <name>'. Is it one of #{source_associations.to_sentence :connector => 'or'}?")
|
||||
super("Could not find the source association(s) #{source_reflection_names.collect(&:inspect).to_sentence :two_words_connector => ' or ', :last_word_connector => ', or '} in model #{through_reflection.klass}. Try 'has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}, :source => <name>'. Is it one of #{source_associations.to_sentence :two_words_connector => ' or ', :last_word_connector => ', or '}?")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2171,7 +2171,7 @@ module ActiveRecord
|
||||
aliased_table_name,
|
||||
foreign_key,
|
||||
parent.aliased_table_name,
|
||||
parent.primary_key
|
||||
reflection.options[:primary_key] || parent.primary_key
|
||||
]
|
||||
end
|
||||
when :belongs_to
|
||||
|
||||
@@ -180,7 +180,10 @@ module ActiveRecord
|
||||
record["#{@reflection.options[:as]}_id"] = @owner.id unless @owner.new_record?
|
||||
record["#{@reflection.options[:as]}_type"] = @owner.class.base_class.name.to_s
|
||||
else
|
||||
record[@reflection.primary_key_name] = @owner.id unless @owner.new_record?
|
||||
unless @owner.new_record?
|
||||
primary_key = @reflection.options[:primary_key] || :id
|
||||
record[@reflection.primary_key_name] = @owner.send(primary_key)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -764,7 +764,7 @@ module ActiveRecord #:nodoc:
|
||||
#
|
||||
# You can delete multiple rows at once by passing an Array of <tt>id</tt>s.
|
||||
#
|
||||
# Careful: although it is often much faster than the alternative,
|
||||
# Note: Although it is often much faster than the alternative,
|
||||
# <tt>#destroy</tt>, skipping callbacks might bypass business logic in
|
||||
# your application that ensures referential integrity or performs other
|
||||
# essential jobs.
|
||||
@@ -859,7 +859,7 @@ module ActiveRecord #:nodoc:
|
||||
# reflect that no changes should be made (since they can't be
|
||||
# persisted).
|
||||
#
|
||||
# Note: the instantiation, callback execution, and deletion of each
|
||||
# Note: Instantiation, callback execution, and deletion of each
|
||||
# record can be time consuming when you're removing many records at
|
||||
# once. It generates at least one SQL +DELETE+ query per record (or
|
||||
# possibly more, to enforce your callbacks). If you want to delete many
|
||||
@@ -1465,7 +1465,10 @@ module ActiveRecord #:nodoc:
|
||||
def respond_to?(method_id, include_private = false)
|
||||
if match = DynamicFinderMatch.match(method_id)
|
||||
return true if all_attributes_exists?(match.attribute_names)
|
||||
elsif match = DynamicScopeMatch.match(method_id)
|
||||
return true if all_attributes_exists?(match.attribute_names)
|
||||
end
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
@@ -1816,11 +1819,12 @@ module ActiveRecord #:nodoc:
|
||||
# It's even possible to use all the additional parameters to +find+. For example, the full interface for +find_all_by_amount+
|
||||
# is actually <tt>find_all_by_amount(amount, options)</tt>.
|
||||
#
|
||||
# This also enables you to initialize a record if it is not found, such as <tt>find_or_initialize_by_amount(amount)</tt>
|
||||
# or <tt>find_or_create_by_user_and_password(user, password)</tt>.
|
||||
# Also enables dynamic scopes like scoped_by_user_name(user_name) and scoped_by_user_name_and_password(user_name, password) that
|
||||
# are turned into scoped(:conditions => ["user_name = ?", user_name]) and scoped(:conditions => ["user_name = ? AND password = ?", user_name, password])
|
||||
# respectively.
|
||||
#
|
||||
# Each dynamic finder or initializer/creator is also defined in the class after it is first invoked, so that future
|
||||
# attempts to use it do not run through <tt>method_missing</tt>.
|
||||
# Each dynamic finder, scope or initializer/creator is also defined in the class after it is first invoked, so that future
|
||||
# attempts to use it do not run through method_missing.
|
||||
def method_missing(method_id, *arguments, &block)
|
||||
if match = DynamicFinderMatch.match(method_id)
|
||||
attribute_names = match.attribute_names
|
||||
@@ -1924,6 +1928,22 @@ module ActiveRecord #:nodoc:
|
||||
}, __FILE__, __LINE__
|
||||
send(method_id, *arguments, &block)
|
||||
end
|
||||
elsif match = DynamicScopeMatch.match(method_id)
|
||||
attribute_names = match.attribute_names
|
||||
super unless all_attributes_exists?(attribute_names)
|
||||
if match.scope?
|
||||
self.class_eval %{
|
||||
def self.#{method_id}(*args) # def self.scoped_by_user_name_and_password(*args)
|
||||
options = args.extract_options! # options = args.extract_options!
|
||||
attributes = construct_attributes_from_arguments( # attributes = construct_attributes_from_arguments(
|
||||
[:#{attribute_names.join(',:')}], args # [:user_name, :password], args
|
||||
) # )
|
||||
#
|
||||
scoped(:conditions => attributes) # scoped(:conditions => attributes)
|
||||
end # end
|
||||
}, __FILE__, __LINE__
|
||||
send(method_id, *arguments)
|
||||
end
|
||||
else
|
||||
super
|
||||
end
|
||||
|
||||
@@ -402,6 +402,10 @@ module ActiveRecord
|
||||
end
|
||||
|
||||
def add_column(table_name, column_name, type, options = {}) #:nodoc:
|
||||
if @connection.respond_to?(:transaction_active?) && @connection.transaction_active?
|
||||
raise StatementInvalid, 'Cannot add columns to a SQLite database while inside a transaction'
|
||||
end
|
||||
|
||||
alter_table(table_name) do |definition|
|
||||
definition.column(column_name, type, options)
|
||||
end
|
||||
|
||||
25
activerecord/lib/active_record/dynamic_scope_match.rb
Normal file
25
activerecord/lib/active_record/dynamic_scope_match.rb
Normal file
@@ -0,0 +1,25 @@
|
||||
module ActiveRecord
|
||||
class DynamicScopeMatch
|
||||
def self.match(method)
|
||||
ds_match = self.new(method)
|
||||
ds_match.scope ? ds_match : nil
|
||||
end
|
||||
|
||||
def initialize(method)
|
||||
@scope = true
|
||||
case method.to_s
|
||||
when /^scoped_by_([_a-zA-Z]\w*)$/
|
||||
names = $1
|
||||
else
|
||||
@scope = nil
|
||||
end
|
||||
@attribute_names = names && names.split('_and_')
|
||||
end
|
||||
|
||||
attr_reader :scope, :attribute_names
|
||||
|
||||
def scope?
|
||||
!@scope.nil?
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -786,4 +786,37 @@ class EagerAssociationTest < ActiveRecord::TestCase
|
||||
assert_equal Person.find(person.id).agents, person.agents
|
||||
end
|
||||
end
|
||||
|
||||
def test_preload_has_many_using_primary_key
|
||||
expected = Firm.find(:first).clients_using_primary_key.to_a
|
||||
firm = Firm.find :first, :include => :clients_using_primary_key
|
||||
assert_no_queries do
|
||||
assert_equal expected, firm.clients_using_primary_key
|
||||
end
|
||||
end
|
||||
|
||||
def test_include_has_many_using_primary_key
|
||||
expected = Firm.find(1).clients_using_primary_key.sort_by &:name
|
||||
firm = Firm.find 1, :include => :clients_using_primary_key, :order => 'clients_using_primary_keys_companies.name'
|
||||
assert_no_queries do
|
||||
assert_equal expected, firm.clients_using_primary_key
|
||||
end
|
||||
end
|
||||
|
||||
def test_preload_has_one_using_primary_key
|
||||
expected = Firm.find(:first).account_using_primary_key
|
||||
firm = Firm.find :first, :include => :account_using_primary_key
|
||||
assert_no_queries do
|
||||
assert_equal expected, firm.account_using_primary_key
|
||||
end
|
||||
end
|
||||
|
||||
def test_include_has_one_using_primary_key
|
||||
expected = Firm.find(1).account_using_primary_key
|
||||
firm = Firm.find(:all, :include => :account_using_primary_key, :order => 'accounts.id').detect {|f| f.id == 1}
|
||||
assert_no_queries do
|
||||
assert_equal expected, firm.account_using_primary_key
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -1115,5 +1115,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
|
||||
assert !client_association.respond_to?(:private_method)
|
||||
assert client_association.respond_to?(:private_method, true)
|
||||
end
|
||||
|
||||
def test_creating_using_primary_key
|
||||
firm = Firm.find(:first)
|
||||
client = firm.clients_using_primary_key.create!(:name => 'test')
|
||||
assert_equal firm.name, client.firm_name
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -254,7 +254,7 @@ class NamedScopeTest < ActiveRecord::TestCase
|
||||
end
|
||||
|
||||
def test_should_use_where_in_query_for_named_scope
|
||||
assert_equal Developer.find_all_by_name('Jamis'), Developer.find_all_by_id(Developer.jamises)
|
||||
assert_equal Developer.find_all_by_name('Jamis').to_set, Developer.find_all_by_id(Developer.jamises).to_set
|
||||
end
|
||||
|
||||
def test_size_should_use_count_when_results_are_not_loaded
|
||||
@@ -278,3 +278,23 @@ class NamedScopeTest < ActiveRecord::TestCase
|
||||
assert_equal post.comments.size, Post.scoped(:joins => join).scoped(:joins => join, :conditions => "posts.id = #{post.id}").size
|
||||
end
|
||||
end
|
||||
|
||||
class DynamicScopeMatchTest < ActiveRecord::TestCase
|
||||
def test_scoped_by_no_match
|
||||
assert_nil ActiveRecord::DynamicScopeMatch.match("not_scoped_at_all")
|
||||
end
|
||||
|
||||
def test_scoped_by
|
||||
match = ActiveRecord::DynamicScopeMatch.match("scoped_by_age_and_sex_and_location")
|
||||
assert_not_nil match
|
||||
assert match.scope?
|
||||
assert_equal %w(age sex location), match.attribute_names
|
||||
end
|
||||
end
|
||||
|
||||
class DynamicScopeTest < ActiveRecord::TestCase
|
||||
def test_dynamic_scope
|
||||
assert_equal Post.scoped_by_author_id(1).find(1), Post.find(1)
|
||||
assert_equal Post.scoped_by_author_id_and_title(1, "Welcome to the weblog").first, Post.find(:first, :conditions => { :author_id => 1, :title => "Welcome to the weblog"})
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
*2.3.0 [Edge]*
|
||||
|
||||
* Object#tap shim for Ruby < 1.8.7. Similar to Object#returning, tap yields self then returns self. [Jeremy Kemper]
|
||||
array.select { ... }.tap(&:inspect).map { ... }
|
||||
|
||||
* TimeWithZone#- gives correct result with wrapped DateTime, and with DateTime argument [Geoff Buesing]
|
||||
|
||||
* Updated i18n gem to version 0.1.1 #1635 [Yaroslav Markin]
|
||||
|
||||
* Add :allow_nil option to delegate. #1127 [Sergio Gil]
|
||||
|
||||
* Add Benchmark.ms convenience method to benchmark realtime in milliseconds. [Jeremy Kemper]
|
||||
|
||||
@@ -192,13 +192,8 @@ module ActiveSupport
|
||||
end
|
||||
|
||||
def should_run_callback?(*args)
|
||||
if options[:if]
|
||||
evaluate_method(options[:if], *args)
|
||||
elsif options[:unless]
|
||||
!evaluate_method(options[:unless], *args)
|
||||
else
|
||||
true
|
||||
end
|
||||
[options[:if]].flatten.compact.all? { |a| evaluate_method(a, *args) } &&
|
||||
![options[:unless]].flatten.compact.any? { |a| evaluate_method(a, *args) }
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -40,6 +40,21 @@ class Object
|
||||
value
|
||||
end
|
||||
|
||||
# Yields <code>x</code> to the block, and then returns <code>x</code>.
|
||||
# The primary purpose of this method is to "tap into" a method chain,
|
||||
# in order to perform operations on intermediate results within the chain.
|
||||
#
|
||||
# (1..10).tap { |x| puts "original: #{x.inspect}" }.to_a.
|
||||
# tap { |x| puts "array: #{x.inspect}" }.
|
||||
# select { |x| x%2 == 0 }.
|
||||
# tap { |x| puts "evens: #{x.inspect}" }.
|
||||
# map { |x| x*x }.
|
||||
# tap { |x| puts "squares: #{x.inspect}" }
|
||||
def tap
|
||||
yield self
|
||||
self
|
||||
end unless Object.respond_to?(:tap)
|
||||
|
||||
# An elegant way to factor duplication out of options passed to a series of
|
||||
# method calls. Each method called in the block, with the block variable as
|
||||
# the receiver, will have its options merged with the default +options+ hash
|
||||
|
||||
@@ -16,7 +16,7 @@ module ActiveSupport
|
||||
|
||||
protected
|
||||
# matches YAML-formatted dates
|
||||
DATE_REGEX = /^\d{4}-\d{2}-\d{2}|\d{4}-\d{1,2}-\d{1,2}[ \t]+\d{1,2}:\d{2}:\d{2}(\.[0-9]*)?(([ \t]*)Z|[-+]\d{2}?(:\d{2})?)?$/
|
||||
DATE_REGEX = /^(?:\d{4}-\d{2}-\d{2}|\d{4}-\d{1,2}-\d{1,2}[ \t]+\d{1,2}:\d{2}:\d{2}(\.[0-9]*)?(([ \t]*)Z|[-+]\d{2}?(:\d{2})?)?)$/
|
||||
|
||||
# Ensure that ":" and "," are always followed by a space
|
||||
def convert_json_to_yaml(json) #:nodoc:
|
||||
|
||||
@@ -12,7 +12,7 @@ module ActiveSupport
|
||||
if benchmark = ARGV.include?('--benchmark') # HAX for rake test
|
||||
{ :benchmark => true,
|
||||
:runs => 4,
|
||||
:metrics => [:process_time, :memory, :objects, :gc_runs, :gc_time],
|
||||
:metrics => [:wall_time, :memory, :objects, :gc_runs, :gc_time],
|
||||
:output => 'tmp/performance' }
|
||||
else
|
||||
{ :benchmark => false,
|
||||
|
||||
@@ -199,7 +199,7 @@ module ActiveSupport
|
||||
# If we're subtracting a Duration of variable length (i.e., years, months, days), move backwards from #time,
|
||||
# otherwise move backwards #utc, for accuracy when moving across DST boundaries
|
||||
if other.acts_like?(:time)
|
||||
utc - other
|
||||
utc.to_f - other.to_f
|
||||
elsif duration_of_variable_length?(other)
|
||||
method_missing(:-, other)
|
||||
else
|
||||
|
||||
@@ -22,8 +22,8 @@ end
|
||||
|
||||
# TODO I18n gem has not been released yet
|
||||
# begin
|
||||
# gem 'i18n', '~> 0.0.1'
|
||||
# gem 'i18n', '~> 0.1.1'
|
||||
# rescue Gem::LoadError
|
||||
$:.unshift "#{File.dirname(__FILE__)}/vendor/i18n-0.0.1"
|
||||
$:.unshift "#{File.dirname(__FILE__)}/vendor/i18n-0.1.1/lib"
|
||||
require 'i18n'
|
||||
# end
|
||||
|
||||
3
activesupport/lib/active_support/vendor/i18n-0.1.1/.gitignore
vendored
Normal file
3
activesupport/lib/active_support/vendor/i18n-0.1.1/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
.DS_Store
|
||||
test/rails/fixtures
|
||||
doc
|
||||
20
activesupport/lib/active_support/vendor/i18n-0.1.1/MIT-LICENSE
vendored
Executable file
20
activesupport/lib/active_support/vendor/i18n-0.1.1/MIT-LICENSE
vendored
Executable file
@@ -0,0 +1,20 @@
|
||||
Copyright (c) 2008 The Ruby I18n team
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
20
activesupport/lib/active_support/vendor/i18n-0.1.1/README.textile
vendored
Normal file
20
activesupport/lib/active_support/vendor/i18n-0.1.1/README.textile
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
h1. Ruby I18n gem
|
||||
|
||||
I18n and localization solution for Ruby.
|
||||
|
||||
For information please refer to http://rails-i18n.org
|
||||
|
||||
h2. Authors
|
||||
|
||||
* "Matt Aimonetti":http://railsontherun.com
|
||||
* "Sven Fuchs":http://www.artweb-design.de
|
||||
* "Joshua Harvey":http://www.workingwithrails.com/person/759-joshua-harvey
|
||||
* "Saimon Moore":http://saimonmoore.net
|
||||
* "Stephan Soller":http://www.arkanis-development.de
|
||||
|
||||
h2. License
|
||||
|
||||
MIT License. See the included MIT-LICENCE file.
|
||||
|
||||
|
||||
|
||||
5
activesupport/lib/active_support/vendor/i18n-0.1.1/Rakefile
vendored
Normal file
5
activesupport/lib/active_support/vendor/i18n-0.1.1/Rakefile
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
task :default => [:test]
|
||||
|
||||
task :test do
|
||||
ruby "test/all.rb"
|
||||
end
|
||||
27
activesupport/lib/active_support/vendor/i18n-0.1.1/i18n.gemspec
vendored
Normal file
27
activesupport/lib/active_support/vendor/i18n-0.1.1/i18n.gemspec
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
Gem::Specification.new do |s|
|
||||
s.name = "i18n"
|
||||
s.version = "0.1.1"
|
||||
s.date = "2008-10-26"
|
||||
s.summary = "Internationalization support for Ruby"
|
||||
s.email = "rails-i18n@googlegroups.com"
|
||||
s.homepage = "http://rails-i18n.org"
|
||||
s.description = "Add Internationalization support to your Ruby application."
|
||||
s.has_rdoc = false
|
||||
s.authors = ['Sven Fuchs', 'Joshua Harvey', 'Matt Aimonetti', 'Stephan Soller', 'Saimon Moore']
|
||||
s.files = [
|
||||
'i18n.gemspec',
|
||||
'lib/i18n/backend/simple.rb',
|
||||
'lib/i18n/exceptions.rb',
|
||||
'lib/i18n.rb',
|
||||
'MIT-LICENSE',
|
||||
'README.textile'
|
||||
]
|
||||
s.test_files = [
|
||||
'test/all.rb',
|
||||
'test/i18n_exceptions_test.rb',
|
||||
'test/i18n_test.rb',
|
||||
'test/locale/en.rb',
|
||||
'test/locale/en.yml',
|
||||
'test/simple_backend_test.rb'
|
||||
]
|
||||
end
|
||||
@@ -2,39 +2,39 @@
|
||||
# Sven Fuchs (http://www.artweb-design.de),
|
||||
# Joshua Harvey (http://www.workingwithrails.com/person/759-joshua-harvey),
|
||||
# Saimon Moore (http://saimonmoore.net),
|
||||
# Stephan Soller (http://www.arkanis-development.de/)
|
||||
# Stephan Soller (http://www.arkanis-development.de/)
|
||||
# Copyright:: Copyright (c) 2008 The Ruby i18n Team
|
||||
# License:: MIT
|
||||
require 'i18n/backend/simple'
|
||||
require 'i18n/exceptions'
|
||||
|
||||
module I18n
|
||||
module I18n
|
||||
@@backend = nil
|
||||
@@load_path = nil
|
||||
@@default_locale = :'en'
|
||||
@@exception_handler = :default_exception_handler
|
||||
|
||||
|
||||
class << self
|
||||
# Returns the current backend. Defaults to +Backend::Simple+.
|
||||
def backend
|
||||
@@backend ||= Backend::Simple.new
|
||||
end
|
||||
|
||||
|
||||
# Sets the current backend. Used to set a custom backend.
|
||||
def backend=(backend)
|
||||
def backend=(backend)
|
||||
@@backend = backend
|
||||
end
|
||||
|
||||
# Returns the current default locale. Defaults to 'en'
|
||||
|
||||
# Returns the current default locale. Defaults to :'en'
|
||||
def default_locale
|
||||
@@default_locale
|
||||
@@default_locale
|
||||
end
|
||||
|
||||
|
||||
# Sets the current default locale. Used to set a custom default locale.
|
||||
def default_locale=(locale)
|
||||
@@default_locale = locale
|
||||
def default_locale=(locale)
|
||||
@@default_locale = locale
|
||||
end
|
||||
|
||||
|
||||
# Returns the current locale. Defaults to I18n.default_locale.
|
||||
def locale
|
||||
Thread.current[:locale] ||= default_locale
|
||||
@@ -44,12 +44,12 @@ module I18n
|
||||
def locale=(locale)
|
||||
Thread.current[:locale] = locale
|
||||
end
|
||||
|
||||
|
||||
# Sets the exception handler.
|
||||
def exception_handler=(exception_handler)
|
||||
@@exception_handler = exception_handler
|
||||
end
|
||||
|
||||
|
||||
# Allow clients to register paths providing translation data sources. The
|
||||
# backend defines acceptable sources.
|
||||
#
|
||||
@@ -74,25 +74,25 @@ module I18n
|
||||
def reload!
|
||||
backend.reload!
|
||||
end
|
||||
|
||||
# Translates, pluralizes and interpolates a given key using a given locale,
|
||||
|
||||
# Translates, pluralizes and interpolates a given key using a given locale,
|
||||
# scope, and default, as well as interpolation values.
|
||||
#
|
||||
# *LOOKUP*
|
||||
#
|
||||
# Translation data is organized as a nested hash using the upper-level keys
|
||||
# as namespaces. <em>E.g.</em>, ActionView ships with the translation:
|
||||
# Translation data is organized as a nested hash using the upper-level keys
|
||||
# as namespaces. <em>E.g.</em>, ActionView ships with the translation:
|
||||
# <tt>:date => {:formats => {:short => "%b %d"}}</tt>.
|
||||
#
|
||||
# Translations can be looked up at any level of this hash using the key argument
|
||||
# and the scope option. <em>E.g.</em>, in this example <tt>I18n.t :date</tt>
|
||||
#
|
||||
# Translations can be looked up at any level of this hash using the key argument
|
||||
# and the scope option. <em>E.g.</em>, in this example <tt>I18n.t :date</tt>
|
||||
# returns the whole translations hash <tt>{:formats => {:short => "%b %d"}}</tt>.
|
||||
#
|
||||
# Key can be either a single key or a dot-separated key (both Strings and Symbols
|
||||
#
|
||||
# Key can be either a single key or a dot-separated key (both Strings and Symbols
|
||||
# work). <em>E.g.</em>, the short format can be looked up using both:
|
||||
# I18n.t 'date.formats.short'
|
||||
# I18n.t :'date.formats.short'
|
||||
#
|
||||
#
|
||||
# Scope can be either a single key, a dot-separated key or an array of keys
|
||||
# or dot-separated keys. Keys and scopes can be combined freely. So these
|
||||
# examples will all look up the same short date format:
|
||||
@@ -105,9 +105,9 @@ module I18n
|
||||
#
|
||||
# Translations can contain interpolation variables which will be replaced by
|
||||
# values passed to #translate as part of the options hash, with the keys matching
|
||||
# the interpolation variable names.
|
||||
# the interpolation variable names.
|
||||
#
|
||||
# <em>E.g.</em>, with a translation <tt>:foo => "foo {{bar}}"</tt> the option
|
||||
# <em>E.g.</em>, with a translation <tt>:foo => "foo {{bar}}"</tt> the option
|
||||
# value for the key +bar+ will be interpolated into the translation:
|
||||
# I18n.t :foo, :bar => 'baz' # => 'foo baz'
|
||||
#
|
||||
@@ -116,7 +116,7 @@ module I18n
|
||||
# Translation data can contain pluralized translations. Pluralized translations
|
||||
# are arrays of singluar/plural versions of translations like <tt>['Foo', 'Foos']</tt>.
|
||||
#
|
||||
# Note that <tt>I18n::Backend::Simple</tt> only supports an algorithm for English
|
||||
# Note that <tt>I18n::Backend::Simple</tt> only supports an algorithm for English
|
||||
# pluralization rules. Other algorithms can be supported by custom backends.
|
||||
#
|
||||
# This returns the singular version of a pluralized translation:
|
||||
@@ -125,9 +125,9 @@ module I18n
|
||||
# These both return the plural version of a pluralized translation:
|
||||
# I18n.t :foo, :count => 0 # => 'Foos'
|
||||
# I18n.t :foo, :count => 2 # => 'Foos'
|
||||
#
|
||||
# The <tt>:count</tt> option can be used both for pluralization and interpolation.
|
||||
# <em>E.g.</em>, with the translation
|
||||
#
|
||||
# The <tt>:count</tt> option can be used both for pluralization and interpolation.
|
||||
# <em>E.g.</em>, with the translation
|
||||
# <tt>:foo => ['{{count}} foo', '{{count}} foos']</tt>, count will
|
||||
# be interpolated to the pluralized translation:
|
||||
# I18n.t :foo, :count => 1 # => '1 foo'
|
||||
@@ -137,11 +137,11 @@ module I18n
|
||||
# This returns the translation for <tt>:foo</tt> or <tt>default</tt> if no translation was found:
|
||||
# I18n.t :foo, :default => 'default'
|
||||
#
|
||||
# This returns the translation for <tt>:foo</tt> or the translation for <tt>:bar</tt> if no
|
||||
# This returns the translation for <tt>:foo</tt> or the translation for <tt>:bar</tt> if no
|
||||
# translation for <tt>:foo</tt> was found:
|
||||
# I18n.t :foo, :default => :bar
|
||||
#
|
||||
# Returns the translation for <tt>:foo</tt> or the translation for <tt>:bar</tt>
|
||||
# Returns the translation for <tt>:foo</tt> or the translation for <tt>:bar</tt>
|
||||
# or <tt>default</tt> if no translations for <tt>:foo</tt> and <tt>:bar</tt> were found.
|
||||
# I18n.t :foo, :default => [:bar, 'default']
|
||||
#
|
||||
@@ -161,9 +161,9 @@ module I18n
|
||||
rescue I18n::ArgumentError => e
|
||||
raise e if options[:raise]
|
||||
send(@@exception_handler, e, locale, key, options)
|
||||
end
|
||||
end
|
||||
alias :t :translate
|
||||
|
||||
|
||||
# Localizes certain objects, such as dates and numbers to local formatting.
|
||||
def localize(object, options = {})
|
||||
locale = options[:locale] || I18n.locale
|
||||
@@ -171,7 +171,7 @@ module I18n
|
||||
backend.localize(locale, object, format)
|
||||
end
|
||||
alias :l :localize
|
||||
|
||||
|
||||
protected
|
||||
# Handles exceptions raised in the backend. All exceptions except for
|
||||
# MissingTranslationData exceptions are re-raised. When a MissingTranslationData
|
||||
@@ -181,7 +181,7 @@ module I18n
|
||||
return exception.message if MissingTranslationData === exception
|
||||
raise exception
|
||||
end
|
||||
|
||||
|
||||
# Merges the given locale, key and scope into a single array of keys.
|
||||
# Splits keys that contain dots into multiple keys. Makes sure all
|
||||
# keys are Symbols.
|
||||
@@ -191,4 +191,4 @@ module I18n
|
||||
keys.flatten.map { |k| k.to_sym }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -6,21 +6,21 @@ module I18n
|
||||
INTERPOLATION_RESERVED_KEYS = %w(scope default)
|
||||
MATCH = /(\\\\)?\{\{([^\}]+)\}\}/
|
||||
|
||||
# Accepts a list of paths to translation files. Loads translations from
|
||||
# Accepts a list of paths to translation files. Loads translations from
|
||||
# plain Ruby (*.rb) or YAML files (*.yml). See #load_rb and #load_yml
|
||||
# for details.
|
||||
def load_translations(*filenames)
|
||||
filenames.each { |filename| load_file(filename) }
|
||||
end
|
||||
|
||||
# Stores translations for the given locale in memory.
|
||||
|
||||
# Stores translations for the given locale in memory.
|
||||
# This uses a deep merge for the translations hash, so existing
|
||||
# translations will be overwritten by new ones only at the deepest
|
||||
# level of the hash.
|
||||
def store_translations(locale, data)
|
||||
merge_translations(locale, data)
|
||||
end
|
||||
|
||||
|
||||
def translate(locale, key, options = {})
|
||||
raise InvalidLocale.new(locale) if locale.nil?
|
||||
return key.map { |k| translate(locale, k, options) } if key.is_a? Array
|
||||
@@ -41,13 +41,13 @@ module I18n
|
||||
entry = interpolate(locale, entry, values)
|
||||
entry
|
||||
end
|
||||
|
||||
# Acts the same as +strftime+, but returns a localized version of the
|
||||
# formatted date string. Takes a key from the date/time formats
|
||||
# translations as a format argument (<em>e.g.</em>, <tt>:short</tt> in <tt>:'date.formats'</tt>).
|
||||
|
||||
# Acts the same as +strftime+, but returns a localized version of the
|
||||
# formatted date string. Takes a key from the date/time formats
|
||||
# translations as a format argument (<em>e.g.</em>, <tt>:short</tt> in <tt>:'date.formats'</tt>).
|
||||
def localize(locale, object, format = :default)
|
||||
raise ArgumentError, "Object must be a Date, DateTime or Time object. #{object.inspect} given." unless object.respond_to?(:strftime)
|
||||
|
||||
|
||||
type = object.respond_to?(:sec) ? 'time' : 'date'
|
||||
# TODO only translate these if format is a String?
|
||||
formats = translate(locale, :"#{type}.formats")
|
||||
@@ -57,14 +57,14 @@ module I18n
|
||||
|
||||
# TODO only translate these if the format string is actually present
|
||||
# TODO check which format strings are present, then bulk translate then, then replace them
|
||||
format.gsub!(/%a/, translate(locale, :"date.abbr_day_names")[object.wday])
|
||||
format.gsub!(/%a/, translate(locale, :"date.abbr_day_names")[object.wday])
|
||||
format.gsub!(/%A/, translate(locale, :"date.day_names")[object.wday])
|
||||
format.gsub!(/%b/, translate(locale, :"date.abbr_month_names")[object.mon])
|
||||
format.gsub!(/%B/, translate(locale, :"date.month_names")[object.mon])
|
||||
format.gsub!(/%p/, translate(locale, :"time.#{object.hour < 12 ? :am : :pm}")) if object.respond_to? :hour
|
||||
object.strftime(format)
|
||||
end
|
||||
|
||||
|
||||
def initialized?
|
||||
@initialized ||= false
|
||||
end
|
||||
@@ -79,12 +79,12 @@ module I18n
|
||||
load_translations(*I18n.load_path)
|
||||
@initialized = true
|
||||
end
|
||||
|
||||
|
||||
def translations
|
||||
@translations ||= {}
|
||||
end
|
||||
|
||||
# Looks up a translation from the translations hash. Returns nil if
|
||||
|
||||
# Looks up a translation from the translations hash. Returns nil if
|
||||
# eiher key is nil, or locale, scope or key do not exist as a key in the
|
||||
# nested translations hash. Splits keys or scopes containing dots
|
||||
# into multiple keys, i.e. <tt>currency.format</tt> is regarded the same as
|
||||
@@ -101,19 +101,19 @@ module I18n
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Evaluates a default translation.
|
||||
|
||||
# Evaluates a default translation.
|
||||
# If the given default is a String it is used literally. If it is a Symbol
|
||||
# it will be translated with the given options. If it is an Array the first
|
||||
# translation yielded will be returned.
|
||||
#
|
||||
# <em>I.e.</em>, <tt>default(locale, [:foo, 'default'])</tt> will return +default+ if
|
||||
#
|
||||
# <em>I.e.</em>, <tt>default(locale, [:foo, 'default'])</tt> will return +default+ if
|
||||
# <tt>translate(locale, :foo)</tt> does not yield a result.
|
||||
def default(locale, default, options = {})
|
||||
case default
|
||||
when String then default
|
||||
when Symbol then translate locale, default, options
|
||||
when Array then default.each do |obj|
|
||||
when Array then default.each do |obj|
|
||||
result = default(locale, obj, options.dup) and return result
|
||||
end and nil
|
||||
end
|
||||
@@ -135,10 +135,10 @@ module I18n
|
||||
end
|
||||
|
||||
# Interpolates values into a given string.
|
||||
#
|
||||
# interpolate "file {{file}} opened by \\{{user}}", :file => 'test.txt', :user => 'Mr. X'
|
||||
#
|
||||
# interpolate "file {{file}} opened by \\{{user}}", :file => 'test.txt', :user => 'Mr. X'
|
||||
# # => "file test.txt opened by {{user}}"
|
||||
#
|
||||
#
|
||||
# Note that you have to double escape the <tt>\\</tt> when you want to escape
|
||||
# the <tt>{{...}}</tt> key in a string (once for the string and once for the
|
||||
# interpolation).
|
||||
@@ -167,8 +167,8 @@ module I18n
|
||||
result.force_encoding(original_encoding) if original_encoding
|
||||
result
|
||||
end
|
||||
|
||||
# Loads a single translations file by delegating to #load_rb or
|
||||
|
||||
# Loads a single translations file by delegating to #load_rb or
|
||||
# #load_yml depending on the file extension and directly merges the
|
||||
# data to the existing translations. Raises I18n::UnknownFileType
|
||||
# for all other file extensions.
|
||||
@@ -178,19 +178,19 @@ module I18n
|
||||
data = send :"load_#{type}", filename # TODO raise a meaningful exception if this does not yield a Hash
|
||||
data.each { |locale, d| merge_translations(locale, d) }
|
||||
end
|
||||
|
||||
|
||||
# Loads a plain Ruby translations file. eval'ing the file must yield
|
||||
# a Hash containing translation data with locales as toplevel keys.
|
||||
def load_rb(filename)
|
||||
eval(IO.read(filename), binding, filename)
|
||||
end
|
||||
|
||||
# Loads a YAML translations file. The data must have locales as
|
||||
|
||||
# Loads a YAML translations file. The data must have locales as
|
||||
# toplevel keys.
|
||||
def load_yml(filename)
|
||||
YAML::load(IO.read(filename))
|
||||
end
|
||||
|
||||
|
||||
# Deep merges the given translations hash with the existing translations
|
||||
# for the given locale
|
||||
def merge_translations(locale, data)
|
||||
@@ -202,7 +202,7 @@ module I18n
|
||||
merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
|
||||
translations[locale].merge!(data, &merger)
|
||||
end
|
||||
|
||||
|
||||
# Return a new hash with all keys and nested keys converted to symbols.
|
||||
def deep_symbolize_keys(hash)
|
||||
hash.inject({}) { |result, (key, value)|
|
||||
@@ -1,6 +1,6 @@
|
||||
module I18n
|
||||
class ArgumentError < ::ArgumentError; end
|
||||
|
||||
|
||||
class InvalidLocale < ArgumentError
|
||||
attr_reader :locale
|
||||
def initialize(locale)
|
||||
@@ -42,7 +42,7 @@ module I18n
|
||||
super "reserved key #{key.inspect} used in #{string.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class UnknownFileType < ArgumentError
|
||||
attr_reader :type, :filename
|
||||
def initialize(type, filename)
|
||||
@@ -50,4 +50,4 @@ module I18n
|
||||
super "can not load translations from #{filename}, the file type #{type} is not known"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
5
activesupport/lib/active_support/vendor/i18n-0.1.1/test/all.rb
vendored
Normal file
5
activesupport/lib/active_support/vendor/i18n-0.1.1/test/all.rb
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
dir = File.dirname(__FILE__)
|
||||
require dir + '/i18n_test.rb'
|
||||
require dir + '/simple_backend_test.rb'
|
||||
require dir + '/i18n_exceptions_test.rb'
|
||||
# *require* dir + '/custom_backend_test.rb'
|
||||
100
activesupport/lib/active_support/vendor/i18n-0.1.1/test/i18n_exceptions_test.rb
vendored
Normal file
100
activesupport/lib/active_support/vendor/i18n-0.1.1/test/i18n_exceptions_test.rb
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
$:.unshift "lib"
|
||||
|
||||
require 'rubygems'
|
||||
require 'test/unit'
|
||||
require 'mocha'
|
||||
require 'i18n'
|
||||
require 'active_support'
|
||||
|
||||
class I18nExceptionsTest < Test::Unit::TestCase
|
||||
def test_invalid_locale_stores_locale
|
||||
force_invalid_locale
|
||||
rescue I18n::ArgumentError => e
|
||||
assert_nil e.locale
|
||||
end
|
||||
|
||||
def test_invalid_locale_message
|
||||
force_invalid_locale
|
||||
rescue I18n::ArgumentError => e
|
||||
assert_equal 'nil is not a valid locale', e.message
|
||||
end
|
||||
|
||||
def test_missing_translation_data_stores_locale_key_and_options
|
||||
force_missing_translation_data
|
||||
rescue I18n::ArgumentError => e
|
||||
options = {:scope => :bar}
|
||||
assert_equal 'de', e.locale
|
||||
assert_equal :foo, e.key
|
||||
assert_equal options, e.options
|
||||
end
|
||||
|
||||
def test_missing_translation_data_message
|
||||
force_missing_translation_data
|
||||
rescue I18n::ArgumentError => e
|
||||
assert_equal 'translation missing: de, bar, foo', e.message
|
||||
end
|
||||
|
||||
def test_invalid_pluralization_data_stores_entry_and_count
|
||||
force_invalid_pluralization_data
|
||||
rescue I18n::ArgumentError => e
|
||||
assert_equal [:bar], e.entry
|
||||
assert_equal 1, e.count
|
||||
end
|
||||
|
||||
def test_invalid_pluralization_data_message
|
||||
force_invalid_pluralization_data
|
||||
rescue I18n::ArgumentError => e
|
||||
assert_equal 'translation data [:bar] can not be used with :count => 1', e.message
|
||||
end
|
||||
|
||||
def test_missing_interpolation_argument_stores_key_and_string
|
||||
force_missing_interpolation_argument
|
||||
rescue I18n::ArgumentError => e
|
||||
assert_equal 'bar', e.key
|
||||
assert_equal "{{bar}}", e.string
|
||||
end
|
||||
|
||||
def test_missing_interpolation_argument_message
|
||||
force_missing_interpolation_argument
|
||||
rescue I18n::ArgumentError => e
|
||||
assert_equal 'interpolation argument bar missing in "{{bar}}"', e.message
|
||||
end
|
||||
|
||||
def test_reserved_interpolation_key_stores_key_and_string
|
||||
force_reserved_interpolation_key
|
||||
rescue I18n::ArgumentError => e
|
||||
assert_equal 'scope', e.key
|
||||
assert_equal "{{scope}}", e.string
|
||||
end
|
||||
|
||||
def test_reserved_interpolation_key_message
|
||||
force_reserved_interpolation_key
|
||||
rescue I18n::ArgumentError => e
|
||||
assert_equal 'reserved key "scope" used in "{{scope}}"', e.message
|
||||
end
|
||||
|
||||
private
|
||||
def force_invalid_locale
|
||||
I18n.backend.translate nil, :foo
|
||||
end
|
||||
|
||||
def force_missing_translation_data
|
||||
I18n.backend.store_translations 'de', :bar => nil
|
||||
I18n.backend.translate 'de', :foo, :scope => :bar
|
||||
end
|
||||
|
||||
def force_invalid_pluralization_data
|
||||
I18n.backend.store_translations 'de', :foo => [:bar]
|
||||
I18n.backend.translate 'de', :foo, :count => 1
|
||||
end
|
||||
|
||||
def force_missing_interpolation_argument
|
||||
I18n.backend.store_translations 'de', :foo => "{{bar}}"
|
||||
I18n.backend.translate 'de', :foo, :baz => 'baz'
|
||||
end
|
||||
|
||||
def force_reserved_interpolation_key
|
||||
I18n.backend.store_translations 'de', :foo => "{{scope}}"
|
||||
I18n.backend.translate 'de', :foo, :baz => 'baz'
|
||||
end
|
||||
end
|
||||
125
activesupport/lib/active_support/vendor/i18n-0.1.1/test/i18n_test.rb
vendored
Normal file
125
activesupport/lib/active_support/vendor/i18n-0.1.1/test/i18n_test.rb
vendored
Normal file
@@ -0,0 +1,125 @@
|
||||
$:.unshift "lib"
|
||||
|
||||
require 'rubygems'
|
||||
require 'test/unit'
|
||||
require 'mocha'
|
||||
require 'i18n'
|
||||
require 'active_support'
|
||||
|
||||
class I18nTest < Test::Unit::TestCase
|
||||
def setup
|
||||
I18n.backend.store_translations :'en', {
|
||||
:currency => {
|
||||
:format => {
|
||||
:separator => '.',
|
||||
:delimiter => ',',
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def test_uses_simple_backend_set_by_default
|
||||
assert I18n.backend.is_a?(I18n::Backend::Simple)
|
||||
end
|
||||
|
||||
def test_can_set_backend
|
||||
assert_nothing_raised{ I18n.backend = self }
|
||||
assert_equal self, I18n.backend
|
||||
I18n.backend = I18n::Backend::Simple.new
|
||||
end
|
||||
|
||||
def test_uses_en_us_as_default_locale_by_default
|
||||
assert_equal 'en', I18n.default_locale
|
||||
end
|
||||
|
||||
def test_can_set_default_locale
|
||||
assert_nothing_raised{ I18n.default_locale = 'de' }
|
||||
assert_equal 'de', I18n.default_locale
|
||||
I18n.default_locale = 'en'
|
||||
end
|
||||
|
||||
def test_uses_default_locale_as_locale_by_default
|
||||
assert_equal I18n.default_locale, I18n.locale
|
||||
end
|
||||
|
||||
def test_can_set_locale_to_thread_current
|
||||
assert_nothing_raised{ I18n.locale = 'de' }
|
||||
assert_equal 'de', I18n.locale
|
||||
assert_equal 'de', Thread.current[:locale]
|
||||
I18n.locale = 'en'
|
||||
end
|
||||
|
||||
def test_can_set_exception_handler
|
||||
assert_nothing_raised{ I18n.exception_handler = :custom_exception_handler }
|
||||
I18n.exception_handler = :default_exception_handler # revert it
|
||||
end
|
||||
|
||||
def test_uses_custom_exception_handler
|
||||
I18n.exception_handler = :custom_exception_handler
|
||||
I18n.expects(:custom_exception_handler)
|
||||
I18n.translate :bogus
|
||||
I18n.exception_handler = :default_exception_handler # revert it
|
||||
end
|
||||
|
||||
def test_delegates_translate_to_backend
|
||||
I18n.backend.expects(:translate).with 'de', :foo, {}
|
||||
I18n.translate :foo, :locale => 'de'
|
||||
end
|
||||
|
||||
def test_delegates_localize_to_backend
|
||||
I18n.backend.expects(:localize).with 'de', :whatever, :default
|
||||
I18n.localize :whatever, :locale => 'de'
|
||||
end
|
||||
|
||||
def test_translate_given_no_locale_uses_i18n_locale
|
||||
I18n.backend.expects(:translate).with 'en', :foo, {}
|
||||
I18n.translate :foo
|
||||
end
|
||||
|
||||
def test_translate_on_nested_symbol_keys_works
|
||||
assert_equal ".", I18n.t(:'currency.format.separator')
|
||||
end
|
||||
|
||||
def test_translate_with_nested_string_keys_works
|
||||
assert_equal ".", I18n.t('currency.format.separator')
|
||||
end
|
||||
|
||||
def test_translate_with_array_as_scope_works
|
||||
assert_equal ".", I18n.t(:separator, :scope => ['currency.format'])
|
||||
end
|
||||
|
||||
def test_translate_with_array_containing_dot_separated_strings_as_scope_works
|
||||
assert_equal ".", I18n.t(:separator, :scope => ['currency.format'])
|
||||
end
|
||||
|
||||
def test_translate_with_key_array_and_dot_separated_scope_works
|
||||
assert_equal [".", ","], I18n.t(%w(separator delimiter), :scope => 'currency.format')
|
||||
end
|
||||
|
||||
def test_translate_with_dot_separated_key_array_and_scope_works
|
||||
assert_equal [".", ","], I18n.t(%w(format.separator format.delimiter), :scope => 'currency')
|
||||
end
|
||||
|
||||
def test_translate_with_options_using_scope_works
|
||||
I18n.backend.expects(:translate).with('de', :precision, :scope => :"currency.format")
|
||||
I18n.with_options :locale => 'de', :scope => :'currency.format' do |locale|
|
||||
locale.t :precision
|
||||
end
|
||||
end
|
||||
|
||||
# def test_translate_given_no_args_raises_missing_translation_data
|
||||
# assert_equal "translation missing: en, no key", I18n.t
|
||||
# end
|
||||
|
||||
def test_translate_given_a_bogus_key_raises_missing_translation_data
|
||||
assert_equal "translation missing: en, bogus", I18n.t(:bogus)
|
||||
end
|
||||
|
||||
def test_localize_nil_raises_argument_error
|
||||
assert_raises(I18n::ArgumentError) { I18n.l nil }
|
||||
end
|
||||
|
||||
def test_localize_object_raises_argument_error
|
||||
assert_raises(I18n::ArgumentError) { I18n.l Object.new }
|
||||
end
|
||||
end
|
||||
1
activesupport/lib/active_support/vendor/i18n-0.1.1/test/locale/en.rb
vendored
Normal file
1
activesupport/lib/active_support/vendor/i18n-0.1.1/test/locale/en.rb
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{:'en-Ruby' => {:foo => {:bar => "baz"}}}
|
||||
3
activesupport/lib/active_support/vendor/i18n-0.1.1/test/locale/en.yml
vendored
Normal file
3
activesupport/lib/active_support/vendor/i18n-0.1.1/test/locale/en.yml
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
en-Yaml:
|
||||
foo:
|
||||
bar: baz
|
||||
502
activesupport/lib/active_support/vendor/i18n-0.1.1/test/simple_backend_test.rb
vendored
Normal file
502
activesupport/lib/active_support/vendor/i18n-0.1.1/test/simple_backend_test.rb
vendored
Normal file
@@ -0,0 +1,502 @@
|
||||
# encoding: utf-8
|
||||
$:.unshift "lib"
|
||||
|
||||
require 'rubygems'
|
||||
require 'test/unit'
|
||||
require 'mocha'
|
||||
require 'i18n'
|
||||
require 'time'
|
||||
require 'yaml'
|
||||
|
||||
module I18nSimpleBackendTestSetup
|
||||
def setup_backend
|
||||
# backend_reset_translations!
|
||||
@backend = I18n::Backend::Simple.new
|
||||
@backend.store_translations 'en', :foo => {:bar => 'bar', :baz => 'baz'}
|
||||
@locale_dir = File.dirname(__FILE__) + '/locale'
|
||||
end
|
||||
alias :setup :setup_backend
|
||||
|
||||
# def backend_reset_translations!
|
||||
# I18n::Backend::Simple::ClassMethods.send :class_variable_set, :@@translations, {}
|
||||
# end
|
||||
|
||||
def backend_get_translations
|
||||
# I18n::Backend::Simple::ClassMethods.send :class_variable_get, :@@translations
|
||||
@backend.instance_variable_get :@translations
|
||||
end
|
||||
|
||||
def add_datetime_translations
|
||||
@backend.store_translations :'de', {
|
||||
:date => {
|
||||
:formats => {
|
||||
:default => "%d.%m.%Y",
|
||||
:short => "%d. %b",
|
||||
:long => "%d. %B %Y",
|
||||
},
|
||||
:day_names => %w(Sonntag Montag Dienstag Mittwoch Donnerstag Freitag Samstag),
|
||||
:abbr_day_names => %w(So Mo Di Mi Do Fr Sa),
|
||||
:month_names => %w(Januar Februar März April Mai Juni Juli August September Oktober November Dezember).unshift(nil),
|
||||
:abbr_month_names => %w(Jan Feb Mar Apr Mai Jun Jul Aug Sep Okt Nov Dez).unshift(nil),
|
||||
:order => [:day, :month, :year]
|
||||
},
|
||||
:time => {
|
||||
:formats => {
|
||||
:default => "%a, %d. %b %Y %H:%M:%S %z",
|
||||
:short => "%d. %b %H:%M",
|
||||
:long => "%d. %B %Y %H:%M",
|
||||
},
|
||||
:am => 'am',
|
||||
:pm => 'pm'
|
||||
},
|
||||
:datetime => {
|
||||
:distance_in_words => {
|
||||
:half_a_minute => 'half a minute',
|
||||
:less_than_x_seconds => {
|
||||
:one => 'less than 1 second',
|
||||
:other => 'less than {{count}} seconds'
|
||||
},
|
||||
:x_seconds => {
|
||||
:one => '1 second',
|
||||
:other => '{{count}} seconds'
|
||||
},
|
||||
:less_than_x_minutes => {
|
||||
:one => 'less than a minute',
|
||||
:other => 'less than {{count}} minutes'
|
||||
},
|
||||
:x_minutes => {
|
||||
:one => '1 minute',
|
||||
:other => '{{count}} minutes'
|
||||
},
|
||||
:about_x_hours => {
|
||||
:one => 'about 1 hour',
|
||||
:other => 'about {{count}} hours'
|
||||
},
|
||||
:x_days => {
|
||||
:one => '1 day',
|
||||
:other => '{{count}} days'
|
||||
},
|
||||
:about_x_months => {
|
||||
:one => 'about 1 month',
|
||||
:other => 'about {{count}} months'
|
||||
},
|
||||
:x_months => {
|
||||
:one => '1 month',
|
||||
:other => '{{count}} months'
|
||||
},
|
||||
:about_x_years => {
|
||||
:one => 'about 1 year',
|
||||
:other => 'about {{count}} year'
|
||||
},
|
||||
:over_x_years => {
|
||||
:one => 'over 1 year',
|
||||
:other => 'over {{count}} years'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
class I18nSimpleBackendTranslationsTest < Test::Unit::TestCase
|
||||
include I18nSimpleBackendTestSetup
|
||||
|
||||
def test_store_translations_adds_translations # no, really :-)
|
||||
@backend.store_translations :'en', :foo => 'bar'
|
||||
assert_equal Hash[:'en', {:foo => 'bar'}], backend_get_translations
|
||||
end
|
||||
|
||||
def test_store_translations_deep_merges_translations
|
||||
@backend.store_translations :'en', :foo => {:bar => 'bar'}
|
||||
@backend.store_translations :'en', :foo => {:baz => 'baz'}
|
||||
assert_equal Hash[:'en', {:foo => {:bar => 'bar', :baz => 'baz'}}], backend_get_translations
|
||||
end
|
||||
|
||||
def test_store_translations_forces_locale_to_sym
|
||||
@backend.store_translations 'en', :foo => 'bar'
|
||||
assert_equal Hash[:'en', {:foo => 'bar'}], backend_get_translations
|
||||
end
|
||||
|
||||
def test_store_translations_converts_keys_to_symbols
|
||||
# backend_reset_translations!
|
||||
@backend.store_translations 'en', 'foo' => {'bar' => 'bar', 'baz' => 'baz'}
|
||||
assert_equal Hash[:'en', {:foo => {:bar => 'bar', :baz => 'baz'}}], backend_get_translations
|
||||
end
|
||||
end
|
||||
|
||||
class I18nSimpleBackendTranslateTest < Test::Unit::TestCase
|
||||
include I18nSimpleBackendTestSetup
|
||||
|
||||
def test_translate_calls_lookup_with_locale_given
|
||||
@backend.expects(:lookup).with('de', :bar, [:foo]).returns 'bar'
|
||||
@backend.translate 'de', :bar, :scope => [:foo]
|
||||
end
|
||||
|
||||
def test_given_no_keys_it_returns_the_default
|
||||
assert_equal 'default', @backend.translate('en', nil, :default => 'default')
|
||||
end
|
||||
|
||||
def test_translate_given_a_symbol_as_a_default_translates_the_symbol
|
||||
assert_equal 'bar', @backend.translate('en', nil, :scope => [:foo], :default => :bar)
|
||||
end
|
||||
|
||||
def test_translate_given_an_array_as_default_uses_the_first_match
|
||||
assert_equal 'bar', @backend.translate('en', :does_not_exist, :scope => [:foo], :default => [:does_not_exist_2, :bar])
|
||||
end
|
||||
|
||||
def test_translate_given_an_array_of_inexistent_keys_it_raises_missing_translation_data
|
||||
assert_raises I18n::MissingTranslationData do
|
||||
@backend.translate('en', :does_not_exist, :scope => [:foo], :default => [:does_not_exist_2, :does_not_exist_3])
|
||||
end
|
||||
end
|
||||
|
||||
def test_translate_an_array_of_keys_translates_all_of_them
|
||||
assert_equal %w(bar baz), @backend.translate('en', [:bar, :baz], :scope => [:foo])
|
||||
end
|
||||
|
||||
def test_translate_calls_pluralize
|
||||
@backend.expects(:pluralize).with 'en', 'bar', 1
|
||||
@backend.translate 'en', :bar, :scope => [:foo], :count => 1
|
||||
end
|
||||
|
||||
def test_translate_calls_interpolate
|
||||
@backend.expects(:interpolate).with 'en', 'bar', {}
|
||||
@backend.translate 'en', :bar, :scope => [:foo]
|
||||
end
|
||||
|
||||
def test_translate_calls_interpolate_including_count_as_a_value
|
||||
@backend.expects(:interpolate).with 'en', 'bar', {:count => 1}
|
||||
@backend.translate 'en', :bar, :scope => [:foo], :count => 1
|
||||
end
|
||||
|
||||
def test_translate_given_nil_as_a_locale_raises_an_argument_error
|
||||
assert_raises(I18n::InvalidLocale){ @backend.translate nil, :bar }
|
||||
end
|
||||
|
||||
def test_translate_with_a_bogus_key_and_no_default_raises_missing_translation_data
|
||||
assert_raises(I18n::MissingTranslationData){ @backend.translate 'de', :bogus }
|
||||
end
|
||||
end
|
||||
|
||||
class I18nSimpleBackendLookupTest < Test::Unit::TestCase
|
||||
include I18nSimpleBackendTestSetup
|
||||
|
||||
# useful because this way we can use the backend with no key for interpolation/pluralization
|
||||
def test_lookup_given_nil_as_a_key_returns_nil
|
||||
assert_nil @backend.send(:lookup, 'en', nil)
|
||||
end
|
||||
|
||||
def test_lookup_given_nested_keys_looks_up_a_nested_hash_value
|
||||
assert_equal 'bar', @backend.send(:lookup, 'en', :bar, [:foo])
|
||||
end
|
||||
end
|
||||
|
||||
class I18nSimpleBackendPluralizeTest < Test::Unit::TestCase
|
||||
include I18nSimpleBackendTestSetup
|
||||
|
||||
def test_pluralize_given_nil_returns_the_given_entry
|
||||
entry = {:one => 'bar', :other => 'bars'}
|
||||
assert_equal entry, @backend.send(:pluralize, nil, entry, nil)
|
||||
end
|
||||
|
||||
def test_pluralize_given_0_returns_zero_string_if_zero_key_given
|
||||
assert_equal 'zero', @backend.send(:pluralize, nil, {:zero => 'zero', :one => 'bar', :other => 'bars'}, 0)
|
||||
end
|
||||
|
||||
def test_pluralize_given_0_returns_plural_string_if_no_zero_key_given
|
||||
assert_equal 'bars', @backend.send(:pluralize, nil, {:one => 'bar', :other => 'bars'}, 0)
|
||||
end
|
||||
|
||||
def test_pluralize_given_1_returns_singular_string
|
||||
assert_equal 'bar', @backend.send(:pluralize, nil, {:one => 'bar', :other => 'bars'}, 1)
|
||||
end
|
||||
|
||||
def test_pluralize_given_2_returns_plural_string
|
||||
assert_equal 'bars', @backend.send(:pluralize, nil, {:one => 'bar', :other => 'bars'}, 2)
|
||||
end
|
||||
|
||||
def test_pluralize_given_3_returns_plural_string
|
||||
assert_equal 'bars', @backend.send(:pluralize, nil, {:one => 'bar', :other => 'bars'}, 3)
|
||||
end
|
||||
|
||||
def test_interpolate_given_incomplete_pluralization_data_raises_invalid_pluralization_data
|
||||
assert_raises(I18n::InvalidPluralizationData){ @backend.send(:pluralize, nil, {:one => 'bar'}, 2) }
|
||||
end
|
||||
|
||||
# def test_interpolate_given_a_string_raises_invalid_pluralization_data
|
||||
# assert_raises(I18n::InvalidPluralizationData){ @backend.send(:pluralize, nil, 'bar', 2) }
|
||||
# end
|
||||
#
|
||||
# def test_interpolate_given_an_array_raises_invalid_pluralization_data
|
||||
# assert_raises(I18n::InvalidPluralizationData){ @backend.send(:pluralize, nil, ['bar'], 2) }
|
||||
# end
|
||||
end
|
||||
|
||||
class I18nSimpleBackendInterpolateTest < Test::Unit::TestCase
|
||||
include I18nSimpleBackendTestSetup
|
||||
|
||||
def test_interpolate_given_a_value_hash_interpolates_the_values_to_the_string
|
||||
assert_equal 'Hi David!', @backend.send(:interpolate, nil, 'Hi {{name}}!', :name => 'David')
|
||||
end
|
||||
|
||||
def test_interpolate_given_a_value_hash_interpolates_into_unicode_string
|
||||
assert_equal 'Häi David!', @backend.send(:interpolate, nil, 'Häi {{name}}!', :name => 'David')
|
||||
end
|
||||
|
||||
def test_interpolate_given_nil_as_a_string_returns_nil
|
||||
assert_nil @backend.send(:interpolate, nil, nil, :name => 'David')
|
||||
end
|
||||
|
||||
def test_interpolate_given_an_non_string_as_a_string_returns_nil
|
||||
assert_equal [], @backend.send(:interpolate, nil, [], :name => 'David')
|
||||
end
|
||||
|
||||
def test_interpolate_given_a_values_hash_with_nil_values_interpolates_the_string
|
||||
assert_equal 'Hi !', @backend.send(:interpolate, nil, 'Hi {{name}}!', {:name => nil})
|
||||
end
|
||||
|
||||
def test_interpolate_given_an_empty_values_hash_raises_missing_interpolation_argument
|
||||
assert_raises(I18n::MissingInterpolationArgument) { @backend.send(:interpolate, nil, 'Hi {{name}}!', {}) }
|
||||
end
|
||||
|
||||
def test_interpolate_given_a_string_containing_a_reserved_key_raises_reserved_interpolation_key
|
||||
assert_raises(I18n::ReservedInterpolationKey) { @backend.send(:interpolate, nil, '{{default}}', {:default => nil}) }
|
||||
end
|
||||
end
|
||||
|
||||
class I18nSimpleBackendLocalizeDateTest < Test::Unit::TestCase
|
||||
include I18nSimpleBackendTestSetup
|
||||
|
||||
def setup
|
||||
@backend = I18n::Backend::Simple.new
|
||||
add_datetime_translations
|
||||
@date = Date.new 2008, 1, 1
|
||||
end
|
||||
|
||||
def test_translate_given_the_short_format_it_uses_it
|
||||
assert_equal '01. Jan', @backend.localize('de', @date, :short)
|
||||
end
|
||||
|
||||
def test_translate_given_the_long_format_it_uses_it
|
||||
assert_equal '01. Januar 2008', @backend.localize('de', @date, :long)
|
||||
end
|
||||
|
||||
def test_translate_given_the_default_format_it_uses_it
|
||||
assert_equal '01.01.2008', @backend.localize('de', @date, :default)
|
||||
end
|
||||
|
||||
def test_translate_given_a_day_name_format_it_returns_a_day_name
|
||||
assert_equal 'Dienstag', @backend.localize('de', @date, '%A')
|
||||
end
|
||||
|
||||
def test_translate_given_an_abbr_day_name_format_it_returns_an_abbrevated_day_name
|
||||
assert_equal 'Di', @backend.localize('de', @date, '%a')
|
||||
end
|
||||
|
||||
def test_translate_given_a_month_name_format_it_returns_a_month_name
|
||||
assert_equal 'Januar', @backend.localize('de', @date, '%B')
|
||||
end
|
||||
|
||||
def test_translate_given_an_abbr_month_name_format_it_returns_an_abbrevated_month_name
|
||||
assert_equal 'Jan', @backend.localize('de', @date, '%b')
|
||||
end
|
||||
|
||||
def test_translate_given_no_format_it_does_not_fail
|
||||
assert_nothing_raised{ @backend.localize 'de', @date }
|
||||
end
|
||||
|
||||
def test_translate_given_an_unknown_format_it_does_not_fail
|
||||
assert_nothing_raised{ @backend.localize 'de', @date, '%x' }
|
||||
end
|
||||
|
||||
def test_localize_nil_raises_argument_error
|
||||
assert_raises(I18n::ArgumentError) { @backend.localize 'de', nil }
|
||||
end
|
||||
|
||||
def test_localize_object_raises_argument_error
|
||||
assert_raises(I18n::ArgumentError) { @backend.localize 'de', Object.new }
|
||||
end
|
||||
end
|
||||
|
||||
class I18nSimpleBackendLocalizeDateTimeTest < Test::Unit::TestCase
|
||||
include I18nSimpleBackendTestSetup
|
||||
|
||||
def setup
|
||||
@backend = I18n::Backend::Simple.new
|
||||
add_datetime_translations
|
||||
@morning = DateTime.new 2008, 1, 1, 6
|
||||
@evening = DateTime.new 2008, 1, 1, 18
|
||||
end
|
||||
|
||||
def test_translate_given_the_short_format_it_uses_it
|
||||
assert_equal '01. Jan 06:00', @backend.localize('de', @morning, :short)
|
||||
end
|
||||
|
||||
def test_translate_given_the_long_format_it_uses_it
|
||||
assert_equal '01. Januar 2008 06:00', @backend.localize('de', @morning, :long)
|
||||
end
|
||||
|
||||
def test_translate_given_the_default_format_it_uses_it
|
||||
assert_equal 'Di, 01. Jan 2008 06:00:00 +0000', @backend.localize('de', @morning, :default)
|
||||
end
|
||||
|
||||
def test_translate_given_a_day_name_format_it_returns_the_correct_day_name
|
||||
assert_equal 'Dienstag', @backend.localize('de', @morning, '%A')
|
||||
end
|
||||
|
||||
def test_translate_given_an_abbr_day_name_format_it_returns_the_correct_abbrevated_day_name
|
||||
assert_equal 'Di', @backend.localize('de', @morning, '%a')
|
||||
end
|
||||
|
||||
def test_translate_given_a_month_name_format_it_returns_the_correct_month_name
|
||||
assert_equal 'Januar', @backend.localize('de', @morning, '%B')
|
||||
end
|
||||
|
||||
def test_translate_given_an_abbr_month_name_format_it_returns_the_correct_abbrevated_month_name
|
||||
assert_equal 'Jan', @backend.localize('de', @morning, '%b')
|
||||
end
|
||||
|
||||
def test_translate_given_a_meridian_indicator_format_it_returns_the_correct_meridian_indicator
|
||||
assert_equal 'am', @backend.localize('de', @morning, '%p')
|
||||
assert_equal 'pm', @backend.localize('de', @evening, '%p')
|
||||
end
|
||||
|
||||
def test_translate_given_no_format_it_does_not_fail
|
||||
assert_nothing_raised{ @backend.localize 'de', @morning }
|
||||
end
|
||||
|
||||
def test_translate_given_an_unknown_format_it_does_not_fail
|
||||
assert_nothing_raised{ @backend.localize 'de', @morning, '%x' }
|
||||
end
|
||||
end
|
||||
|
||||
class I18nSimpleBackendLocalizeTimeTest < Test::Unit::TestCase
|
||||
include I18nSimpleBackendTestSetup
|
||||
|
||||
def setup
|
||||
@old_timezone, ENV['TZ'] = ENV['TZ'], 'UTC'
|
||||
@backend = I18n::Backend::Simple.new
|
||||
add_datetime_translations
|
||||
@morning = Time.parse '2008-01-01 6:00 UTC'
|
||||
@evening = Time.parse '2008-01-01 18:00 UTC'
|
||||
end
|
||||
|
||||
def teardown
|
||||
@old_timezone ? ENV['TZ'] = @old_timezone : ENV.delete('TZ')
|
||||
end
|
||||
|
||||
def test_translate_given_the_short_format_it_uses_it
|
||||
assert_equal '01. Jan 06:00', @backend.localize('de', @morning, :short)
|
||||
end
|
||||
|
||||
def test_translate_given_the_long_format_it_uses_it
|
||||
assert_equal '01. Januar 2008 06:00', @backend.localize('de', @morning, :long)
|
||||
end
|
||||
|
||||
# TODO Seems to break on Windows because ENV['TZ'] is ignored. What's a better way to do this?
|
||||
# def test_translate_given_the_default_format_it_uses_it
|
||||
# assert_equal 'Di, 01. Jan 2008 06:00:00 +0000', @backend.localize('de', @morning, :default)
|
||||
# end
|
||||
|
||||
def test_translate_given_a_day_name_format_it_returns_the_correct_day_name
|
||||
assert_equal 'Dienstag', @backend.localize('de', @morning, '%A')
|
||||
end
|
||||
|
||||
def test_translate_given_an_abbr_day_name_format_it_returns_the_correct_abbrevated_day_name
|
||||
assert_equal 'Di', @backend.localize('de', @morning, '%a')
|
||||
end
|
||||
|
||||
def test_translate_given_a_month_name_format_it_returns_the_correct_month_name
|
||||
assert_equal 'Januar', @backend.localize('de', @morning, '%B')
|
||||
end
|
||||
|
||||
def test_translate_given_an_abbr_month_name_format_it_returns_the_correct_abbrevated_month_name
|
||||
assert_equal 'Jan', @backend.localize('de', @morning, '%b')
|
||||
end
|
||||
|
||||
def test_translate_given_a_meridian_indicator_format_it_returns_the_correct_meridian_indicator
|
||||
assert_equal 'am', @backend.localize('de', @morning, '%p')
|
||||
assert_equal 'pm', @backend.localize('de', @evening, '%p')
|
||||
end
|
||||
|
||||
def test_translate_given_no_format_it_does_not_fail
|
||||
assert_nothing_raised{ @backend.localize 'de', @morning }
|
||||
end
|
||||
|
||||
def test_translate_given_an_unknown_format_it_does_not_fail
|
||||
assert_nothing_raised{ @backend.localize 'de', @morning, '%x' }
|
||||
end
|
||||
end
|
||||
|
||||
class I18nSimpleBackendHelperMethodsTest < Test::Unit::TestCase
|
||||
def setup
|
||||
@backend = I18n::Backend::Simple.new
|
||||
end
|
||||
|
||||
def test_deep_symbolize_keys_works
|
||||
result = @backend.send :deep_symbolize_keys, 'foo' => {'bar' => {'baz' => 'bar'}}
|
||||
expected = {:foo => {:bar => {:baz => 'bar'}}}
|
||||
assert_equal expected, result
|
||||
end
|
||||
end
|
||||
|
||||
class I18nSimpleBackendLoadTranslationsTest < Test::Unit::TestCase
|
||||
include I18nSimpleBackendTestSetup
|
||||
|
||||
def test_load_translations_with_unknown_file_type_raises_exception
|
||||
assert_raises(I18n::UnknownFileType) { @backend.load_translations "#{@locale_dir}/en.xml" }
|
||||
end
|
||||
|
||||
def test_load_translations_with_ruby_file_type_does_not_raise_exception
|
||||
assert_nothing_raised { @backend.load_translations "#{@locale_dir}/en.rb" }
|
||||
end
|
||||
|
||||
def test_load_rb_loads_data_from_ruby_file
|
||||
data = @backend.send :load_rb, "#{@locale_dir}/en.rb"
|
||||
assert_equal({:'en-Ruby' => {:foo => {:bar => "baz"}}}, data)
|
||||
end
|
||||
|
||||
def test_load_rb_loads_data_from_yaml_file
|
||||
data = @backend.send :load_yml, "#{@locale_dir}/en.yml"
|
||||
assert_equal({'en-Yaml' => {'foo' => {'bar' => 'baz'}}}, data)
|
||||
end
|
||||
|
||||
def test_load_translations_loads_from_different_file_formats
|
||||
@backend = I18n::Backend::Simple.new
|
||||
@backend.load_translations "#{@locale_dir}/en.rb", "#{@locale_dir}/en.yml"
|
||||
expected = {
|
||||
:'en-Ruby' => {:foo => {:bar => "baz"}},
|
||||
:'en-Yaml' => {:foo => {:bar => "baz"}}
|
||||
}
|
||||
assert_equal expected, backend_get_translations
|
||||
end
|
||||
end
|
||||
|
||||
class I18nSimpleBackendReloadTranslationsTest < Test::Unit::TestCase
|
||||
include I18nSimpleBackendTestSetup
|
||||
|
||||
def setup
|
||||
@backend = I18n::Backend::Simple.new
|
||||
I18n.load_path = [File.dirname(__FILE__) + '/locale/en.yml']
|
||||
assert_nil backend_get_translations
|
||||
@backend.send :init_translations
|
||||
end
|
||||
|
||||
def teardown
|
||||
I18n.load_path = []
|
||||
end
|
||||
|
||||
def test_setup
|
||||
assert_not_nil backend_get_translations
|
||||
end
|
||||
|
||||
def test_reload_translations_unloads_translations
|
||||
@backend.reload!
|
||||
assert_nil backend_get_translations
|
||||
end
|
||||
|
||||
def test_reload_translations_uninitializes_translations
|
||||
@backend.reload!
|
||||
assert_equal @backend.initialized?, false
|
||||
end
|
||||
end
|
||||
@@ -53,10 +53,41 @@ class Person < Record
|
||||
end
|
||||
|
||||
class ConditionalPerson < Record
|
||||
# proc
|
||||
before_save Proc.new { |r| r.history << [:before_save, :proc] }, :if => Proc.new { |r| true }
|
||||
before_save Proc.new { |r| r.history << "b00m" }, :if => Proc.new { |r| false }
|
||||
before_save Proc.new { |r| r.history << [:before_save, :proc] }, :unless => Proc.new { |r| false }
|
||||
before_save Proc.new { |r| r.history << "b00m" }, :unless => Proc.new { |r| true }
|
||||
# symbol
|
||||
before_save Proc.new { |r| r.history << [:before_save, :symbol] }, :if => :yes
|
||||
before_save Proc.new { |r| r.history << "b00m" }, :if => :no
|
||||
before_save Proc.new { |r| r.history << [:before_save, :symbol] }, :unless => :no
|
||||
before_save Proc.new { |r| r.history << "b00m" }, :unless => :yes
|
||||
# string
|
||||
before_save Proc.new { |r| r.history << [:before_save, :string] }, :if => 'yes'
|
||||
before_save Proc.new { |r| r.history << "b00m" }, :if => 'no'
|
||||
before_save Proc.new { |r| r.history << [:before_save, :string] }, :unless => 'no'
|
||||
before_save Proc.new { |r| r.history << "b00m" }, :unless => 'yes'
|
||||
# Array with conditions
|
||||
before_save Proc.new { |r| r.history << [:before_save, :symbol_array] }, :if => [:yes, :other_yes]
|
||||
before_save Proc.new { |r| r.history << "b00m" }, :if => [:yes, :no]
|
||||
before_save Proc.new { |r| r.history << [:before_save, :symbol_array] }, :unless => [:no, :other_no]
|
||||
before_save Proc.new { |r| r.history << "b00m" }, :unless => [:yes, :no]
|
||||
# Combined if and unless
|
||||
before_save Proc.new { |r| r.history << [:before_save, :combined_symbol] }, :if => :yes, :unless => :no
|
||||
before_save Proc.new { |r| r.history << "b00m" }, :if => :yes, :unless => :yes
|
||||
# Array with different types of conditions
|
||||
before_save Proc.new { |r| r.history << [:before_save, :symbol_proc_string_array] }, :if => [:yes, Proc.new { |r| true }, 'yes']
|
||||
before_save Proc.new { |r| r.history << "b00m" }, :if => [:yes, Proc.new { |r| true }, 'no']
|
||||
# Array with different types of conditions comibned if and unless
|
||||
before_save Proc.new { |r| r.history << [:before_save, :combined_symbol_proc_string_array] },
|
||||
:if => [:yes, Proc.new { |r| true }, 'yes'], :unless => [:no, 'no']
|
||||
before_save Proc.new { |r| r.history << "b00m" }, :if => [:yes, Proc.new { |r| true }, 'no'], :unless => [:no, 'no']
|
||||
|
||||
def yes; true; end
|
||||
def other_yes; true; end
|
||||
def no; false; end
|
||||
def other_no; false; end
|
||||
|
||||
def save
|
||||
run_callbacks(:before_save)
|
||||
@@ -90,7 +121,16 @@ class ConditionalCallbackTest < Test::Unit::TestCase
|
||||
person.save
|
||||
assert_equal [
|
||||
[:before_save, :proc],
|
||||
[:before_save, :proc]
|
||||
[:before_save, :proc],
|
||||
[:before_save, :symbol],
|
||||
[:before_save, :symbol],
|
||||
[:before_save, :string],
|
||||
[:before_save, :string],
|
||||
[:before_save, :symbol_array],
|
||||
[:before_save, :symbol_array],
|
||||
[:before_save, :combined_symbol],
|
||||
[:before_save, :symbol_proc_string_array],
|
||||
[:before_save, :combined_symbol_proc_string_array]
|
||||
], person.history
|
||||
end
|
||||
end
|
||||
|
||||
8
activesupport/test/core_ext/object_ext_test.rb
Normal file
8
activesupport/test/core_ext/object_ext_test.rb
Normal file
@@ -0,0 +1,8 @@
|
||||
require 'abstract_unit'
|
||||
|
||||
class ObjectExtTest < Test::Unit::TestCase
|
||||
def test_tap_yields_and_returns_self
|
||||
foo = Object.new
|
||||
assert_equal foo, foo.tap { |x| assert_equal foo, x; :bar }
|
||||
end
|
||||
end
|
||||
@@ -256,6 +256,15 @@ class TimeWithZoneTest < Test::Unit::TestCase
|
||||
twz2 = ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2), ActiveSupport::TimeZone['UTC'] )
|
||||
assert_equal 86_400.0, twz2 - twz1
|
||||
end
|
||||
|
||||
def test_minus_with_datetime
|
||||
assert_equal 86_400.0, ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2), ActiveSupport::TimeZone['UTC'] ) - DateTime.civil(2000, 1, 1)
|
||||
end
|
||||
|
||||
def test_minus_with_wrapped_datetime
|
||||
assert_equal 86_400.0, ActiveSupport::TimeWithZone.new( DateTime.civil(2000, 1, 2), ActiveSupport::TimeZone['UTC'] ) - Time.utc(2000, 1, 1)
|
||||
assert_equal 86_400.0, ActiveSupport::TimeWithZone.new( DateTime.civil(2000, 1, 2), ActiveSupport::TimeZone['UTC'] ) - DateTime.civil(2000, 1, 1)
|
||||
end
|
||||
|
||||
def test_plus_and_minus_enforce_spring_dst_rules
|
||||
silence_warnings do # silence warnings raised by tzinfo gem
|
||||
|
||||
@@ -15,7 +15,8 @@ class TestJSONDecoding < Test::Unit::TestCase
|
||||
# no time zone
|
||||
%({a: "2007-01-01 01:12:34"}) => {'a' => "2007-01-01 01:12:34"},
|
||||
# needs to be *exact*
|
||||
%({a: " 2007-01-01 01:12:34 Z "}) => {'a' => " 2007-01-01 01:12:34 Z "},
|
||||
%({a: " 2007-01-01 01:12:34 Z "}) => {'a' => " 2007-01-01 01:12:34 Z "},
|
||||
%({a: "2007-01-01 : it's your birthday"}) => {'a' => "2007-01-01 : it's your birthday"},
|
||||
%([]) => [],
|
||||
%({}) => {},
|
||||
%(1) => 1,
|
||||
|
||||
@@ -23,6 +23,7 @@ cd "#{root_dir}/activesupport" do
|
||||
build_results[:activesupport] = system 'rake'
|
||||
end
|
||||
|
||||
rm_f "#{root_dir}/activerecord/debug.log"
|
||||
cd "#{root_dir}/activerecord" do
|
||||
puts
|
||||
puts "[CruiseControl] Building ActiveRecord with MySQL"
|
||||
@@ -37,13 +38,12 @@ cd "#{root_dir}/activerecord" do
|
||||
build_results[:activerecord_postgresql8] = system 'rake test_postgresql'
|
||||
end
|
||||
|
||||
# Sqlite2 is disabled until tests are fixed
|
||||
# cd "#{root_dir}/activerecord" do
|
||||
# puts
|
||||
# puts "[CruiseControl] Building ActiveRecord with SQLite 2"
|
||||
# puts
|
||||
# build_results[:activerecord_sqlite] = system 'rake test_sqlite'
|
||||
# end
|
||||
cd "#{root_dir}/activerecord" do
|
||||
puts
|
||||
puts "[CruiseControl] Building ActiveRecord with SQLite 2"
|
||||
puts
|
||||
build_results[:activerecord_sqlite] = system 'rake test_sqlite'
|
||||
end
|
||||
|
||||
cd "#{root_dir}/activerecord" do
|
||||
puts
|
||||
@@ -59,6 +59,7 @@ cd "#{root_dir}/activemodel" do
|
||||
build_results[:activemodel] = system 'rake'
|
||||
end
|
||||
|
||||
rm_f "#{root_dir}/activeresource/debug.log"
|
||||
cd "#{root_dir}/activeresource" do
|
||||
puts
|
||||
puts "[CruiseControl] Building ActiveResource"
|
||||
|
||||
@@ -54,10 +54,14 @@ ci ALL=NOPASSWD: /usr/local/bin/geminstaller, /usr/local/bin/ruby, /usr/loc
|
||||
* Install/setup nginx:
|
||||
$ sudo aptitude install nginx
|
||||
$ sudo vi /etc/nginx/sites-available/default
|
||||
# Change server_name entry to match server name
|
||||
|
||||
# comment two lines and add one to proxy to ccrb:
|
||||
# root /var/www/nginx-default;
|
||||
# index index.html index.htm;
|
||||
proxy_pass http://127.0.0.1:3333;
|
||||
|
||||
# also comment default locations for /doc and /images
|
||||
$ sudo /etc/init.d/nginx start
|
||||
|
||||
* Add project to cruise (It will still fail until everything is set up):
|
||||
@@ -101,6 +105,13 @@ $ sudo aptitude install sqlite sqlite3 libsqlite-dev libsqlite3-dev
|
||||
$ sudo aptitude install postgresql postgresql-server-dev-8.3
|
||||
$ sudo su - postgres -c 'createuser -s ci'
|
||||
|
||||
* Install fcgi libraries
|
||||
$ sudo apt-get install libfcgi-dev
|
||||
|
||||
* Install memcached and start for first time (should start on reboot automatically)
|
||||
$ sudo aptitude install memcached
|
||||
$ sudo /etc/init.d/memcached start
|
||||
|
||||
* Install and run GemInstaller to get all dependency gems
|
||||
$ sudo gem install geminstaller
|
||||
$ cd ~/.cruise/projects/rails/work
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
Project.configure do |project|
|
||||
project.build_command = 'ruby ci/ci_build.rb'
|
||||
project.email_notifier.emails = ['thewoolleyman@gmail.com','michael@koziarski.com', 'david@loudthinking.com', 'jeremy@bitsweat.net', 'josh@joshpeek.com', 'pratiknaik@gmail.com']
|
||||
# project.email_notifier.emails = ['thewoolleyman@gmail.com']
|
||||
project.email_notifier.emails = ['thewoolleyman@gmail.com','michael@koziarski.com', 'david@loudthinking.com', 'jeremy@bitsweat.net', 'josh@joshpeek.com', 'pratiknaik@gmail.com', 'wycats@gmail.com']
|
||||
project.email_notifier.from = 'thewoolleyman+railsci@gmail.com'
|
||||
end
|
||||
|
||||
@@ -2,13 +2,19 @@
|
||||
gems:
|
||||
- name: geminstaller
|
||||
version: >= 0.4.3
|
||||
- name: fcgi
|
||||
version: >= 0.8.7
|
||||
- name: memcache-client
|
||||
version: >= 1.5.0
|
||||
- name: mocha
|
||||
version: >= 0.9.0
|
||||
version: >= 0.9.4
|
||||
- name: mysql
|
||||
#version: >= 2.7
|
||||
version: = 2.7
|
||||
- name: postgres
|
||||
version: >= 0.7.9.2008.01.28
|
||||
- name: rack
|
||||
version: '~> 0.9.0'
|
||||
- name: rake
|
||||
version: >= 0.8.1
|
||||
- name: sqlite-ruby
|
||||
|
||||
@@ -285,9 +285,19 @@ ul#navMain {
|
||||
|
||||
<li><a href="#_callbacks_registration">Callbacks registration</a></li>
|
||||
|
||||
<li><a href="#_registering_callbacks_by_overriding_the_callback_methods">Registering callbacks by overriding the callback methods</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#_conditional_callbacks">Conditional callbacks</a>
|
||||
<ul>
|
||||
|
||||
<li><a href="#_registering_callbacks_by_using_macro_style_class_methods">Registering callbacks by using macro-style class methods</a></li>
|
||||
<li><a href="#_using_a_symbol_with_the_tt_if_tt_and_tt_unless_tt_options_2">Using a symbol with the <tt>:if</tt> and <tt>:unless</tt> options</a></li>
|
||||
|
||||
<li><a href="#_using_a_string_with_the_tt_if_tt_and_tt_unless_tt_options_2">Using a string with the <tt>:if</tt> and <tt>:unless</tt> options</a></li>
|
||||
|
||||
<li><a href="#_using_a_proc_object_with_the_tt_if_tt_and_tt_unless_tt_options_2">Using a Proc object with the <tt>:if</tt> and :<tt>unless</tt> options</a></li>
|
||||
|
||||
<li><a href="#_multiple_conditions_for_callbacks">Multiple Conditions for Callbacks</a></li>
|
||||
|
||||
</ul>
|
||||
</li>
|
||||
@@ -635,7 +645,7 @@ http://www.gnu.org/software/src-highlite -->
|
||||
<td class="icon">
|
||||
<img src="./images/icons/note.png" alt="Note" />
|
||||
</td>
|
||||
<td class="content">If you want to validate the presence of a boolean field (where the real values are true and false), you will want to use validates_inclusion_of :field_name, :in => [true, false] This is due to the way Object#blank? handles boolean values. false.blank? # => true</td>
|
||||
<td class="content">If you want to validate the presence of a boolean field (where the real values are true and false), you will want to use validates_inclusion_of :field_name, :in ⇒ [true, false] This is due to the way Object#blank? handles boolean values. false.blank? # ⇒ true</td>
|
||||
</tr></table>
|
||||
</div>
|
||||
<div class="paragraph"><p>The default error message for <tt>validates_presence_of</tt> is "<em>can’t be empty</em>".</p></div>
|
||||
@@ -735,7 +745,7 @@ http://www.gnu.org/software/src-highlite -->
|
||||
<div class="sectionbody">
|
||||
<div class="paragraph"><p>Sometimes it will make sense to validate an object just when a given predicate is satisfied. You can do that by using the <tt>:if</tt> and <tt>:unless</tt> options, which can take a symbol, a string or a Ruby Proc. You may use the <tt>:if</tt> option when you want to specify when the validation <strong>should</strong> happen. If you want to specify when the validation <strong>should not</strong> happen, then you may use the <tt>:unless</tt> option.</p></div>
|
||||
<h3 id="_using_a_symbol_with_the_tt_if_tt_and_tt_unless_tt_options">5.1. Using a symbol with the <tt>:if</tt> and <tt>:unless</tt> options</h3>
|
||||
<div class="paragraph"><p>You can associated the <tt>:if</tt> and <tt>:unless</tt> options with a symbol corresponding to the name of a method that will get called right before validation happens. This is the most commonly used option.</p></div>
|
||||
<div class="paragraph"><p>You can associate the <tt>:if</tt> and <tt>:unless</tt> options with a symbol corresponding to the name of a method that will get called right before validation happens. This is the most commonly used option.</p></div>
|
||||
<div class="listingblock">
|
||||
<div class="content"><!-- Generator: GNU source-highlight 2.9
|
||||
by Lorenzo Bettini
|
||||
@@ -1055,26 +1065,7 @@ An object of the <tt>ActionView::Helpers::InstanceTag</tt> class.
|
||||
<div class="sectionbody">
|
||||
<div class="paragraph"><p>Callbacks are methods that get called at certain moments of an object’s lifecycle. With callbacks it’s possible to write code that will run whenever an Active Record object is created, saved, updated, deleted or loaded from the database.</p></div>
|
||||
<h3 id="_callbacks_registration">9.1. Callbacks registration</h3>
|
||||
<div class="paragraph"><p>In order to use the available callbacks, you need to registrate them. There are two ways of doing that.</p></div>
|
||||
<h3 id="_registering_callbacks_by_overriding_the_callback_methods">9.2. Registering callbacks by overriding the callback methods</h3>
|
||||
<div class="paragraph"><p>You can specify the callback method directly, by overriding it. Let’s see how it works using the <tt>before_validation</tt> callback, which will surprisingly run right before any validation is done.</p></div>
|
||||
<div class="listingblock">
|
||||
<div class="content"><!-- Generator: GNU source-highlight 2.9
|
||||
by Lorenzo Bettini
|
||||
http://www.lorenzobettini.it
|
||||
http://www.gnu.org/software/src-highlite -->
|
||||
<pre><tt><span style="font-weight: bold"><span style="color: #0000FF">class</span></span> User <span style="color: #990000"><</span> ActiveRecord<span style="color: #990000">::</span>Base
|
||||
validates_presence_of <span style="color: #990000">:</span>login<span style="color: #990000">,</span> <span style="color: #990000">:</span>email
|
||||
|
||||
protected
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">def</span></span> before_validation
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">if</span></span> <span style="font-weight: bold"><span style="color: #0000FF">self</span></span><span style="color: #990000">.</span>login<span style="color: #990000">.</span><span style="font-weight: bold"><span style="color: #0000FF">nil</span></span><span style="color: #990000">?</span>
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">self</span></span><span style="color: #990000">.</span>login <span style="color: #990000">=</span> email <span style="font-weight: bold"><span style="color: #0000FF">unless</span></span> email<span style="color: #990000">.</span>blank?
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">end</span></span>
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">end</span></span>
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">end</span></span></tt></pre></div></div>
|
||||
<h3 id="_registering_callbacks_by_using_macro_style_class_methods">9.3. Registering callbacks by using macro-style class methods</h3>
|
||||
<div class="paragraph"><p>The other way you can register a callback method is by implementing it as an ordinary method, and then using a macro-style class method to register it as a callback. The last example could be written like that:</p></div>
|
||||
<div class="paragraph"><p>In order to use the available callbacks, you need to registrate them. You can do that by implementing them as an ordinary methods, and then using a macro-style class method to register then as callbacks.</p></div>
|
||||
<div class="listingblock">
|
||||
<div class="content"><!-- Generator: GNU source-highlight 2.9
|
||||
by Lorenzo Bettini
|
||||
@@ -1103,19 +1094,6 @@ http://www.gnu.org/software/src-highlite -->
|
||||
|
||||
before_create <span style="color: #FF0000">{</span><span style="color: #990000">|</span>user<span style="color: #990000">|</span> user<span style="color: #990000">.</span>name <span style="color: #990000">=</span> user<span style="color: #990000">.</span>login<span style="color: #990000">.</span>capitalize <span style="font-weight: bold"><span style="color: #0000FF">if</span></span> user<span style="color: #990000">.</span>name<span style="color: #990000">.</span>blank?<span style="color: #FF0000">}</span>
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">end</span></span></tt></pre></div></div>
|
||||
<div class="paragraph"><p>In Rails, the preferred way of registering callbacks is by using macro-style class methods. The main advantages of using macro-style class methods are:</p></div>
|
||||
<div class="ulist"><ul>
|
||||
<li>
|
||||
<p>
|
||||
You can add more than one method for each type of callback. Those methods will be queued for execution at the same order they were registered.
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>
|
||||
Readability, since your callback declarations will live at the beggining of your models' files.
|
||||
</p>
|
||||
</li>
|
||||
</ul></div>
|
||||
<div class="admonitionblock">
|
||||
<table><tr>
|
||||
<td class="icon">
|
||||
@@ -1125,10 +1103,56 @@ Readability, since your callback declarations will live at the beggining of your
|
||||
</tr></table>
|
||||
</div>
|
||||
</div>
|
||||
<h2 id="_available_callbacks">10. Available callbacks</h2>
|
||||
<h2 id="_conditional_callbacks">10. Conditional callbacks</h2>
|
||||
<div class="sectionbody">
|
||||
<div class="paragraph"><p>Like in validations, we can also make our callbacks conditional, calling then only when a given predicate is satisfied. You can do that by using the <tt>:if</tt> and <tt>:unless</tt> options, which can take a symbol, a string or a Ruby Proc. You may use the <tt>:if</tt> option when you want to specify when the callback <strong>should</strong> get called. If you want to specify when the callback <strong>should not</strong> be called, then you may use the <tt>:unless</tt> option.</p></div>
|
||||
<h3 id="_using_a_symbol_with_the_tt_if_tt_and_tt_unless_tt_options_2">10.1. Using a symbol with the <tt>:if</tt> and <tt>:unless</tt> options</h3>
|
||||
<div class="paragraph"><p>You can associate the <tt>:if</tt> and <tt>:unless</tt> options with a symbol corresponding to the name of a method that will get called right before the callback. If this method returns <tt>false</tt> the callback won’t be executed. This is the most common option. Using this form of registration it’s also possible to register several different methods that should be called to check the if the callback should be executed.</p></div>
|
||||
<div class="listingblock">
|
||||
<div class="content"><!-- Generator: GNU source-highlight 2.9
|
||||
by Lorenzo Bettini
|
||||
http://www.lorenzobettini.it
|
||||
http://www.gnu.org/software/src-highlite -->
|
||||
<pre><tt><span style="font-weight: bold"><span style="color: #0000FF">class</span></span> Order <span style="color: #990000"><</span> ActiveRecord<span style="color: #990000">::</span>Base
|
||||
before_save <span style="color: #990000">:</span>normalize_card_number<span style="color: #990000">,</span> <span style="color: #990000">:</span><span style="font-weight: bold"><span style="color: #0000FF">if</span></span> <span style="color: #990000">=></span> <span style="color: #990000">:</span>paid_with_card?
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">end</span></span></tt></pre></div></div>
|
||||
<h3 id="_using_a_string_with_the_tt_if_tt_and_tt_unless_tt_options_2">10.2. Using a string with the <tt>:if</tt> and <tt>:unless</tt> options</h3>
|
||||
<div class="paragraph"><p>You can also use a string that will be evaluated using <tt>:eval</tt> and needs to contain valid Ruby code. You should use this option only when the string represents a really short condition.</p></div>
|
||||
<div class="listingblock">
|
||||
<div class="content"><!-- Generator: GNU source-highlight 2.9
|
||||
by Lorenzo Bettini
|
||||
http://www.lorenzobettini.it
|
||||
http://www.gnu.org/software/src-highlite -->
|
||||
<pre><tt><span style="font-weight: bold"><span style="color: #0000FF">class</span></span> Order <span style="color: #990000"><</span> ActiveRecord<span style="color: #990000">::</span>Base
|
||||
before_save <span style="color: #990000">:</span>normalize_card_number<span style="color: #990000">,</span> <span style="color: #990000">:</span><span style="font-weight: bold"><span style="color: #0000FF">if</span></span> <span style="color: #990000">=></span> <span style="color: #FF0000">"paid_with_card?"</span>
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">end</span></span></tt></pre></div></div>
|
||||
<h3 id="_using_a_proc_object_with_the_tt_if_tt_and_tt_unless_tt_options_2">10.3. Using a Proc object with the <tt>:if</tt> and :<tt>unless</tt> options</h3>
|
||||
<div class="paragraph"><p>Finally, it’s possible to associate <tt>:if</tt> and <tt>:unless</tt> with a Ruby Proc object. This option is best suited when writing short validation methods, usually one-liners.</p></div>
|
||||
<div class="listingblock">
|
||||
<div class="content"><!-- Generator: GNU source-highlight 2.9
|
||||
by Lorenzo Bettini
|
||||
http://www.lorenzobettini.it
|
||||
http://www.gnu.org/software/src-highlite -->
|
||||
<pre><tt><span style="font-weight: bold"><span style="color: #0000FF">class</span></span> Order <span style="color: #990000"><</span> ActiveRecord<span style="color: #990000">::</span>Base
|
||||
before_save <span style="color: #990000">:</span>normalize_card_number<span style="color: #990000">,</span>
|
||||
<span style="color: #990000">:</span><span style="font-weight: bold"><span style="color: #0000FF">if</span></span> <span style="color: #990000">=></span> Proc<span style="color: #990000">.</span>new <span style="color: #FF0000">{</span> <span style="color: #990000">|</span>order<span style="color: #990000">|</span> order<span style="color: #990000">.</span>paid_with_card? <span style="color: #FF0000">}</span>
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">end</span></span></tt></pre></div></div>
|
||||
<h3 id="_multiple_conditions_for_callbacks">10.4. Multiple Conditions for Callbacks</h3>
|
||||
<div class="paragraph"><p>When writing conditional callbacks, it’s possible to mix both <tt>:if</tt> and <tt>:unless</tt> in the same callback declaration.</p></div>
|
||||
<div class="listingblock">
|
||||
<div class="content"><!-- Generator: GNU source-highlight 2.9
|
||||
by Lorenzo Bettini
|
||||
http://www.lorenzobettini.it
|
||||
http://www.gnu.org/software/src-highlite -->
|
||||
<pre><tt><span style="font-weight: bold"><span style="color: #0000FF">class</span></span> Comment <span style="color: #990000"><</span> ActiveRecord<span style="color: #990000">::</span>Base
|
||||
after_create <span style="color: #990000">:</span>send_email_to_author<span style="color: #990000">,</span> <span style="color: #990000">:</span><span style="font-weight: bold"><span style="color: #0000FF">if</span></span> <span style="color: #990000">=></span> <span style="color: #990000">:</span>author_wants_emails?<span style="color: #990000">,</span>
|
||||
<span style="color: #990000">:</span><span style="font-weight: bold"><span style="color: #0000FF">unless</span></span> <span style="color: #990000">=></span> Proc<span style="color: #990000">.</span>new <span style="color: #FF0000">{</span> <span style="color: #990000">|</span>comment<span style="color: #990000">|</span> comment<span style="color: #990000">.</span>post<span style="color: #990000">.</span>ignore_comments? <span style="color: #FF0000">}</span>
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">end</span></span></tt></pre></div></div>
|
||||
</div>
|
||||
<h2 id="_available_callbacks">11. Available callbacks</h2>
|
||||
<div class="sectionbody">
|
||||
<div class="paragraph"><p>Here is a list with all the available Active Record callbacks, listed in the same order in which they will get called during the respective operations.</p></div>
|
||||
<h3 id="_callbacks_called_both_when_creating_or_updating_a_record">10.1. Callbacks called both when creating or updating a record.</h3>
|
||||
<h3 id="_callbacks_called_both_when_creating_or_updating_a_record">11.1. Callbacks called both when creating or updating a record.</h3>
|
||||
<div class="ulist"><ul>
|
||||
<li>
|
||||
<p>
|
||||
@@ -1156,7 +1180,7 @@ Readability, since your callback declarations will live at the beggining of your
|
||||
</p>
|
||||
</li>
|
||||
</ul></div>
|
||||
<h3 id="_callbacks_called_only_when_creating_a_new_record">10.2. Callbacks called only when creating a new record.</h3>
|
||||
<h3 id="_callbacks_called_only_when_creating_a_new_record">11.2. Callbacks called only when creating a new record.</h3>
|
||||
<div class="ulist"><ul>
|
||||
<li>
|
||||
<p>
|
||||
@@ -1184,7 +1208,7 @@ Readability, since your callback declarations will live at the beggining of your
|
||||
</p>
|
||||
</li>
|
||||
</ul></div>
|
||||
<h3 id="_callbacks_called_only_when_updating_an_existing_record">10.3. Callbacks called only when updating an existing record.</h3>
|
||||
<h3 id="_callbacks_called_only_when_updating_an_existing_record">11.3. Callbacks called only when updating an existing record.</h3>
|
||||
<div class="ulist"><ul>
|
||||
<li>
|
||||
<p>
|
||||
@@ -1212,7 +1236,7 @@ Readability, since your callback declarations will live at the beggining of your
|
||||
</p>
|
||||
</li>
|
||||
</ul></div>
|
||||
<h3 id="_callbacks_called_when_removing_a_record_from_the_database">10.4. Callbacks called when removing a record from the database.</h3>
|
||||
<h3 id="_callbacks_called_when_removing_a_record_from_the_database">11.4. Callbacks called when removing a record from the database.</h3>
|
||||
<div class="ulist"><ul>
|
||||
<li>
|
||||
<p>
|
||||
@@ -1231,16 +1255,16 @@ Readability, since your callback declarations will live at the beggining of your
|
||||
</li>
|
||||
</ul></div>
|
||||
<div class="paragraph"><p>The <tt>before_destroy</tt> and <tt>after_destroy</tt> callbacks will only be called if you delete the model using either the <tt>destroy</tt> instance method or one of the <tt>destroy</tt> or <tt>destroy_all</tt> class methods of your Active Record class. If you use <tt>delete</tt> or <tt>delete_all</tt> no callback operations will run, since Active Record will not instantiate any objects, accessing the records to be deleted directly in the database.</p></div>
|
||||
<h3 id="_the_tt_after_initialize_tt_and_tt_after_find_tt_callbacks">10.5. The <tt>after_initialize</tt> and <tt>after_find</tt> callbacks</h3>
|
||||
<h3 id="_the_tt_after_initialize_tt_and_tt_after_find_tt_callbacks">11.5. The <tt>after_initialize</tt> and <tt>after_find</tt> callbacks</h3>
|
||||
<div class="paragraph"><p>The <tt>after_initialize</tt> callback will be called whenever an Active Record object is instantiated, either by direcly using <tt>new</tt> or when a record is loaded from the database. It can be useful to avoid the need to directly override your Active Record <tt>initialize</tt> method.</p></div>
|
||||
<div class="paragraph"><p>The <tt>after_find</tt> callback will be called whenever Active Record loads a record from the database. When used together with <tt>after_initialize</tt> it will run first, since Active Record will first read the record from the database and them create the model object that will hold it.</p></div>
|
||||
<div class="paragraph"><p>The <tt>after_initialize</tt> and <tt>after_find</tt> callbacks are a bit different from the others, since the only way to register those callbacks is by defining them as methods. If you try to register <tt>after_initialize</tt> or <tt>after_find</tt> using macro-style class methods, they will just be ignored. This behaviour is due to performance reasons, since <tt>after_initialize</tt> and <tt>after_find</tt> will both be called for each record found in the database, significantly slowing down the queries.</p></div>
|
||||
</div>
|
||||
<h2 id="_halting_execution">11. Halting Execution</h2>
|
||||
<h2 id="_halting_execution">12. Halting Execution</h2>
|
||||
<div class="sectionbody">
|
||||
<div class="paragraph"><p>As you start registering new callbacks for your models, they will be queued for execution. This queue will include all your model’s validations, the registered callbacks and the database operation to be executed. However, if at any moment one of the <tt>before_create</tt>, <tt>before_save</tt>, <tt>before_update</tt> or <tt>before_destroy</tt> callback methods returns a boolean <tt>false</tt> (not <tt>nil</tt>) value, this execution chain will be halted and the desired operation will not complete: your model will not get persisted in the database, or your records will not get deleted and so on.</p></div>
|
||||
</div>
|
||||
<h2 id="_callback_classes">12. Callback classes</h2>
|
||||
<h2 id="_callback_classes">13. Callback classes</h2>
|
||||
<div class="sectionbody">
|
||||
<div class="paragraph"><p>Sometimes the callback methods that you’ll write will be useful enough to be reused at other models. Active Record makes it possible to create classes that encapsulate the callback methods, so it becomes very easy to reuse them.</p></div>
|
||||
<div class="paragraph"><p>Here’s an example where we create a class with a after_destroy callback for a PictureFile model.</p></div>
|
||||
@@ -1285,7 +1309,7 @@ http://www.gnu.org/software/src-highlite -->
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">end</span></span></tt></pre></div></div>
|
||||
<div class="paragraph"><p>You can declare as many callbacks as you want inside your callback classes.</p></div>
|
||||
</div>
|
||||
<h2 id="_observers">13. Observers</h2>
|
||||
<h2 id="_observers">14. Observers</h2>
|
||||
<div class="sectionbody">
|
||||
<div class="paragraph"><p>Active Record callbacks are a powerful feature, but they can pollute your model implementation with code that’s not directly related to the model’s purpose. In object-oriented software, it’s always a good idea to design your classes with a single responsibility in the whole system. For example, it wouldn’t make much sense to have a <tt>User</tt> model with a method that writes data about a login attempt to a log file. Whenever you’re using callbacks to write code that’s not directly related to your model class purposes, it may be a good moment to create an Observer.</p></div>
|
||||
<div class="paragraph"><p>An Active Record Observer is an object that links itself to a model and registers its methods for callbacks. Your model’s implementation remains clean, while you can reuse the code in the Observer to add behaviour to more than one model class. OK, you may say that we can also do that using callback classes, but it would still force us to add code to our model’s implementation.</p></div>
|
||||
@@ -1311,7 +1335,7 @@ http://www.gnu.org/software/src-highlite -->
|
||||
<pre><tt><span style="font-weight: bold"><span style="color: #0000FF">class</span></span> Auditor <span style="color: #990000"><</span> ActiveRecord<span style="color: #990000">::</span>Observer
|
||||
observe User<span style="color: #990000">,</span> Registration<span style="color: #990000">,</span> Invoice
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">end</span></span></tt></pre></div></div>
|
||||
<h3 id="_registering_observers">13.1. Registering observers</h3>
|
||||
<h3 id="_registering_observers">14.1. Registering observers</h3>
|
||||
<div class="paragraph"><p>If you paid attention, you may be wondering where Active Record Observers are referenced in our applications, so they get instantiated and begin to interact with our models. For observers to work we need to register them somewhere. The usual place to do that is in our application’s <strong>config/environment.rb</strong> file. In this file there is a commented-out line where we can define the observers that our application should load at start-up.</p></div>
|
||||
<div class="listingblock">
|
||||
<div class="content"><!-- Generator: GNU source-highlight 2.9
|
||||
@@ -1322,10 +1346,10 @@ http://www.gnu.org/software/src-highlite -->
|
||||
config<span style="color: #990000">.</span>active_record<span style="color: #990000">.</span>observers <span style="color: #990000">=</span> <span style="color: #990000">:</span>registration_observer<span style="color: #990000">,</span> <span style="color: #990000">:</span>auditor</tt></pre></div></div>
|
||||
<div class="paragraph"><p>You can uncomment the line with <tt>config.active_record.observers</tt> and change the symbols for the name of the observers that should be registered.</p></div>
|
||||
<div class="paragraph"><p>It’s also possible to register callbacks in any of the files living at <strong>config/environments/</strong>, if you want an observer to work only in a specific environment. There is not a <tt>config.active_record.observers</tt> line at any of those files, but you can simply add it.</p></div>
|
||||
<h3 id="_where_to_put_the_observers_source_files">13.2. Where to put the observers' source files</h3>
|
||||
<h3 id="_where_to_put_the_observers_source_files">14.2. Where to put the observers' source files</h3>
|
||||
<div class="paragraph"><p>By convention, you should always save your observers' source files inside <strong>app/models</strong>.</p></div>
|
||||
</div>
|
||||
<h2 id="_changelog">14. Changelog</h2>
|
||||
<h2 id="_changelog">15. Changelog</h2>
|
||||
<div class="sectionbody">
|
||||
<div class="paragraph"><p><a href="http://rails.lighthouseapp.com/projects/16213/tickets/26-active-record-validations-and-callbacks">http://rails.lighthouseapp.com/projects/16213/tickets/26-active-record-validations-and-callbacks</a></p></div>
|
||||
</div>
|
||||
|
||||
@@ -348,7 +348,7 @@ of your code.</p></div>
|
||||
</div></div>
|
||||
<div class="sidebarblock">
|
||||
<div class="sidebar-content">
|
||||
<div class="sidebar-title"><a href="performance_testing.html">Performance testing Rails Applications</a></div>
|
||||
<div class="sidebar-title"><a href="performance_testing.html">Performance Testing Rails Applications</a></div>
|
||||
<div class="admonitionblock">
|
||||
<table><tr>
|
||||
<td class="icon">
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<title>Performance testing Rails Applications</title>
|
||||
<title>Performance Testing Rails Applications</title>
|
||||
<!--[if lt IE 8]>
|
||||
<script src="http://ie7-js.googlecode.com/svn/version/2.0(beta3)/IE8.js" type="text/javascript"></script>
|
||||
<![endif]-->
|
||||
@@ -198,33 +198,63 @@ ul#navMain {
|
||||
<div id="sidebar">
|
||||
<h2>Chapters</h2>
|
||||
<ol>
|
||||
<li>
|
||||
<a href="#_using_and_understanding_the_log_files">Using and understanding the log files</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#_helper_methods">Helper methods</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#_performance_test_cases">Performance Test Cases</a>
|
||||
<ul>
|
||||
|
||||
<li><a href="#_generating_performance_tests">Generating performance tests</a></li>
|
||||
|
||||
<li><a href="#_examples">Examples</a></li>
|
||||
|
||||
<li><a href="#_modes">Modes</a></li>
|
||||
|
||||
<li><a href="#_metrics">Metrics</a></li>
|
||||
|
||||
<li><a href="#_understanding_the_output">Understanding the output</a></li>
|
||||
|
||||
<li><a href="#_preparing_ruby_and_ruby_prof">Preparing Ruby and Ruby-prof</a></li>
|
||||
<li><a href="#_tuning_test_runs">Tuning Test Runs</a></li>
|
||||
|
||||
<li><a href="#_generating_performance_test">Generating performance test</a></li>
|
||||
<li><a href="#gc">Installing GC-Patched Ruby</a></li>
|
||||
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#_other_profiling_tools">Other Profiling Tools</a>
|
||||
<a href="#_command_line_tools">Command Line Tools</a>
|
||||
<ul>
|
||||
|
||||
<li><a href="#_benchmarker">benchmarker</a></li>
|
||||
|
||||
<li><a href="#_profiler">profiler</a></li>
|
||||
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#_commercial_products_dedicated_to_rails_perfomance">Commercial products dedicated to Rails Perfomance</a>
|
||||
<a href="#_helper_methods">Helper methods</a>
|
||||
<ul>
|
||||
|
||||
<li><a href="#_model">Model</a></li>
|
||||
|
||||
<li><a href="#_controller">Controller</a></li>
|
||||
|
||||
<li><a href="#_view">View</a></li>
|
||||
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#_request_logging">Request Logging</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#_useful_profiling_tools">Useful Profiling Tools</a>
|
||||
<ul>
|
||||
|
||||
<li><a href="#_rails_plugins_and_gems">Rails Plugins and Gems</a></li>
|
||||
|
||||
<li><a href="#_external">External</a></li>
|
||||
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#_commercial_products">Commercial Products</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#_changelog">Changelog</a>
|
||||
@@ -233,10 +263,10 @@ ul#navMain {
|
||||
</div>
|
||||
|
||||
<div id="content">
|
||||
<h1>Performance testing Rails Applications</h1>
|
||||
<h1>Performance Testing Rails Applications</h1>
|
||||
<div id="preamble">
|
||||
<div class="sectionbody">
|
||||
<div class="paragraph"><p>This guide covers the benchmarking and profiling tactics/tools of Rails and Ruby in general. By referring to this guide, you will be able to:</p></div>
|
||||
<div class="paragraph"><p>This guide covers the various ways of performance testing a Ruby on Rails application. By referring to this guide, you will be able to:</p></div>
|
||||
<div class="ulist"><ul>
|
||||
<li>
|
||||
<p>
|
||||
@@ -245,17 +275,17 @@ Understand the various types of benchmarking and profiling metrics
|
||||
</li>
|
||||
<li>
|
||||
<p>
|
||||
Generate performance/benchmarking tests
|
||||
Generate performance and benchmarking tests
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>
|
||||
Use GC patched Ruby binary to measure memory usage and object allocation
|
||||
Use a GC-patched Ruby binary to measure memory usage and object allocation
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>
|
||||
Understand the information provided by Rails inside the log files
|
||||
Understand the benchmarking information provided by Rails inside the log files
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
@@ -264,77 +294,13 @@ Learn about various tools facilitating benchmarking and profiling
|
||||
</p>
|
||||
</li>
|
||||
</ul></div>
|
||||
<div class="paragraph"><p>Performance testing is an integral part of the development cycle. It is very important that you don’t make your end users wait for too long before the page is completely loaded. Ensuring a plesant browsing experience to the end users and cutting cost of unnecessary hardwares is important for any web application.</p></div>
|
||||
<div class="paragraph"><p>Performance testing is an integral part of the development cycle. It is very important that you don’t make your end users wait for too long before the page is completely loaded. Ensuring a pleasant browsing experience for end users and cutting the cost of unnecessary hardware is important for any non-trivial web application.</p></div>
|
||||
</div>
|
||||
</div>
|
||||
<h2 id="_using_and_understanding_the_log_files">1. Using and understanding the log files</h2>
|
||||
<h2 id="_performance_test_cases">1. Performance Test Cases</h2>
|
||||
<div class="sectionbody">
|
||||
<div class="paragraph"><p>Rails logs files containt basic but very useful information about the time taken to serve every request. A typical log entry looks something like :</p></div>
|
||||
<div class="listingblock">
|
||||
<div class="content"><!-- Generator: GNU source-highlight 2.9
|
||||
by Lorenzo Bettini
|
||||
http://www.lorenzobettini.it
|
||||
http://www.gnu.org/software/src-highlite -->
|
||||
<pre><tt>Processing ItemsController<span style="font-style: italic"><span style="color: #9A1900">#index (for 127.0.0.1 at 2009-01-08 03:06:39) [GET]</span></span>
|
||||
Rendering template within layouts<span style="color: #990000">/</span>items
|
||||
Rendering items<span style="color: #990000">/</span>index
|
||||
Completed <span style="font-weight: bold"><span style="color: #0000FF">in</span></span> 5ms <span style="color: #990000">(</span>View<span style="color: #990000">:</span> <span style="color: #993399">2</span><span style="color: #990000">,</span> DB<span style="color: #990000">:</span> <span style="color: #993399">0</span><span style="color: #990000">)</span> <span style="color: #990000">|</span> <span style="color: #993399">200</span> OK <span style="color: #990000">[</span>http<span style="color: #990000">:</span><span style="color: #FF6600">//0.0.0.0/</span>items<span style="color: #990000">]</span></tt></pre></div></div>
|
||||
<div class="paragraph"><p>For this section, we’re only interested in the last line from that log entry:</p></div>
|
||||
<div class="listingblock">
|
||||
<div class="content"><!-- Generator: GNU source-highlight 2.9
|
||||
by Lorenzo Bettini
|
||||
http://www.lorenzobettini.it
|
||||
http://www.gnu.org/software/src-highlite -->
|
||||
<pre><tt>Completed <span style="font-weight: bold"><span style="color: #0000FF">in</span></span> 5ms <span style="color: #990000">(</span>View<span style="color: #990000">:</span> <span style="color: #993399">2</span><span style="color: #990000">,</span> DB<span style="color: #990000">:</span> <span style="color: #993399">0</span><span style="color: #990000">)</span> <span style="color: #990000">|</span> <span style="color: #993399">200</span> OK <span style="color: #990000">[</span>http<span style="color: #990000">:</span><span style="color: #FF6600">//0.0.0.0/</span>items<span style="color: #990000">]</span></tt></pre></div></div>
|
||||
<div class="paragraph"><p>This data is fairly straight forward to understand. Rails uses millisecond(ms) as the metric to measures the time taken. The complete request spent 5 ms inside Rails, out of which 2 ms were spent rendering views and none was spent communication with the database. It’s safe to assume that the remaining 3 ms were spent inside the controller.</p></div>
|
||||
</div>
|
||||
<h2 id="_helper_methods">2. Helper methods</h2>
|
||||
<div class="sectionbody">
|
||||
<div class="paragraph"><p>Rails provides various helper methods inside Active Record, Action Controller and Action View to measure the time taken by a specific code. The method is called <tt>benchmark()</tt> in all three components.</p></div>
|
||||
<div class="listingblock">
|
||||
<div class="content"><!-- Generator: GNU source-highlight 2.9
|
||||
by Lorenzo Bettini
|
||||
http://www.lorenzobettini.it
|
||||
http://www.gnu.org/software/src-highlite -->
|
||||
<pre><tt>Project<span style="color: #990000">.</span>benchmark<span style="color: #990000">(</span><span style="color: #FF0000">"Creating project"</span><span style="color: #990000">)</span> <span style="font-weight: bold"><span style="color: #0000FF">do</span></span>
|
||||
project <span style="color: #990000">=</span> Project<span style="color: #990000">.</span>create<span style="color: #990000">(</span><span style="color: #FF0000">"name"</span> <span style="color: #990000">=></span> <span style="color: #FF0000">"stuff"</span><span style="color: #990000">)</span>
|
||||
project<span style="color: #990000">.</span>create_manager<span style="color: #990000">(</span><span style="color: #FF0000">"name"</span> <span style="color: #990000">=></span> <span style="color: #FF0000">"David"</span><span style="color: #990000">)</span>
|
||||
project<span style="color: #990000">.</span>milestones <span style="color: #990000"><<</span> Milestone<span style="color: #990000">.</span>find<span style="color: #990000">(:</span>all<span style="color: #990000">)</span>
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">end</span></span></tt></pre></div></div>
|
||||
<div class="paragraph"><p>The above code benchmarks the multiple statments enclosed inside <tt>Project.benchmark("Creating project") do..end</tt> block and prints the results inside log files. The statement inside log files will look like:</p></div>
|
||||
<div class="listingblock">
|
||||
<div class="content"><!-- Generator: GNU source-highlight 2.9
|
||||
by Lorenzo Bettini
|
||||
http://www.lorenzobettini.it
|
||||
http://www.gnu.org/software/src-highlite -->
|
||||
<pre><tt>Creating projectem <span style="color: #990000">(</span><span style="color: #993399">185</span><span style="color: #990000">.</span>3ms<span style="color: #990000">)</span></tt></pre></div></div>
|
||||
<div class="paragraph"><p>Please refer to <a href="http://api.rubyonrails.com/classes/ActiveRecord/Base.html#M001336">API docs</a> for optional options to <tt>benchmark()</tt></p></div>
|
||||
<div class="paragraph"><p>Similarly, you could use this helper method inside <a href="http://api.rubyonrails.com/classes/ActionController/Benchmarking/ClassMethods.html#M000715">controllers</a> ( Note that it’s a class method here ):</p></div>
|
||||
<div class="listingblock">
|
||||
<div class="content"><!-- Generator: GNU source-highlight 2.9
|
||||
by Lorenzo Bettini
|
||||
http://www.lorenzobettini.it
|
||||
http://www.gnu.org/software/src-highlite -->
|
||||
<pre><tt><span style="font-weight: bold"><span style="color: #0000FF">def</span></span> process_projects
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">self</span></span><span style="color: #990000">.</span><span style="font-weight: bold"><span style="color: #0000FF">class</span></span><span style="color: #990000">.</span>benchmark<span style="color: #990000">(</span><span style="color: #FF0000">"Processing projects"</span><span style="color: #990000">)</span> <span style="font-weight: bold"><span style="color: #0000FF">do</span></span>
|
||||
Project<span style="color: #990000">.</span>process<span style="color: #990000">(</span>params<span style="color: #990000">[:</span>project_ids<span style="color: #990000">])</span>
|
||||
Project<span style="color: #990000">.</span>update_cached_projects
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">end</span></span>
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">end</span></span></tt></pre></div></div>
|
||||
<div class="paragraph"><p>and <a href="http://api.rubyonrails.com/classes/ActionController/Benchmarking/ClassMethods.html#M000715">views</a>:</p></div>
|
||||
<div class="listingblock">
|
||||
<div class="content"><!-- Generator: GNU source-highlight 2.9
|
||||
by Lorenzo Bettini
|
||||
http://www.lorenzobettini.it
|
||||
http://www.gnu.org/software/src-highlite -->
|
||||
<pre><tt><span style="color: #FF0000"><% benchmark("Showing projects partial") do %></span>
|
||||
<span style="color: #FF0000"><%= render :partial =></span> <span style="color: #009900">@projects</span> <span style="color: #990000">%></span>
|
||||
<span style="color: #FF0000"><% end %></span></tt></pre></div></div>
|
||||
</div>
|
||||
<h2 id="_performance_test_cases">3. Performance Test Cases</h2>
|
||||
<div class="sectionbody">
|
||||
<div class="paragraph"><p>Rails provides a very easy way to write performance test cases, which look just like the regular integration tests. Performance tests run a code profiler on your test methods. Profiling output for combinations of each test method, measurement, and output format are written to your <tt>tmp/performance</tt> directory. By default, process_time is measured and both flat and graph_html output formats are written, so you’ll have two output files per test method.</p></div>
|
||||
<div class="paragraph"><p>If you have a look at <tt>test/performance/browsing_test.rb</tt> in a newly created Rails application:</p></div>
|
||||
<div class="paragraph"><p>Rails performance tests are a special type of integration tests, designed for benchmarking and profiling the test code. With performance tests, you can determine where your application’s memory or speed problems are coming from, and get a more in-depth picture of those problems.</p></div>
|
||||
<div class="paragraph"><p>In a freshly generated Rails application, <tt>test/performance/browsing_test.rb</tt> contains an example of a performance test:</p></div>
|
||||
<div class="listingblock">
|
||||
<div class="content"><!-- Generator: GNU source-highlight 2.9
|
||||
by Lorenzo Bettini
|
||||
@@ -349,48 +315,161 @@ http://www.gnu.org/software/src-highlite -->
|
||||
get <span style="color: #FF0000">'/'</span>
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">end</span></span>
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">end</span></span></tt></pre></div></div>
|
||||
<div class="paragraph"><p>This is an automatically generated example performance test file, for testing performance of homepage(<em>/</em>) of the application.</p></div>
|
||||
<h3 id="_modes">3.1. Modes</h3>
|
||||
<div class="paragraph"><p>Performance test cases can be run in two modes : Benchmarking and Profling.</p></div>
|
||||
<h4 id="_benchmarking">3.1.1. Benchmarking</h4>
|
||||
<div class="paragraph"><p>Benchmarking helps you find out how fast are your test cases. Each Test case is run <tt>4 times</tt> in this mode. To run performance tests in benchmarking mode:</p></div>
|
||||
<div class="paragraph"><p>This example is a simple performance test case for profiling a GET request to the application’s homepage.</p></div>
|
||||
<h3 id="_generating_performance_tests">1.1. Generating performance tests</h3>
|
||||
<div class="paragraph"><p>Rails provides a generator called <tt>performance_test</tt> for creating new performance tests:</p></div>
|
||||
<div class="listingblock">
|
||||
<div class="content"><!-- Generator: GNU source-highlight 2.9
|
||||
by Lorenzo Bettini
|
||||
http://www.lorenzobettini.it
|
||||
http://www.gnu.org/software/src-highlite -->
|
||||
<pre><tt>script/generate performance_test homepage</tt></pre></div></div>
|
||||
<div class="paragraph"><p>This generates <tt>homepage_test.rb</tt> in the <tt>test/performance</tt> directory:</p></div>
|
||||
<div class="listingblock">
|
||||
<div class="content"><!-- Generator: GNU source-highlight 2.9
|
||||
by Lorenzo Bettini
|
||||
http://www.lorenzobettini.it
|
||||
http://www.gnu.org/software/src-highlite -->
|
||||
<pre><tt><span style="font-weight: bold"><span style="color: #000080">require</span></span> <span style="color: #FF0000">'test_helper'</span>
|
||||
<span style="font-weight: bold"><span style="color: #000080">require</span></span> <span style="color: #FF0000">'performance_test_help'</span>
|
||||
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">class</span></span> HomepageTest <span style="color: #990000"><</span> ActionController<span style="color: #990000">::</span>PerformanceTest
|
||||
<span style="font-style: italic"><span style="color: #9A1900"># Replace this with your real tests.</span></span>
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">def</span></span> test_homepage
|
||||
get <span style="color: #FF0000">'/'</span>
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">end</span></span>
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">end</span></span></tt></pre></div></div>
|
||||
<h3 id="_examples">1.2. Examples</h3>
|
||||
<div class="paragraph"><p>Let’s assume your application has the following controller and model:</p></div>
|
||||
<div class="listingblock">
|
||||
<div class="content"><!-- Generator: GNU source-highlight 2.9
|
||||
by Lorenzo Bettini
|
||||
http://www.lorenzobettini.it
|
||||
http://www.gnu.org/software/src-highlite -->
|
||||
<pre><tt><span style="font-style: italic"><span style="color: #9A1900"># routes.rb</span></span>
|
||||
map<span style="color: #990000">.</span>root <span style="color: #990000">:</span>controller <span style="color: #990000">=></span> <span style="color: #FF0000">'home'</span>
|
||||
map<span style="color: #990000">.</span>resources <span style="color: #990000">:</span>posts
|
||||
|
||||
<span style="font-style: italic"><span style="color: #9A1900"># home_controller.rb</span></span>
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">class</span></span> HomeController <span style="color: #990000"><</span> ApplicationController
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">def</span></span> dashboard
|
||||
<span style="color: #009900">@users</span> <span style="color: #990000">=</span> User<span style="color: #990000">.</span>last_ten<span style="color: #990000">(:</span><span style="font-weight: bold"><span style="color: #0000FF">include</span></span> <span style="color: #990000">=></span> <span style="color: #990000">:</span>avatars<span style="color: #990000">)</span>
|
||||
<span style="color: #009900">@posts</span> <span style="color: #990000">=</span> Post<span style="color: #990000">.</span>all_today
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">end</span></span>
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">end</span></span>
|
||||
|
||||
<span style="font-style: italic"><span style="color: #9A1900"># posts_controller.rb</span></span>
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">class</span></span> PostsController <span style="color: #990000"><</span> ApplicationController
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">def</span></span> create
|
||||
<span style="color: #009900">@post</span> <span style="color: #990000">=</span> Post<span style="color: #990000">.</span>create<span style="color: #990000">(</span>params<span style="color: #990000">[:</span>post<span style="color: #990000">])</span>
|
||||
redirect_to<span style="color: #990000">(</span><span style="color: #009900">@post</span><span style="color: #990000">)</span>
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">end</span></span>
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">end</span></span>
|
||||
|
||||
<span style="font-style: italic"><span style="color: #9A1900"># post.rb</span></span>
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">class</span></span> Post <span style="color: #990000"><</span> ActiveRecord<span style="color: #990000">::</span>Base
|
||||
before_save <span style="color: #990000">:</span>recalculate_costly_stats
|
||||
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">def</span></span> slow_method
|
||||
<span style="font-style: italic"><span style="color: #9A1900"># I fire gallzilion queries sleeping all around</span></span>
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">end</span></span>
|
||||
|
||||
private
|
||||
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">def</span></span> recalculate_costly_stats
|
||||
<span style="font-style: italic"><span style="color: #9A1900"># CPU heavy calculations</span></span>
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">end</span></span>
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">end</span></span></tt></pre></div></div>
|
||||
<h4 id="_controller_example">1.2.1. Controller Example</h4>
|
||||
<div class="paragraph"><p>Because performance tests are a special kind of integration test, you can use the <tt>get</tt> and <tt>post</tt> methods in them.</p></div>
|
||||
<div class="paragraph"><p>Here’s the performance test for <tt>HomeController#dashboard</tt> and <tt>PostsController#create</tt>:</p></div>
|
||||
<div class="listingblock">
|
||||
<div class="content"><!-- Generator: GNU source-highlight 2.9
|
||||
by Lorenzo Bettini
|
||||
http://www.lorenzobettini.it
|
||||
http://www.gnu.org/software/src-highlite -->
|
||||
<pre><tt><span style="font-weight: bold"><span style="color: #000080">require</span></span> <span style="color: #FF0000">'test_helper'</span>
|
||||
<span style="font-weight: bold"><span style="color: #000080">require</span></span> <span style="color: #FF0000">'performance_test_help'</span>
|
||||
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">class</span></span> PostPerformanceTest <span style="color: #990000"><</span> ActionController<span style="color: #990000">::</span>PerformanceTest
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">def</span></span> setup
|
||||
<span style="font-style: italic"><span style="color: #9A1900"># Application requires logged-in user</span></span>
|
||||
login_as<span style="color: #990000">(:</span>lifo<span style="color: #990000">)</span>
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">end</span></span>
|
||||
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">def</span></span> test_homepage
|
||||
get <span style="color: #FF0000">'/dashboard'</span>
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">end</span></span>
|
||||
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">def</span></span> test_creating_new_post
|
||||
post <span style="color: #FF0000">'/posts'</span><span style="color: #990000">,</span> <span style="color: #990000">:</span>post <span style="color: #990000">=></span> <span style="color: #FF0000">{</span> <span style="color: #990000">:</span>body <span style="color: #990000">=></span> <span style="color: #FF0000">'lifo is fooling you'</span> <span style="color: #FF0000">}</span>
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">end</span></span>
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">end</span></span></tt></pre></div></div>
|
||||
<div class="paragraph"><p>You can find more details about the <tt>get</tt> and <tt>post</tt> methods in the <a href="../testing_rails_applications.html#mgunderloy">Testing Rails Applications</a> guide.</p></div>
|
||||
<h4 id="_model_example">1.2.2. Model Example</h4>
|
||||
<div class="paragraph"><p>Even though the performance tests are integration tests and hence closer to the request/response cycle by nature, you can still performance test pure model code.</p></div>
|
||||
<div class="paragraph"><p>Performance test for <tt>Post</tt> model:</p></div>
|
||||
<div class="listingblock">
|
||||
<div class="content"><!-- Generator: GNU source-highlight 2.9
|
||||
by Lorenzo Bettini
|
||||
http://www.lorenzobettini.it
|
||||
http://www.gnu.org/software/src-highlite -->
|
||||
<pre><tt><span style="font-weight: bold"><span style="color: #000080">require</span></span> <span style="color: #FF0000">'test_helper'</span>
|
||||
<span style="font-weight: bold"><span style="color: #000080">require</span></span> <span style="color: #FF0000">'performance_test_help'</span>
|
||||
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">class</span></span> PostModelTest <span style="color: #990000"><</span> ActionController<span style="color: #990000">::</span>PerformanceTest
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">def</span></span> test_creation
|
||||
Post<span style="color: #990000">.</span>create <span style="color: #990000">:</span>body <span style="color: #990000">=></span> <span style="color: #FF0000">'still fooling you'</span><span style="color: #990000">,</span> <span style="color: #990000">:</span>cost <span style="color: #990000">=></span> <span style="color: #FF0000">'100'</span>
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">end</span></span>
|
||||
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">def</span></span> test_slow_method
|
||||
<span style="font-style: italic"><span style="color: #9A1900"># Using posts(:awesome) fixture</span></span>
|
||||
posts<span style="color: #990000">(:</span>awesome<span style="color: #990000">).</span>slow_method
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">end</span></span>
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">end</span></span></tt></pre></div></div>
|
||||
<h3 id="_modes">1.3. Modes</h3>
|
||||
<div class="paragraph"><p>Performance tests can be run in two modes : Benchmarking and Profiling.</p></div>
|
||||
<h4 id="_benchmarking">1.3.1. Benchmarking</h4>
|
||||
<div class="paragraph"><p>Benchmarking helps find out how fast each performance test runs. Each test case is run <tt>4 times</tt> in benchmarking mode.</p></div>
|
||||
<div class="paragraph"><p>To run performance tests in benchmarking mode:</p></div>
|
||||
<div class="listingblock">
|
||||
<div class="content"><!-- Generator: GNU source-highlight 2.9
|
||||
by Lorenzo Bettini
|
||||
http://www.lorenzobettini.it
|
||||
http://www.gnu.org/software/src-highlite -->
|
||||
<pre><tt>$ rake <span style="font-weight: bold"><span style="color: #0000FF">test</span></span><span style="color: #990000">:</span>benchmark</tt></pre></div></div>
|
||||
<h4 id="_profiling">3.1.2. Profiling</h4>
|
||||
<div class="paragraph"><p>Profiling helps introspect into your test cases and figure out which are the slow parts. Each Test case is run <tt>1 time</tt> in this mode. To run performance tests in profiling mode:</p></div>
|
||||
<h4 id="_profiling">1.3.2. Profiling</h4>
|
||||
<div class="paragraph"><p>Profiling helps you see the details of a performance test and provide an in-depth picture of the slow and memory hungry parts. Each test case is run <tt>1 time</tt> in profiling mode.</p></div>
|
||||
<div class="paragraph"><p>To run performance tests in profiling mode:</p></div>
|
||||
<div class="listingblock">
|
||||
<div class="content"><!-- Generator: GNU source-highlight 2.9
|
||||
by Lorenzo Bettini
|
||||
http://www.lorenzobettini.it
|
||||
http://www.gnu.org/software/src-highlite -->
|
||||
<pre><tt>$ rake <span style="font-weight: bold"><span style="color: #0000FF">test</span></span><span style="color: #990000">:</span>profile</tt></pre></div></div>
|
||||
<h3 id="_metrics">3.2. Metrics</h3>
|
||||
<div class="paragraph"><p>Benchmarking and profiling run performance test cases in various modes to help precisely figure out the where the problem lies.</p></div>
|
||||
<h4 id="_wall_time">3.2.1. Wall Time</h4>
|
||||
<div class="paragraph"><p>Measures the real world time elapsed during the test run. It is affected by any other processes concurrently running on the system.</p></div>
|
||||
<h3 id="_metrics">1.4. Metrics</h3>
|
||||
<div class="paragraph"><p>Benchmarking and profiling run performance tests in various modes described below.</p></div>
|
||||
<h4 id="_wall_time">1.4.1. Wall Time</h4>
|
||||
<div class="paragraph"><p>Wall time measures the real world time elapsed during the test run. It is affected by any other processes concurrently running on the system.</p></div>
|
||||
<div class="paragraph"><p>Mode : Benchmarking</p></div>
|
||||
<h4 id="_process_time">3.2.2. Process Time</h4>
|
||||
<div class="paragraph"><p>Measures the time taken by the process. It is unaffected by any other processes running concurrently on the same system. Hence, process time is likely to be constant for any given performance test, irrespective of the machine load.</p></div>
|
||||
<h4 id="_process_time">1.4.2. Process Time</h4>
|
||||
<div class="paragraph"><p>Process time measures the time taken by the process. It is unaffected by any other processes running concurrently on the same system. Hence, process time is likely to be constant for any given performance test, irrespective of the machine load.</p></div>
|
||||
<div class="paragraph"><p>Mode : Profiling</p></div>
|
||||
<h4 id="_memory">3.2.3. Memory</h4>
|
||||
<div class="paragraph"><p>Measures the amount of memory used for the performance test case.</p></div>
|
||||
<div class="paragraph"><p>Mode : Benchmarking, Profiling [Requires specially compiled Ruby]</p></div>
|
||||
<h4 id="_objects">3.2.4. Objects</h4>
|
||||
<div class="paragraph"><p>Measures the number of objects allocated for the performance test case.</p></div>
|
||||
<div class="paragraph"><p>Mode : Benchmarking, Profiling [Requires specially compiled Ruby]</p></div>
|
||||
<h4 id="_gc_runs">3.2.5. GC Runs</h4>
|
||||
<div class="paragraph"><p>Measures the number of times GC was invoked for the performance test case.</p></div>
|
||||
<div class="paragraph"><p>Mode : Benchmarking [Requires specially compiled Ruby]</p></div>
|
||||
<h4 id="_gc_time">3.2.6. GC Time</h4>
|
||||
<div class="paragraph"><p>Measures the amount of time spent in GC for the performance test case.</p></div>
|
||||
<div class="paragraph"><p>Mode : Benchmarking [Requires specially compiled Ruby]</p></div>
|
||||
<h3 id="_understanding_the_output">3.3. Understanding the output</h3>
|
||||
<div class="paragraph"><p>Performance tests generate different outputs inside <tt>tmp/performance</tt> directory based on the mode it is run in and the metric.</p></div>
|
||||
<h4 id="_benchmarking_2">3.3.1. Benchmarking</h4>
|
||||
<h4 id="_memory">1.4.3. Memory</h4>
|
||||
<div class="paragraph"><p>Memory measures the amount of memory used for the performance test case.</p></div>
|
||||
<div class="paragraph"><p>Mode : Benchmarking, Profiling [<a href="#gc">Requires GC-Patched Ruby</a>]</p></div>
|
||||
<h4 id="_objects">1.4.4. Objects</h4>
|
||||
<div class="paragraph"><p>Objects measures the number of objects allocated for the performance test case.</p></div>
|
||||
<div class="paragraph"><p>Mode : Benchmarking, Profiling [<a href="#gc">Requires GC-Patched Ruby</a>]</p></div>
|
||||
<h4 id="_gc_runs">1.4.5. GC Runs</h4>
|
||||
<div class="paragraph"><p>GC Runs measures the number of times GC was invoked for the performance test case.</p></div>
|
||||
<div class="paragraph"><p>Mode : Benchmarking [<a href="#gc">Requires GC-Patched Ruby</a>]</p></div>
|
||||
<h4 id="_gc_time">1.4.6. GC Time</h4>
|
||||
<div class="paragraph"><p>GC Time measures the amount of time spent in GC for the performance test case.</p></div>
|
||||
<div class="paragraph"><p>Mode : Benchmarking [<a href="#gc">Requires GC-Patched Ruby</a>]</p></div>
|
||||
<h3 id="_understanding_the_output">1.5. Understanding the output</h3>
|
||||
<div class="paragraph"><p>Performance tests generate different outputs inside <tt>tmp/performance</tt> directory depending on their mode and metric.</p></div>
|
||||
<h4 id="_benchmarking_2">1.5.1. Benchmarking</h4>
|
||||
<div class="paragraph"><p>In benchmarking mode, performance tests generate two types of outputs :</p></div>
|
||||
<h5 id="_command_line">Command line</h5>
|
||||
<div class="paragraph"><p>This is the primary form of output in benchmarking mode. Example :</p></div>
|
||||
@@ -406,7 +485,7 @@ http://www.gnu.org/software/src-highlite -->
|
||||
gc_runs<span style="color: #990000">:</span> <span style="color: #993399">0</span>
|
||||
gc_time<span style="color: #990000">:</span> <span style="color: #993399">19</span> ms</tt></pre></div></div>
|
||||
<h5 id="_csv_files">CSV files</h5>
|
||||
<div class="paragraph"><p>Performance tests results are also appended to <tt>.csv</tt> files inside <tt>tmp/performance/<Class>#<test>_<metric>.csv</tt> file. For example, running the default <tt>BrowsingTest#test_homepage</tt> will generate following five files :</p></div>
|
||||
<div class="paragraph"><p>Performance test results are also appended to <tt>.csv</tt> files inside <tt>tmp/performance</tt>. For example, running the default <tt>BrowsingTest#test_homepage</tt> will generate following five files :</p></div>
|
||||
<div class="ulist"><ul>
|
||||
<li>
|
||||
<p>
|
||||
@@ -434,8 +513,8 @@ BrowsingTest#test_homepage_wall_time.csv
|
||||
</p>
|
||||
</li>
|
||||
</ul></div>
|
||||
<div class="paragraph"><p>As the results are appended to these files each time the performance tests are run in benchmarking mode, it enables you gather data over a sustainable period of time which can be very helpful with various performance analysis.</p></div>
|
||||
<div class="paragraph"><p>Sample output of +BrowsingTest#test_homepage_wall_time.csv + :</p></div>
|
||||
<div class="paragraph"><p>As the results are appended to these files each time the performance tests are run in benchmarking mode, you can collect data over a period of time. This can be very helpful in analyzing the effects of code changes.</p></div>
|
||||
<div class="paragraph"><p>Sample output of <tt>BrowsingTest#test_homepage_wall_time.csv</tt>:</p></div>
|
||||
<div class="listingblock">
|
||||
<div class="content"><!-- Generator: GNU source-highlight 2.9
|
||||
by Lorenzo Bettini
|
||||
@@ -452,9 +531,10 @@ http://www.gnu.org/software/src-highlite -->
|
||||
<span style="color: #993399">0.00740450000000004</span><span style="color: #990000">,</span><span style="color: #993399">2009</span>-<span style="color: #993399">01</span>-09T03<span style="color: #990000">:</span><span style="color: #993399">54</span><span style="color: #990000">:</span>47Z<span style="color: #990000">,,</span><span style="color: #993399">2.3</span><span style="color: #990000">.</span><span style="color: #993399">0</span><span style="color: #990000">.</span>master<span style="color: #990000">.</span><span style="color: #993399">859e150</span><span style="color: #990000">,</span>ruby-<span style="color: #993399">1.8</span><span style="color: #990000">.</span><span style="color: #993399">6.110</span><span style="color: #990000">,</span>i686-darwin<span style="color: #993399">9.0</span><span style="color: #990000">.</span><span style="color: #993399">0</span>
|
||||
<span style="color: #993399">0.00603150000000008</span><span style="color: #990000">,</span><span style="color: #993399">2009</span>-<span style="color: #993399">01</span>-09T03<span style="color: #990000">:</span><span style="color: #993399">54</span><span style="color: #990000">:</span>57Z<span style="color: #990000">,,</span><span style="color: #993399">2.3</span><span style="color: #990000">.</span><span style="color: #993399">0</span><span style="color: #990000">.</span>master<span style="color: #990000">.</span><span style="color: #993399">859e150</span><span style="color: #990000">,</span>ruby-<span style="color: #993399">1.8</span><span style="color: #990000">.</span><span style="color: #993399">6.111</span><span style="color: #990000">,</span>i686-darwin<span style="color: #993399">9.1</span><span style="color: #990000">.</span><span style="color: #993399">0</span>
|
||||
<span style="color: #993399">0.00771250000000012</span><span style="color: #990000">,</span><span style="color: #993399">2009</span>-<span style="color: #993399">01</span>-09T15<span style="color: #990000">:</span><span style="color: #993399">46</span><span style="color: #990000">:</span>03Z<span style="color: #990000">,,</span><span style="color: #993399">2.3</span><span style="color: #990000">.</span><span style="color: #993399">0</span><span style="color: #990000">.</span>master<span style="color: #990000">.</span><span style="color: #993399">859e150</span><span style="color: #990000">,</span>ruby-<span style="color: #993399">1.8</span><span style="color: #990000">.</span><span style="color: #993399">6.110</span><span style="color: #990000">,</span>i686-darwin<span style="color: #993399">9.0</span><span style="color: #990000">.</span><span style="color: #993399">0</span></tt></pre></div></div>
|
||||
<h4 id="_profiling_2">3.3.2. Profiling</h4>
|
||||
<h4 id="_profiling_2">1.5.2. Profiling</h4>
|
||||
<div class="paragraph"><p>In profiling mode, you can choose from four types of output.</p></div>
|
||||
<h5 id="_command_line_2">Command line</h5>
|
||||
<div class="paragraph"><p>This is the very basic form of output in profiling mode. Example :</p></div>
|
||||
<div class="paragraph"><p>This is a very basic form of output in profiling mode:</p></div>
|
||||
<div class="listingblock">
|
||||
<div class="content"><!-- Generator: GNU source-highlight 2.9
|
||||
by Lorenzo Bettini
|
||||
@@ -465,92 +545,242 @@ http://www.gnu.org/software/src-highlite -->
|
||||
memory<span style="color: #990000">:</span> <span style="color: #993399">832.13</span> KB
|
||||
objects<span style="color: #990000">:</span> <span style="color: #993399">7882</span></tt></pre></div></div>
|
||||
<h5 id="_flat">Flat</h5>
|
||||
<div class="paragraph"><p>Flat output shows the total amount of time spent in each method. <a href="http://ruby-prof.rubyforge.org/files/examples/flat_txt.html">Check ruby prof documentation for a better explaination</a>.</p></div>
|
||||
<div class="paragraph"><p>Flat output shows the total amount of time spent in each method. <a href="http://ruby-prof.rubyforge.org/files/examples/flat_txt.html">Check ruby prof documentation for a better explanation</a>.</p></div>
|
||||
<h5 id="_graph">Graph</h5>
|
||||
<div class="paragraph"><p>Graph output shows how long each method takes to run, which methods call it and which methods it calls. <a href="http://ruby-prof.rubyforge.org/files/examples/graph_txt.html">Check ruby prof documentation for a better explaination</a>.</p></div>
|
||||
<div class="paragraph"><p>Graph output shows how long each method takes to run, which methods call it and which methods it calls. <a href="http://ruby-prof.rubyforge.org/files/examples/graph_txt.html">Check ruby prof documentation for a better explanation</a>.</p></div>
|
||||
<h5 id="_tree">Tree</h5>
|
||||
<div class="paragraph"><p>Tree output is profiling information in calltree format for use by kcachegrind and similar tools.</p></div>
|
||||
<h3 id="_preparing_ruby_and_ruby_prof">3.4. Preparing Ruby and Ruby-prof</h3>
|
||||
<div class="paragraph"><p>Before we go ahead, Rails performance testing requires you to build a special Ruby binary with some super powers - GC patch for measuring GC Runs/Time. This process is very straight forward. If you’ve never compiled a Ruby binary before, you can follow the following steps to build a ruby binary inside your home directory:</p></div>
|
||||
<h4 id="_compile">3.4.1. Compile</h4>
|
||||
<div class="paragraph"><p>Tree output is profiling information in calltree format for use by <a href="http://kcachegrind.sourceforge.net/html/Home.html">kcachegrind</a> and similar tools.</p></div>
|
||||
<h3 id="_tuning_test_runs">1.6. Tuning Test Runs</h3>
|
||||
<div class="paragraph"><p>By default, each performance test is run <tt>4 times</tt> in benchmarking mode and <tt>1 time</tt> in profiling. However, test runs can easily be configured.</p></div>
|
||||
<div class="admonitionblock">
|
||||
<table><tr>
|
||||
<td class="icon">
|
||||
<img src="./images/icons/caution.png" alt="Caution" />
|
||||
</td>
|
||||
<td class="content">Performance test configurability is not yet enabled in Rails. But it will be soon.</td>
|
||||
</tr></table>
|
||||
</div>
|
||||
<h3 id="gc">1.7. Installing GC-Patched Ruby</h3>
|
||||
<div class="paragraph"><p>To get the best from Rails performance tests, you need to build a special Ruby binary with some super powers - <a href="http://rubyforge.org/tracker/download.php/1814/7062/17676/3291/ruby186gc.patch">GC patch</a> for measuring GC Runs/Time and memory/object allocation.</p></div>
|
||||
<div class="paragraph"><p>The process is fairly straight forward. If you’ve never compiled a Ruby binary before, follow these steps to build a ruby binary inside your home directory:</p></div>
|
||||
<h4 id="_installation">1.7.1. Installation</h4>
|
||||
<div class="paragraph"><p>Compile Ruby and apply this <a href="http://rubyforge.org/tracker/download.php/1814/7062/17676/3291/ruby186gc.patch">GC Patch</a>:</p></div>
|
||||
<h4 id="_download_and_extract">1.7.2. Download and Extract</h4>
|
||||
<div class="listingblock">
|
||||
<div class="content"><!-- Generator: GNU source-highlight 2.9
|
||||
by Lorenzo Bettini
|
||||
http://www.lorenzobettini.it
|
||||
http://www.gnu.org/software/src-highlite -->
|
||||
<pre><tt><span style="color: #990000">[</span>lifo@null <span style="color: #990000">~]</span>$ mkdir rubygc
|
||||
<span style="color: #990000">[</span>lifo@null <span style="color: #990000">~]</span>$ wget ftp<span style="color: #990000">:</span>//ftp<span style="color: #990000">.</span>ruby-lang<span style="color: #990000">.</span>org/pub/ruby<span style="color: #990000">/</span><span style="color: #993399">1.8</span>/ruby-<span style="color: #993399">1.8</span><span style="color: #990000">.</span><span style="color: #993399">6</span>-p<span style="color: #993399">111</span><span style="color: #990000">.</span>tar<span style="color: #990000">.</span>gz
|
||||
<span style="color: #990000">[</span>lifo@null <span style="color: #990000">~]</span>$ tar -xzvf ruby-<span style="color: #993399">1.8</span><span style="color: #990000">.</span><span style="color: #993399">6</span>-p<span style="color: #993399">111</span><span style="color: #990000">.</span>tar<span style="color: #990000">.</span>gz
|
||||
<span style="color: #990000">[</span>lifo@null <span style="color: #990000">~]</span>$ cd ruby-<span style="color: #993399">1.8</span><span style="color: #990000">.</span><span style="color: #993399">6</span>-p<span style="color: #993399">111</span>
|
||||
<span style="color: #990000">[</span>lifo@null ruby-<span style="color: #993399">1.8</span><span style="color: #990000">.</span><span style="color: #993399">6</span>-p<span style="color: #993399">111</span><span style="color: #990000">]</span>$ curl http<span style="color: #990000">:</span>//rubyforge<span style="color: #990000">.</span>org/tracker/download<span style="color: #990000">.</span>php<span style="color: #990000">/</span><span style="color: #993399">1814</span><span style="color: #990000">/</span><span style="color: #993399">7062</span><span style="color: #990000">/</span><span style="color: #993399">17676</span><span style="color: #990000">/</span><span style="color: #993399">3291</span>/ruby186gc<span style="color: #990000">.</span>patch <span style="color: #990000">|</span> patch -p<span style="color: #993399">0</span>
|
||||
<span style="color: #990000">[</span>lifo@null ruby-<span style="color: #993399">1.8</span><span style="color: #990000">.</span><span style="color: #993399">6</span>-p<span style="color: #993399">111</span><span style="color: #990000">]</span>$ <span style="color: #990000">.</span>/configure --prefix<span style="color: #990000">=</span>/Users/lifo/rubygc
|
||||
<span style="color: #990000">[</span>lifo@null ruby-<span style="color: #993399">1.8</span><span style="color: #990000">.</span><span style="color: #993399">6</span>-p<span style="color: #993399">111</span><span style="color: #990000">]</span>$ make <span style="color: #990000">&&</span> make install</tt></pre></div></div>
|
||||
<h4 id="_prepare_aliases">3.4.2. Prepare aliases</h4>
|
||||
<div class="paragraph"><p>Add the following lines in your ~/.profile for convenience:</p></div>
|
||||
<div class="listingblock">
|
||||
<div class="content">
|
||||
<pre><tt>alias gcruby='/Users/lifo/rubygc/bin/ruby'
|
||||
alias gcrake='/Users/lifo/rubygc/bin/rake'
|
||||
alias gcgem='/Users/lifo/rubygc/bin/gem'
|
||||
alias gcirb='/Users/lifo/rubygc/bin/irb'
|
||||
alias gcrails='/Users/lifo/rubygc/bin/rails'</tt></pre>
|
||||
</div></div>
|
||||
<h4 id="_install_rubygems_and_some_basic_gems">3.4.3. Install rubygems and some basic gems</h4>
|
||||
<div class="paragraph"><p>Download <a href="http://rubyforge.org/projects/rubygems">Rubygems</a> and install it from source. Afterwards, install rake. rails, ruby-prof and rack gems:</p></div>
|
||||
<div class="listingblock">
|
||||
<div class="content">
|
||||
<pre><tt>[lifo@null ~]$ gcgem install rake
|
||||
[lifo@null ~]$ gcgem install rails
|
||||
[lifo@null ~]$ gcgem install ruby-prof
|
||||
[lifo@null ~]$ gcgem install rack</tt></pre>
|
||||
</div></div>
|
||||
<h4 id="_install_mysql_gem">3.4.4. Install MySQL gem</h4>
|
||||
<div class="listingblock">
|
||||
<div class="content">
|
||||
<pre><tt>[lifo@null ~]$ gcgem install mysql</tt></pre>
|
||||
</div></div>
|
||||
<div class="paragraph"><p>If this fails, you can try to install it manually:</p></div>
|
||||
<div class="listingblock">
|
||||
<div class="content">
|
||||
<pre><tt>[lifo@null ~]$ cd /Users/lifo/rubygc/lib/ruby/gems/1.8/gems/mysql-2.7/
|
||||
[lifo@null mysql-2.7]$ gcruby extconf.rb --with-mysql-config
|
||||
[lifo@null mysql-2.7]$ make && make install</tt></pre>
|
||||
</div></div>
|
||||
<h3 id="_generating_performance_test">3.5. Generating performance test</h3>
|
||||
<div class="paragraph"><p>Rails provides a generator for creating new performance tests:</p></div>
|
||||
<span style="color: #990000">[</span>lifo@null <span style="color: #990000">~]</span>$ wget <span style="color: #990000"><</span>download the latest stable ruby from ftp<span style="color: #990000">:</span>//ftp<span style="color: #990000">.</span>ruby-lang<span style="color: #990000">.</span>org/pub/ruby<span style="color: #990000">></span>
|
||||
<span style="color: #990000">[</span>lifo@null <span style="color: #990000">~]</span>$ tar -xzvf <span style="color: #990000"><</span>ruby-version<span style="color: #990000">.</span>tar<span style="color: #990000">.</span>gz<span style="color: #990000">></span>
|
||||
<span style="color: #990000">[</span>lifo@null <span style="color: #990000">~]</span>$ cd <span style="color: #990000"><</span>ruby-version<span style="color: #990000">></span></tt></pre></div></div>
|
||||
<h4 id="_apply_the_patch">1.7.3. Apply the patch</h4>
|
||||
<div class="listingblock">
|
||||
<div class="content"><!-- Generator: GNU source-highlight 2.9
|
||||
by Lorenzo Bettini
|
||||
http://www.lorenzobettini.it
|
||||
http://www.gnu.org/software/src-highlite -->
|
||||
<pre><tt><span style="color: #990000">[</span>lifo@null application <span style="color: #990000">(</span>master<span style="color: #990000">)]</span>$ script/generate performance_test homepage</tt></pre></div></div>
|
||||
<div class="paragraph"><p>This will generate <tt>test/performance/homepage_test.rb</tt>:</p></div>
|
||||
<pre><tt><span style="color: #990000">[</span>lifo@null ruby-version<span style="color: #990000">]</span>$ curl http<span style="color: #990000">:</span>//rubyforge<span style="color: #990000">.</span>org/tracker/download<span style="color: #990000">.</span>php<span style="color: #990000">/</span><span style="color: #993399">1814</span><span style="color: #990000">/</span><span style="color: #993399">7062</span><span style="color: #990000">/</span><span style="color: #993399">17676</span><span style="color: #990000">/</span><span style="color: #993399">3291</span>/ruby186gc<span style="color: #990000">.</span>patch <span style="color: #990000">|</span> patch -p<span style="color: #993399">0</span></tt></pre></div></div>
|
||||
<h4 id="_configure_and_install">1.7.4. Configure and Install</h4>
|
||||
<div class="paragraph"><p>The following will install ruby in your home directory’s <tt>/rubygc</tt> directory. Make sure to replace <tt><homedir></tt> with a full patch to your actual home directory.</p></div>
|
||||
<div class="listingblock">
|
||||
<div class="content"><!-- Generator: GNU source-highlight 2.9
|
||||
by Lorenzo Bettini
|
||||
http://www.lorenzobettini.it
|
||||
http://www.gnu.org/software/src-highlite -->
|
||||
<pre><tt><span style="font-weight: bold"><span style="color: #000080">require</span></span> <span style="color: #FF0000">'test_helper'</span>
|
||||
<span style="font-weight: bold"><span style="color: #000080">require</span></span> <span style="color: #FF0000">'performance_test_help'</span>
|
||||
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">class</span></span> HomepageTest <span style="color: #990000"><</span> ActionController<span style="color: #990000">::</span>PerformanceTest
|
||||
<span style="font-style: italic"><span style="color: #9A1900"># Replace this with your real tests.</span></span>
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">def</span></span> test_homepage
|
||||
get <span style="color: #FF0000">'/'</span>
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">end</span></span>
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">end</span></span></tt></pre></div></div>
|
||||
<div class="paragraph"><p>Which you can modify to suit your needs.</p></div>
|
||||
</div>
|
||||
<h2 id="_other_profiling_tools">4. Other Profiling Tools</h2>
|
||||
<div class="sectionbody">
|
||||
<pre><tt><span style="color: #990000">[</span>lifo@null ruby-version<span style="color: #990000">]</span>$ <span style="color: #990000">.</span>/configure --prefix<span style="color: #990000">=/<</span>homedir<span style="color: #990000">></span>/rubygc
|
||||
<span style="color: #990000">[</span>lifo@null ruby-version<span style="color: #990000">]</span>$ make <span style="color: #990000">&&</span> make install</tt></pre></div></div>
|
||||
<h4 id="_prepare_aliases">1.7.5. Prepare aliases</h4>
|
||||
<div class="paragraph"><p>For convenience, add the following lines in your <tt>~/.profile</tt>:</p></div>
|
||||
<div class="listingblock">
|
||||
<div class="content">
|
||||
<pre><tt>alias gcruby='~/rubygc/bin/ruby'
|
||||
alias gcrake='~/rubygc/bin/rake'
|
||||
alias gcgem='~/rubygc/bin/gem'
|
||||
alias gcirb='~/rubygc/bin/irb'
|
||||
alias gcrails='~/rubygc/bin/rails'</tt></pre>
|
||||
</div></div>
|
||||
<h4 id="_install_rubygems_and_dependency_gems">1.7.6. Install rubygems and dependency gems</h4>
|
||||
<div class="paragraph"><p>Download <a href="http://rubyforge.org/projects/rubygems">Rubygems</a> and install it from source. Rubygem’s README file should have necessary installation instructions.</p></div>
|
||||
<div class="paragraph"><p>Additionally, install the following gems :</p></div>
|
||||
<div class="ulist"><ul>
|
||||
<li>
|
||||
<p>
|
||||
<a href="http://www.hpl.hp.com/research/linux/httperf/">httperf</a>
|
||||
<tt>rake</tt>
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>
|
||||
<tt>rails</tt>
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>
|
||||
<tt>ruby-prof</tt>
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>
|
||||
<tt>rack</tt>
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>
|
||||
<tt>mysql</tt>
|
||||
</p>
|
||||
</li>
|
||||
</ul></div>
|
||||
<div class="paragraph"><p>If installing <tt>mysql</tt> fails, you can try to install it manually:</p></div>
|
||||
<div class="listingblock">
|
||||
<div class="content"><!-- Generator: GNU source-highlight 2.9
|
||||
by Lorenzo Bettini
|
||||
http://www.lorenzobettini.it
|
||||
http://www.gnu.org/software/src-highlite -->
|
||||
<pre><tt><span style="color: #990000">[</span>lifo@null mysql<span style="color: #990000">]</span>$ gcruby extconf<span style="color: #990000">.</span>rb --with-mysql-config
|
||||
<span style="color: #990000">[</span>lifo@null mysql<span style="color: #990000">]</span>$ make <span style="color: #990000">&&</span> make install</tt></pre></div></div>
|
||||
<div class="paragraph"><p>And you’re ready to go. Don’t forget to use <tt>gcruby</tt> and <tt>gcrake</tt> aliases when running the performance tests.</p></div>
|
||||
</div>
|
||||
<h2 id="_command_line_tools">2. Command Line Tools</h2>
|
||||
<div class="sectionbody">
|
||||
<div class="paragraph"><p>Writing performance test cases could be an overkill when you are looking for one time tests. Rails ships with two command line tools that enable quick and dirty performance testing:</p></div>
|
||||
<h3 id="_benchmarker">2.1. benchmarker</h3>
|
||||
<div class="paragraph"><p><tt>benchmarker</tt> is a wrapper around Ruby’s <a href="http://ruby-doc.org/core/classes/Benchmark.html">Benchmark</a> module.</p></div>
|
||||
<div class="paragraph"><p>Usage:</p></div>
|
||||
<div class="listingblock">
|
||||
<div class="content"><!-- Generator: GNU source-highlight 2.9
|
||||
by Lorenzo Bettini
|
||||
http://www.lorenzobettini.it
|
||||
http://www.gnu.org/software/src-highlite -->
|
||||
<pre><tt>$ script/performance/benchmarker <span style="color: #990000">[</span><span style="font-weight: bold"><span style="color: #0000FF">times</span></span><span style="color: #990000">]</span> <span style="color: #FF0000">'Person.expensive_way'</span> <span style="color: #FF0000">'Person.another_expensive_way'</span> <span style="color: #990000">...</span></tt></pre></div></div>
|
||||
<div class="paragraph"><p>Examples:</p></div>
|
||||
<div class="listingblock">
|
||||
<div class="content"><!-- Generator: GNU source-highlight 2.9
|
||||
by Lorenzo Bettini
|
||||
http://www.lorenzobettini.it
|
||||
http://www.gnu.org/software/src-highlite -->
|
||||
<pre><tt>$ script/performance/benchmarker <span style="color: #993399">10</span> <span style="color: #FF0000">'Item.all'</span> <span style="color: #FF0000">'CouchItem.all'</span></tt></pre></div></div>
|
||||
<div class="paragraph"><p>If the <tt>[times]</tt> argument is omitted, supplied methods are run just once:</p></div>
|
||||
<div class="listingblock">
|
||||
<div class="content"><!-- Generator: GNU source-highlight 2.9
|
||||
by Lorenzo Bettini
|
||||
http://www.lorenzobettini.it
|
||||
http://www.gnu.org/software/src-highlite -->
|
||||
<pre><tt>$ script/performance/benchmarker <span style="color: #FF0000">'Item.first'</span> <span style="color: #FF0000">'Item.last'</span></tt></pre></div></div>
|
||||
<h3 id="_profiler">2.2. profiler</h3>
|
||||
<div class="paragraph"><p><tt>profiler</tt> is a wrapper around <a href="http://ruby-prof.rubyforge.org/">ruby-prof</a> gem.</p></div>
|
||||
<div class="paragraph"><p>Usage:</p></div>
|
||||
<div class="listingblock">
|
||||
<div class="content"><!-- Generator: GNU source-highlight 2.9
|
||||
by Lorenzo Bettini
|
||||
http://www.lorenzobettini.it
|
||||
http://www.gnu.org/software/src-highlite -->
|
||||
<pre><tt>$ script/performance/profiler <span style="color: #FF0000">'Person.expensive_method(10)'</span> <span style="color: #990000">[</span><span style="font-weight: bold"><span style="color: #0000FF">times</span></span><span style="color: #990000">]</span> <span style="color: #990000">[</span>flat<span style="color: #990000">|</span>graph<span style="color: #990000">|</span>graph_html<span style="color: #990000">]</span></tt></pre></div></div>
|
||||
<div class="paragraph"><p>Examples:</p></div>
|
||||
<div class="listingblock">
|
||||
<div class="content"><!-- Generator: GNU source-highlight 2.9
|
||||
by Lorenzo Bettini
|
||||
http://www.lorenzobettini.it
|
||||
http://www.gnu.org/software/src-highlite -->
|
||||
<pre><tt>$ script/performance/profiler <span style="color: #FF0000">'Item.all'</span></tt></pre></div></div>
|
||||
<div class="paragraph"><p>This will profile <tt>Item.all</tt> in <tt>RubyProf::WALL_TIME</tt> measure mode. By default, it prints flat output to the shell.</p></div>
|
||||
<div class="listingblock">
|
||||
<div class="content"><!-- Generator: GNU source-highlight 2.9
|
||||
by Lorenzo Bettini
|
||||
http://www.lorenzobettini.it
|
||||
http://www.gnu.org/software/src-highlite -->
|
||||
<pre><tt>$ script/performance/profiler <span style="color: #FF0000">'Item.all'</span> <span style="color: #993399">10</span> graph</tt></pre></div></div>
|
||||
<div class="paragraph"><p>This will profile <tt>10.times { Item.all }</tt> with <tt>RubyProf::WALL_TIME</tt> measure mode and print graph output to the shell.</p></div>
|
||||
<div class="paragraph"><p>If you want to store the output in a file:</p></div>
|
||||
<div class="listingblock">
|
||||
<div class="content"><!-- Generator: GNU source-highlight 2.9
|
||||
by Lorenzo Bettini
|
||||
http://www.lorenzobettini.it
|
||||
http://www.gnu.org/software/src-highlite -->
|
||||
<pre><tt>$ script/performance/profiler <span style="color: #FF0000">'Item.all'</span> <span style="color: #993399">10</span> graph <span style="color: #993399">2</span><span style="color: #990000">></span> graph<span style="color: #990000">.</span>txt</tt></pre></div></div>
|
||||
</div>
|
||||
<h2 id="_helper_methods">3. Helper methods</h2>
|
||||
<div class="sectionbody">
|
||||
<div class="paragraph"><p>Rails provides various helper methods inside Active Record, Action Controller and Action View to measure the time taken by a given piece of code. The method is called <tt>benchmark()</tt> in all the three components.</p></div>
|
||||
<h3 id="_model">3.1. Model</h3>
|
||||
<div class="listingblock">
|
||||
<div class="content"><!-- Generator: GNU source-highlight 2.9
|
||||
by Lorenzo Bettini
|
||||
http://www.lorenzobettini.it
|
||||
http://www.gnu.org/software/src-highlite -->
|
||||
<pre><tt>Project<span style="color: #990000">.</span>benchmark<span style="color: #990000">(</span><span style="color: #FF0000">"Creating project"</span><span style="color: #990000">)</span> <span style="font-weight: bold"><span style="color: #0000FF">do</span></span>
|
||||
project <span style="color: #990000">=</span> Project<span style="color: #990000">.</span>create<span style="color: #990000">(</span><span style="color: #FF0000">"name"</span> <span style="color: #990000">=></span> <span style="color: #FF0000">"stuff"</span><span style="color: #990000">)</span>
|
||||
project<span style="color: #990000">.</span>create_manager<span style="color: #990000">(</span><span style="color: #FF0000">"name"</span> <span style="color: #990000">=></span> <span style="color: #FF0000">"David"</span><span style="color: #990000">)</span>
|
||||
project<span style="color: #990000">.</span>milestones <span style="color: #990000"><<</span> Milestone<span style="color: #990000">.</span>find<span style="color: #990000">(:</span>all<span style="color: #990000">)</span>
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">end</span></span></tt></pre></div></div>
|
||||
<div class="paragraph"><p>This benchmarks the code enclosed in the <tt>Project.benchmark("Creating project") do..end</tt> block and prints the result to the log file:</p></div>
|
||||
<div class="listingblock">
|
||||
<div class="content"><!-- Generator: GNU source-highlight 2.9
|
||||
by Lorenzo Bettini
|
||||
http://www.lorenzobettini.it
|
||||
http://www.gnu.org/software/src-highlite -->
|
||||
<pre><tt>Creating project <span style="color: #990000">(</span><span style="color: #993399">185</span><span style="color: #990000">.</span>3ms<span style="color: #990000">)</span></tt></pre></div></div>
|
||||
<div class="paragraph"><p>Please refer to the <a href="http://api.rubyonrails.com/classes/ActiveRecord/Base.html#M001336">API docs</a> for additional options to <tt>benchmark()</tt></p></div>
|
||||
<h3 id="_controller">3.2. Controller</h3>
|
||||
<div class="paragraph"><p>Similarly, you could use this helper method inside <a href="http://api.rubyonrails.com/classes/ActionController/Benchmarking/ClassMethods.html#M000715">controllers</a></p></div>
|
||||
<div class="admonitionblock">
|
||||
<table><tr>
|
||||
<td class="icon">
|
||||
<img src="./images/icons/note.png" alt="Note" />
|
||||
</td>
|
||||
<td class="content"><tt>benchmark</tt> is a class method inside controllers</td>
|
||||
</tr></table>
|
||||
</div>
|
||||
<div class="listingblock">
|
||||
<div class="content"><!-- Generator: GNU source-highlight 2.9
|
||||
by Lorenzo Bettini
|
||||
http://www.lorenzobettini.it
|
||||
http://www.gnu.org/software/src-highlite -->
|
||||
<pre><tt><span style="font-weight: bold"><span style="color: #0000FF">def</span></span> process_projects
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">self</span></span><span style="color: #990000">.</span><span style="font-weight: bold"><span style="color: #0000FF">class</span></span><span style="color: #990000">.</span>benchmark<span style="color: #990000">(</span><span style="color: #FF0000">"Processing projects"</span><span style="color: #990000">)</span> <span style="font-weight: bold"><span style="color: #0000FF">do</span></span>
|
||||
Project<span style="color: #990000">.</span>process<span style="color: #990000">(</span>params<span style="color: #990000">[:</span>project_ids<span style="color: #990000">])</span>
|
||||
Project<span style="color: #990000">.</span>update_cached_projects
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">end</span></span>
|
||||
<span style="font-weight: bold"><span style="color: #0000FF">end</span></span></tt></pre></div></div>
|
||||
<h3 id="_view">3.3. View</h3>
|
||||
<div class="paragraph"><p>And in <a href="http://api.rubyonrails.com/classes/ActionController/Benchmarking/ClassMethods.html#M000715">views</a>:</p></div>
|
||||
<div class="listingblock">
|
||||
<div class="content"><!-- Generator: GNU source-highlight 2.9
|
||||
by Lorenzo Bettini
|
||||
http://www.lorenzobettini.it
|
||||
http://www.gnu.org/software/src-highlite -->
|
||||
<pre><tt><span style="color: #FF0000"><% benchmark("Showing projects partial") do %></span>
|
||||
<span style="color: #FF0000"><%= render :partial =></span> <span style="color: #009900">@projects</span> <span style="color: #990000">%></span>
|
||||
<span style="color: #FF0000"><% end %></span></tt></pre></div></div>
|
||||
</div>
|
||||
<h2 id="_request_logging">4. Request Logging</h2>
|
||||
<div class="sectionbody">
|
||||
<div class="paragraph"><p>Rails log files contain very useful information about the time taken to serve each request. Here’s a typical log file entry:</p></div>
|
||||
<div class="listingblock">
|
||||
<div class="content"><!-- Generator: GNU source-highlight 2.9
|
||||
by Lorenzo Bettini
|
||||
http://www.lorenzobettini.it
|
||||
http://www.gnu.org/software/src-highlite -->
|
||||
<pre><tt>Processing ItemsController<span style="font-style: italic"><span style="color: #9A1900">#index (for 127.0.0.1 at 2009-01-08 03:06:39) [GET]</span></span>
|
||||
Rendering template within layouts<span style="color: #990000">/</span>items
|
||||
Rendering items<span style="color: #990000">/</span>index
|
||||
Completed <span style="font-weight: bold"><span style="color: #0000FF">in</span></span> 5ms <span style="color: #990000">(</span>View<span style="color: #990000">:</span> <span style="color: #993399">2</span><span style="color: #990000">,</span> DB<span style="color: #990000">:</span> <span style="color: #993399">0</span><span style="color: #990000">)</span> <span style="color: #990000">|</span> <span style="color: #993399">200</span> OK <span style="color: #990000">[</span>http<span style="color: #990000">:</span><span style="color: #FF6600">//0.0.0.0/</span>items<span style="color: #990000">]</span></tt></pre></div></div>
|
||||
<div class="paragraph"><p>For this section, we’re only interested in the last line:</p></div>
|
||||
<div class="listingblock">
|
||||
<div class="content"><!-- Generator: GNU source-highlight 2.9
|
||||
by Lorenzo Bettini
|
||||
http://www.lorenzobettini.it
|
||||
http://www.gnu.org/software/src-highlite -->
|
||||
<pre><tt>Completed <span style="font-weight: bold"><span style="color: #0000FF">in</span></span> 5ms <span style="color: #990000">(</span>View<span style="color: #990000">:</span> <span style="color: #993399">2</span><span style="color: #990000">,</span> DB<span style="color: #990000">:</span> <span style="color: #993399">0</span><span style="color: #990000">)</span> <span style="color: #990000">|</span> <span style="color: #993399">200</span> OK <span style="color: #990000">[</span>http<span style="color: #990000">:</span><span style="color: #FF6600">//0.0.0.0/</span>items<span style="color: #990000">]</span></tt></pre></div></div>
|
||||
<div class="paragraph"><p>This data is fairly straightforward to understand. Rails uses millisecond(ms) as the metric to measures the time taken. The complete request spent 5 ms inside Rails, out of which 2 ms were spent rendering views and none was spent communication with the database. It’s safe to assume that the remaining 3 ms were spent inside the controller.</p></div>
|
||||
<div class="paragraph"><p>Michael Koziarski has an <a href="http://www.therailsway.com/2009/1/6/requests-per-second">interesting blog post</a> explaining the importance of using milliseconds as the metric.</p></div>
|
||||
</div>
|
||||
<h2 id="_useful_profiling_tools">5. Useful Profiling Tools</h2>
|
||||
<div class="sectionbody">
|
||||
<h3 id="_rails_plugins_and_gems">5.1. Rails Plugins and Gems</h3>
|
||||
<div class="ulist"><ul>
|
||||
<li>
|
||||
<p>
|
||||
<a href="http://rails-analyzer.rubyforge.org/">Rails Analyzer</a>
|
||||
</p>
|
||||
</li>
|
||||
@@ -559,10 +789,39 @@ http://www.gnu.org/software/src-highlite -->
|
||||
<a href="http://www.flyingmachinestudios.com/projects/">Palmist</a>
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>
|
||||
<a href="http://github.com/josevalim/rails-footnotes/tree/master">Rails Footnotes</a>
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>
|
||||
<a href="http://github.com/dsboulder/query_reviewer/tree/master">Query Reviewer</a>
|
||||
</p>
|
||||
</li>
|
||||
</ul></div>
|
||||
<h3 id="_external">5.2. External</h3>
|
||||
<div class="ulist"><ul>
|
||||
<li>
|
||||
<p>
|
||||
<a href="http://www.hpl.hp.com/research/linux/httperf">httperf</a>
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>
|
||||
<a href="http://httpd.apache.org/docs/2.2/programs/ab.html">ab</a>
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>
|
||||
<a href="http://jakarta.apache.org/jmeter">JMeter</a>
|
||||
</p>
|
||||
</li>
|
||||
</ul></div>
|
||||
</div>
|
||||
<h2 id="_commercial_products_dedicated_to_rails_perfomance">5. Commercial products dedicated to Rails Perfomance</h2>
|
||||
<h2 id="_commercial_products">6. Commercial Products</h2>
|
||||
<div class="sectionbody">
|
||||
<div class="paragraph"><p>Rails has been lucky to have three startups dedicated to Rails specific performance tools:</p></div>
|
||||
<div class="ulist"><ul>
|
||||
<li>
|
||||
<p>
|
||||
@@ -581,13 +840,13 @@ http://www.gnu.org/software/src-highlite -->
|
||||
</li>
|
||||
</ul></div>
|
||||
</div>
|
||||
<h2 id="_changelog">6. Changelog</h2>
|
||||
<h2 id="_changelog">7. Changelog</h2>
|
||||
<div class="sectionbody">
|
||||
<div class="paragraph"><p><a href="http://rails.lighthouseapp.com/projects/16213-rails-guides/tickets/4">Lighthouse ticket</a></p></div>
|
||||
<div class="ulist"><ul>
|
||||
<li>
|
||||
<p>
|
||||
January 9, 2009: Rewrite by Pratik
|
||||
January 9, 2009: Complete rewrite by Pratik
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
|
||||
@@ -359,7 +359,7 @@ Sometimes it will make sense to validate an object just when a given predicate i
|
||||
|
||||
=== Using a symbol with the +:if+ and +:unless+ options
|
||||
|
||||
You can associated the +:if+ and +:unless+ options with a symbol corresponding to the name of a method that will get called right before validation happens. This is the most commonly used option.
|
||||
You can associate the +:if+ and +:unless+ options with a symbol corresponding to the name of a method that will get called right before validation happens. This is the most commonly used option.
|
||||
|
||||
[source, ruby]
|
||||
------------------------------------------------------------------
|
||||
@@ -620,29 +620,7 @@ Callbacks are methods that get called at certain moments of an object's lifecycl
|
||||
|
||||
=== Callbacks registration
|
||||
|
||||
In order to use the available callbacks, you need to registrate them. There are two ways of doing that.
|
||||
|
||||
=== Registering callbacks by overriding the callback methods
|
||||
|
||||
You can specify the callback method directly, by overriding it. Let's see how it works using the +before_validation+ callback, which will surprisingly run right before any validation is done.
|
||||
|
||||
[source, ruby]
|
||||
------------------------------------------------------------------
|
||||
class User < ActiveRecord::Base
|
||||
validates_presence_of :login, :email
|
||||
|
||||
protected
|
||||
def before_validation
|
||||
if self.login.nil?
|
||||
self.login = email unless email.blank?
|
||||
end
|
||||
end
|
||||
end
|
||||
------------------------------------------------------------------
|
||||
|
||||
=== Registering callbacks by using macro-style class methods
|
||||
|
||||
The other way you can register a callback method is by implementing it as an ordinary method, and then using a macro-style class method to register it as a callback. The last example could be written like that:
|
||||
In order to use the available callbacks, you need to registrate them. You can do that by implementing them as an ordinary methods, and then using a macro-style class method to register then as callbacks.
|
||||
|
||||
[source, ruby]
|
||||
------------------------------------------------------------------
|
||||
@@ -671,13 +649,58 @@ class User < ActiveRecord::Base
|
||||
end
|
||||
------------------------------------------------------------------
|
||||
|
||||
In Rails, the preferred way of registering callbacks is by using macro-style class methods. The main advantages of using macro-style class methods are:
|
||||
|
||||
* You can add more than one method for each type of callback. Those methods will be queued for execution at the same order they were registered.
|
||||
* Readability, since your callback declarations will live at the beggining of your models' files.
|
||||
|
||||
CAUTION: Remember to always declare the callback methods as being protected or private. These methods should never be public, otherwise it will be possible to call them from code outside the model, violating object encapsulation and exposing implementation details.
|
||||
|
||||
== Conditional callbacks
|
||||
|
||||
Like in validations, we can also make our callbacks conditional, calling then only when a given predicate is satisfied. You can do that by using the +:if+ and +:unless+ options, which can take a symbol, a string or a Ruby Proc. You may use the +:if+ option when you want to specify when the callback *should* get called. If you want to specify when the callback *should not* be called, then you may use the +:unless+ option.
|
||||
|
||||
=== Using a symbol with the +:if+ and +:unless+ options
|
||||
|
||||
You can associate the +:if+ and +:unless+ options with a symbol corresponding to the name of a method that will get called right before the callback. If this method returns +false+ the callback won't be executed. This is the most common option. Using this form of registration it's also possible to register several different methods that should be called to check the if the callback should be executed.
|
||||
|
||||
[source, ruby]
|
||||
------------------------------------------------------------------
|
||||
class Order < ActiveRecord::Base
|
||||
before_save :normalize_card_number, :if => :paid_with_card?
|
||||
end
|
||||
------------------------------------------------------------------
|
||||
|
||||
=== Using a string with the +:if+ and +:unless+ options
|
||||
|
||||
You can also use a string that will be evaluated using +:eval+ and needs to contain valid Ruby code. You should use this option only when the string represents a really short condition.
|
||||
|
||||
[source, ruby]
|
||||
------------------------------------------------------------------
|
||||
class Order < ActiveRecord::Base
|
||||
before_save :normalize_card_number, :if => "paid_with_card?"
|
||||
end
|
||||
------------------------------------------------------------------
|
||||
|
||||
=== Using a Proc object with the +:if+ and :+unless+ options
|
||||
|
||||
Finally, it's possible to associate +:if+ and +:unless+ with a Ruby Proc object. This option is best suited when writing short validation methods, usually one-liners.
|
||||
|
||||
[source, ruby]
|
||||
------------------------------------------------------------------
|
||||
class Order < ActiveRecord::Base
|
||||
before_save :normalize_card_number,
|
||||
:if => Proc.new { |order| order.paid_with_card? }
|
||||
end
|
||||
------------------------------------------------------------------
|
||||
|
||||
=== Multiple Conditions for Callbacks
|
||||
|
||||
When writing conditional callbacks, it's possible to mix both +:if+ and +:unless+ in the same callback declaration.
|
||||
|
||||
[source, ruby]
|
||||
------------------------------------------------------------------
|
||||
class Comment < ActiveRecord::Base
|
||||
after_create :send_email_to_author, :if => :author_wants_emails?,
|
||||
:unless => Proc.new { |comment| comment.post.ignore_comments? }
|
||||
end
|
||||
------------------------------------------------------------------
|
||||
|
||||
== Available callbacks
|
||||
|
||||
Here is a list with all the available Active Record callbacks, listed in the same order in which they will get called during the respective operations.
|
||||
|
||||
@@ -113,7 +113,7 @@ ways of achieving this and how to understand what is happening "behind the scene
|
||||
of your code.
|
||||
***********************************************************
|
||||
|
||||
.link:performance_testing.html[Performance testing Rails Applications]
|
||||
.link:performance_testing.html[Performance Testing Rails Applications]
|
||||
***********************************************************
|
||||
CAUTION: link:http://rails.lighthouseapp.com/projects/16213/tickets/4[Lighthouse Ticket]
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user