mirror of https://github.com/rails/rails
Merge remote branch 'mainstream/master'
Conflicts: actionpack/lib/action_controller/metal/flash.rb
This commit is contained in:
commit
b04230e3bb
|
@ -1,4 +1,3 @@
|
|||
.DS_Store
|
||||
debug.log
|
||||
doc/rdoc
|
||||
activemodel/doc
|
||||
|
@ -17,10 +16,6 @@ railties/doc/guides/html/images
|
|||
railties/doc/guides/html/stylesheets
|
||||
benches
|
||||
railties/guides/output
|
||||
*.rbc
|
||||
*.swp
|
||||
*.swo
|
||||
*.tmproj
|
||||
bin
|
||||
vendor/gems/
|
||||
railties/tmp
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
[submodule "arel"]
|
||||
path = arel
|
||||
url = git://github.com/rails/arel.git
|
||||
[submodule "rack"]
|
||||
path = rack
|
||||
url = git://github.com/rails/rack.git
|
||||
|
|
4
Gemfile
4
Gemfile
|
@ -7,9 +7,6 @@ gem "rails", "3.0.pre", :path => "railties"
|
|||
gem lib, '3.0.pre', :path => lib
|
||||
end
|
||||
|
||||
# AS
|
||||
gem "i18n", ">= 0.3.0"
|
||||
|
||||
# AR
|
||||
gem "arel", "0.2.pre", :git => "git://github.com/rails/arel.git"
|
||||
gem "sqlite3-ruby", ">= 1.2.5"
|
||||
|
@ -20,7 +17,6 @@ only :test do
|
|||
end
|
||||
|
||||
# AP
|
||||
gem "rack", "1.1.0", :git => "git://github.com/rack/rack.git"
|
||||
gem "rack-test", "0.5.3"
|
||||
gem "RedCloth", ">= 4.2.2"
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ Gem::Specification.new do |s|
|
|||
s.homepage = "http://www.rubyonrails.org"
|
||||
|
||||
s.add_dependency('actionpack', '= 3.0.pre')
|
||||
s.add_dependency('mail', '~> 1.5.0')
|
||||
s.add_dependency('mail', '~> 1.6.0')
|
||||
|
||||
s.files = Dir['CHANGELOG', 'README', 'MIT-LICENSE', 'lib/**/*']
|
||||
s.has_rdoc = true
|
||||
|
|
|
@ -259,10 +259,9 @@ module ActionMailer #:nodoc:
|
|||
include AbstractController::LocalizedCache
|
||||
include AbstractController::Layouts
|
||||
include AbstractController::Helpers
|
||||
include AbstractController::UrlFor
|
||||
|
||||
helper ActionMailer::MailHelper
|
||||
|
||||
include ActionController::UrlWriter
|
||||
include ActionMailer::DeprecatedBody
|
||||
|
||||
private_class_method :new #:nodoc:
|
||||
|
@ -399,10 +398,12 @@ module ActionMailer #:nodoc:
|
|||
# ...
|
||||
# end
|
||||
# end
|
||||
def receive(raw_email)
|
||||
logger.info "Received mail:\n #{raw_email}" unless logger.nil?
|
||||
mail = Mail.new(raw_email)
|
||||
new.receive(mail)
|
||||
def receive(raw_mail)
|
||||
ActiveSupport::Notifications.instrument("action_mailer.receive") do |payload|
|
||||
mail = Mail.new(raw_mail)
|
||||
set_payload_for_mail(payload, mail)
|
||||
new.receive(mail)
|
||||
end
|
||||
end
|
||||
|
||||
# Deliver the given mail object directly. This can be used to deliver
|
||||
|
@ -424,7 +425,19 @@ module ActionMailer #:nodoc:
|
|||
self.view_paths = ActionView::Base.process_view_paths(root)
|
||||
end
|
||||
|
||||
def set_payload_for_mail(payload, mail) #:nodoc:
|
||||
payload[:message_id] = mail.message_id
|
||||
payload[:subject] = mail.subject
|
||||
payload[:to] = mail.to
|
||||
payload[:from] = mail.from
|
||||
payload[:bcc] = mail.bcc if mail.bcc.present?
|
||||
payload[:cc] = mail.cc if mail.cc.present?
|
||||
payload[:date] = mail.date
|
||||
payload[:mail] = mail.encoded
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def matches_dynamic_method?(method_name) #:nodoc:
|
||||
method_name = method_name.to_s
|
||||
/^(create|deliver)_([_a-z]\w*)/.match(method_name) || /^(new)$/.match(method_name)
|
||||
|
@ -495,17 +508,14 @@ module ActionMailer #:nodoc:
|
|||
def deliver!(mail = @mail)
|
||||
raise "no mail object available for delivery!" unless mail
|
||||
|
||||
if logger
|
||||
logger.info "Sent mail to #{Array(recipients).join(', ')}"
|
||||
logger.debug "\n#{mail.encoded}"
|
||||
end
|
||||
|
||||
ActiveSupport::Notifications.instrument(:deliver_mail, :mail => mail) do
|
||||
begin
|
||||
begin
|
||||
ActiveSupport::Notifications.instrument("action_mailer.deliver",
|
||||
:template => template, :mailer => self.class.name) do |payload|
|
||||
self.class.set_payload_for_mail(payload, mail)
|
||||
self.delivery_method.perform_delivery(mail) if perform_deliveries
|
||||
rescue Exception => e # Net::SMTP errors or sendmail pipe errors
|
||||
raise e if raise_delivery_errors
|
||||
end
|
||||
rescue Exception => e # Net::SMTP errors or sendmail pipe errors
|
||||
raise e if raise_delivery_errors
|
||||
end
|
||||
|
||||
mail
|
||||
|
|
|
@ -5,6 +5,9 @@ module ActionMailer
|
|||
class Railtie < Rails::Railtie
|
||||
plugin_name :action_mailer
|
||||
|
||||
require "action_mailer/railties/subscriber"
|
||||
subscriber ActionMailer::Railties::Subscriber.new
|
||||
|
||||
initializer "action_mailer.set_configs" do |app|
|
||||
app.config.action_mailer.each do |k,v|
|
||||
ActionMailer::Base.send "#{k}=", v
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
module ActionMailer
|
||||
module Railties
|
||||
class Subscriber < Rails::Subscriber
|
||||
def deliver(event)
|
||||
recipients = Array(event.payload[:to]).join(', ')
|
||||
info("Sent mail to #{recipients} (%1.fms)" % event.duration)
|
||||
debug("\n#{event.payload[:mail]}")
|
||||
end
|
||||
|
||||
def receive(event)
|
||||
info("Received mail (%.1fms)" % event.duration)
|
||||
debug("\n#{event.payload[:mail]}")
|
||||
end
|
||||
|
||||
def logger
|
||||
ActionMailer::Base.logger
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,6 +1,3 @@
|
|||
require 'active_support/test_case'
|
||||
require 'action_mailer/base'
|
||||
|
||||
module ActionMailer
|
||||
class NonInferrableMailerError < ::StandardError
|
||||
def initialize(name)
|
||||
|
|
|
@ -10,7 +10,6 @@ require 'rubygems'
|
|||
require 'test/unit'
|
||||
|
||||
require 'action_mailer'
|
||||
require 'action_mailer/test_case'
|
||||
|
||||
# Show backtraces for deprecated behavior for quicker cleanup.
|
||||
ActiveSupport::Deprecation.debug = true
|
||||
|
|
|
@ -688,40 +688,6 @@ class ActionMailerTest < Test::Unit::TestCase
|
|||
TestMailer.deliver_signed_up(@recipient)
|
||||
end
|
||||
|
||||
class FakeLogger
|
||||
attr_reader :info_contents, :debug_contents
|
||||
|
||||
def initialize
|
||||
@info_contents, @debug_contents = "", ""
|
||||
end
|
||||
|
||||
def info(str = nil, &blk)
|
||||
@info_contents << str if str
|
||||
@info_contents << blk.call if block_given?
|
||||
end
|
||||
|
||||
def debug(str = nil, &blk)
|
||||
@debug_contents << str if str
|
||||
@debug_contents << blk.call if block_given?
|
||||
end
|
||||
end
|
||||
|
||||
def test_delivery_logs_sent_mail
|
||||
mail = TestMailer.create_signed_up(@recipient)
|
||||
# logger = mock()
|
||||
# logger.expects(:info).with("Sent mail to #{@recipient}")
|
||||
# logger.expects(:debug).with("\n#{mail.encoded}")
|
||||
TestMailer.logger = FakeLogger.new
|
||||
TestMailer.deliver_signed_up(@recipient)
|
||||
assert(TestMailer.logger.info_contents =~ /Sent mail to #{@recipient}/)
|
||||
expected = TestMailer.logger.debug_contents
|
||||
actual = "\n#{mail.encoded}"
|
||||
expected.gsub!(/Message-ID:.*\r\n/, "Message-ID: <123@456>\r\n")
|
||||
actual.gsub!(/Message-ID:.*\r\n/, "Message-ID: <123@456>\r\n")
|
||||
|
||||
assert_equal(expected, actual)
|
||||
end
|
||||
|
||||
def test_unquote_quoted_printable_subject
|
||||
msg = <<EOF
|
||||
From: me@example.com
|
||||
|
@ -1110,10 +1076,15 @@ EOF
|
|||
assert_equal "another@somewhere.test", mail['return-path'].to_s
|
||||
end
|
||||
|
||||
def test_return_path_with_create
|
||||
mail = TestMailer.create_return_path
|
||||
assert_equal "another@somewhere.test", mail.return_path
|
||||
end
|
||||
|
||||
def test_return_path_with_deliver
|
||||
ActionMailer::Base.delivery_method = :smtp
|
||||
TestMailer.deliver_return_path
|
||||
assert_match %r{^Return-Path: another@somewhere.test}, MockSMTP.deliveries[0][0]
|
||||
assert_match %r{^Return-Path: <another@somewhere.test>}, MockSMTP.deliveries[0][0]
|
||||
assert_equal "another@somewhere.test", MockSMTP.deliveries[0][1].to_s
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
require "abstract_unit"
|
||||
require "rails/subscriber/test_helper"
|
||||
require "action_mailer/railties/subscriber"
|
||||
|
||||
module SubscriberTest
|
||||
Rails::Subscriber.add(:action_mailer, ActionMailer::Railties::Subscriber.new)
|
||||
|
||||
class TestMailer < ActionMailer::Base
|
||||
def basic
|
||||
recipients "somewhere@example.com"
|
||||
subject "basic"
|
||||
from "basic@example.com"
|
||||
render :text => "Hello world"
|
||||
end
|
||||
|
||||
def receive(mail)
|
||||
# Do nothing
|
||||
end
|
||||
end
|
||||
|
||||
def set_logger(logger)
|
||||
ActionMailer::Base.logger = logger
|
||||
end
|
||||
|
||||
def test_deliver_is_notified
|
||||
TestMailer.deliver_basic
|
||||
wait
|
||||
assert_equal 1, @logger.logged(:info).size
|
||||
assert_match /Sent mail to somewhere@example.com/, @logger.logged(:info).first
|
||||
assert_equal 1, @logger.logged(:debug).size
|
||||
assert_match /Hello world/, @logger.logged(:debug).first
|
||||
end
|
||||
|
||||
def test_receive_is_notified
|
||||
fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email")
|
||||
TestMailer.receive(fixture)
|
||||
wait
|
||||
assert_equal 1, @logger.logged(:info).size
|
||||
assert_match /Received mail/, @logger.logged(:info).first
|
||||
assert_equal 1, @logger.logged(:debug).size
|
||||
assert_match /Jamis/, @logger.logged(:debug).first
|
||||
end
|
||||
|
||||
class SyncSubscriberTest < ActionMailer::TestCase
|
||||
include Rails::Subscriber::SyncTestHelper
|
||||
include SubscriberTest
|
||||
end
|
||||
|
||||
class AsyncSubscriberTest < ActionMailer::TestCase
|
||||
include Rails::Subscriber::AsyncTestHelper
|
||||
include SubscriberTest
|
||||
end
|
||||
end
|
|
@ -1,5 +1,13 @@
|
|||
*Edge*
|
||||
|
||||
* Fixed that PrototypeHelper#update_page should return html_safe [DHH]
|
||||
|
||||
* Fixed that much of DateHelper wouldn't return html_safe? strings [DHH]
|
||||
|
||||
* Fixed that fragment caching should return a cache hit as html_safe (or it would all just get escaped) [DHH]
|
||||
|
||||
* Added that ActionController::Base now does helper :all instead of relying on the default ApplicationController in Rails to do it [DHH]
|
||||
|
||||
* Added ActionDispatch::Request#authorization to access the http authentication header regardless of its proxy hiding [DHH]
|
||||
|
||||
* Added :alert, :notice, and :flash as options to ActionController::Base#redirect_to that'll automatically set the proper flash before the redirection [DHH]. Examples:
|
||||
|
|
|
@ -15,4 +15,5 @@ module AbstractController
|
|||
autoload :LocalizedCache
|
||||
autoload :Logger
|
||||
autoload :Rendering
|
||||
autoload :UrlFor
|
||||
end
|
||||
|
|
|
@ -86,6 +86,11 @@ module AbstractController
|
|||
|
||||
abstract!
|
||||
|
||||
# Initialize controller with nil formats.
|
||||
def initialize #:nodoc:
|
||||
@_formats = nil
|
||||
end
|
||||
|
||||
# Calls the action going through the entire action dispatch stack.
|
||||
#
|
||||
# The actual method that is called is determined by calling
|
||||
|
|
|
@ -17,12 +17,6 @@ module AbstractController
|
|||
self._view_paths ||= ActionView::PathSet.new
|
||||
end
|
||||
|
||||
# Initialize controller with nil formats.
|
||||
def initialize(*) #:nodoc:
|
||||
@_formats = nil
|
||||
super
|
||||
end
|
||||
|
||||
# An instance of a view class. The default view class is ActionView::Base
|
||||
#
|
||||
# The view class must have the following methods:
|
||||
|
|
|
@ -0,0 +1,156 @@
|
|||
module AbstractController
|
||||
# In <b>routes.rb</b> one defines URL-to-controller mappings, but the reverse
|
||||
# is also possible: an URL can be generated from one of your routing definitions.
|
||||
# URL generation functionality is centralized in this module.
|
||||
#
|
||||
# See AbstractController::Routing and AbstractController::Resources for general
|
||||
# information about routing and routes.rb.
|
||||
#
|
||||
# <b>Tip:</b> If you need to generate URLs from your models or some other place,
|
||||
# then AbstractController::UrlFor is what you're looking for. Read on for
|
||||
# an introduction.
|
||||
#
|
||||
# == URL generation from parameters
|
||||
#
|
||||
# As you may know, some functions - such as AbstractController::Base#url_for
|
||||
# and ActionView::Helpers::UrlHelper#link_to, can generate URLs given a set
|
||||
# of parameters. For example, you've probably had the chance to write code
|
||||
# like this in one of your views:
|
||||
#
|
||||
# <%= link_to('Click here', :controller => 'users',
|
||||
# :action => 'new', :message => 'Welcome!') %>
|
||||
#
|
||||
# #=> Generates a link to: /users/new?message=Welcome%21
|
||||
#
|
||||
# link_to, and all other functions that require URL generation functionality,
|
||||
# actually use AbstractController::UrlFor under the hood. And in particular,
|
||||
# they use the AbstractController::UrlFor#url_for method. One can generate
|
||||
# the same path as the above example by using the following code:
|
||||
#
|
||||
# include UrlFor
|
||||
# url_for(:controller => 'users',
|
||||
# :action => 'new',
|
||||
# :message => 'Welcome!',
|
||||
# :only_path => true)
|
||||
# # => "/users/new?message=Welcome%21"
|
||||
#
|
||||
# Notice the <tt>:only_path => true</tt> part. This is because UrlFor has no
|
||||
# information about the website hostname that your Rails app is serving. So if you
|
||||
# want to include the hostname as well, then you must also pass the <tt>:host</tt>
|
||||
# argument:
|
||||
#
|
||||
# include UrlFor
|
||||
# url_for(:controller => 'users',
|
||||
# :action => 'new',
|
||||
# :message => 'Welcome!',
|
||||
# :host => 'www.example.com') # Changed this.
|
||||
# # => "http://www.example.com/users/new?message=Welcome%21"
|
||||
#
|
||||
# By default, all controllers and views have access to a special version of url_for,
|
||||
# that already knows what the current hostname is. So if you use url_for in your
|
||||
# controllers or your views, then you don't need to explicitly pass the <tt>:host</tt>
|
||||
# argument.
|
||||
#
|
||||
# For convenience reasons, mailers provide a shortcut for AbstractController::UrlFor#url_for.
|
||||
# So within mailers, you only have to type 'url_for' instead of 'AbstractController::UrlFor#url_for'
|
||||
# in full. However, mailers don't have hostname information, and what's why you'll still
|
||||
# have to specify the <tt>:host</tt> argument when generating URLs in mailers.
|
||||
#
|
||||
#
|
||||
# == URL generation for named routes
|
||||
#
|
||||
# UrlFor also allows one to access methods that have been auto-generated from
|
||||
# named routes. For example, suppose that you have a 'users' resource in your
|
||||
# <b>routes.rb</b>:
|
||||
#
|
||||
# map.resources :users
|
||||
#
|
||||
# This generates, among other things, the method <tt>users_path</tt>. By default,
|
||||
# this method is accessible from your controllers, views and mailers. If you need
|
||||
# to access this auto-generated method from other places (such as a model), then
|
||||
# you can do that by including AbstractController::UrlFor in your class:
|
||||
#
|
||||
# class User < ActiveRecord::Base
|
||||
# include AbstractController::UrlFor
|
||||
#
|
||||
# def base_uri
|
||||
# user_path(self)
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# User.find(1).base_uri # => "/users/1"
|
||||
#
|
||||
module UrlFor
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
ActionController::Routing::Routes.install_helpers(self)
|
||||
extlib_inheritable_accessor :default_url_options,
|
||||
:instance_writer => false, :instance_reader => false
|
||||
self.default_url_options ||= {}
|
||||
end
|
||||
|
||||
# Overwrite to implement a number of default options that all url_for-based methods will use. The default options should come in
|
||||
# the form of a hash, just like the one you would use for url_for directly. Example:
|
||||
#
|
||||
# def default_url_options(options)
|
||||
# { :project => @project.active? ? @project.url_name : "unknown" }
|
||||
# end
|
||||
#
|
||||
# As you can infer from the example, this is mostly useful for situations where you want to centralize dynamic decisions about the
|
||||
# urls as they stem from the business domain. Please note that any individual url_for call can always override the defaults set
|
||||
# by this method.
|
||||
def default_url_options(options = nil)
|
||||
self.class.default_url_options
|
||||
end
|
||||
|
||||
def rewrite_options(options) #:nodoc:
|
||||
if options.delete(:use_defaults) != false && (defaults = default_url_options(options))
|
||||
defaults.merge(options)
|
||||
else
|
||||
options
|
||||
end
|
||||
end
|
||||
|
||||
# Generate a url based on the options provided, default_url_options and the
|
||||
# routes defined in routes.rb. The following options are supported:
|
||||
#
|
||||
# * <tt>:only_path</tt> - If true, the relative url is returned. Defaults to +false+.
|
||||
# * <tt>:protocol</tt> - The protocol to connect to. Defaults to 'http'.
|
||||
# * <tt>:host</tt> - Specifies the host the link should be targeted at.
|
||||
# If <tt>:only_path</tt> is false, this option must be
|
||||
# provided either explicitly, or via +default_url_options+.
|
||||
# * <tt>:port</tt> - Optionally specify the port to connect to.
|
||||
# * <tt>:anchor</tt> - An anchor name to be appended to the path.
|
||||
# * <tt>:skip_relative_url_root</tt> - If true, the url is not constructed using the
|
||||
# +relative_url_root+ set in AbstractController::Base.relative_url_root.
|
||||
# * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in "/archive/2009/"
|
||||
#
|
||||
# Any other key (<tt>:controller</tt>, <tt>:action</tt>, etc.) given to
|
||||
# +url_for+ is forwarded to the Routes module.
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :port=>'8080' # => 'http://somehost.org:8080/tasks/testing'
|
||||
# url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :anchor => 'ok', :only_path => true # => '/tasks/testing#ok'
|
||||
# url_for :controller => 'tasks', :action => 'testing', :trailing_slash=>true # => 'http://somehost.org/tasks/testing/'
|
||||
# url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :number => '33' # => 'http://somehost.org/tasks/testing?number=33'
|
||||
def url_for(options = {})
|
||||
options ||= {}
|
||||
case options
|
||||
when String
|
||||
options
|
||||
when Hash
|
||||
_url_rewriter.rewrite(rewrite_options(options))
|
||||
else
|
||||
polymorphic_url(options)
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def _url_rewriter
|
||||
ActionController::UrlRewriter
|
||||
end
|
||||
end
|
||||
end
|
|
@ -23,31 +23,32 @@ module ActionController
|
|||
autoload :Helpers
|
||||
autoload :HideActions
|
||||
autoload :HttpAuthentication
|
||||
autoload :Logger
|
||||
autoload :Instrumentation
|
||||
autoload :MimeResponds
|
||||
autoload :RackDelegation
|
||||
autoload :Redirecting
|
||||
autoload :Rendering
|
||||
autoload :Renderers
|
||||
autoload :Rendering
|
||||
autoload :RequestForgeryProtection
|
||||
autoload :Rescue
|
||||
autoload :Responder
|
||||
autoload :SessionManagement
|
||||
autoload :Streaming
|
||||
autoload :Testing
|
||||
autoload :UrlFor
|
||||
autoload :Verification
|
||||
end
|
||||
|
||||
autoload :Dispatcher, 'action_controller/dispatch/dispatcher'
|
||||
autoload :PerformanceTest, 'action_controller/deprecated/performance_test'
|
||||
autoload :Routing, 'action_controller/deprecated'
|
||||
autoload :Dispatcher, 'action_controller/deprecated/dispatcher'
|
||||
autoload :Integration, 'action_controller/deprecated/integration_test'
|
||||
autoload :IntegrationTest, 'action_controller/deprecated/integration_test'
|
||||
autoload :PerformanceTest, 'action_controller/deprecated/performance_test'
|
||||
autoload :Routing, 'action_controller/deprecated'
|
||||
autoload :TestCase, 'action_controller/test_case'
|
||||
|
||||
eager_autoload do
|
||||
autoload :RecordIdentifier
|
||||
autoload :UrlRewriter
|
||||
autoload :UrlWriter, 'action_controller/url_rewriter'
|
||||
|
||||
# TODO: Don't autoload exceptions, setup explicit
|
||||
# requires for files that need them
|
||||
|
|
|
@ -6,6 +6,8 @@ module ActionController
|
|||
include AbstractController::Layouts
|
||||
|
||||
include ActionController::Helpers
|
||||
helper :all # By default, all helpers should be included
|
||||
|
||||
include ActionController::HideActions
|
||||
include ActionController::UrlFor
|
||||
include ActionController::Redirecting
|
||||
|
@ -13,7 +15,6 @@ module ActionController
|
|||
include ActionController::Renderers::All
|
||||
include ActionController::ConditionalGet
|
||||
include ActionController::RackDelegation
|
||||
include ActionController::Logger
|
||||
include ActionController::Configuration
|
||||
|
||||
# Legacy modules
|
||||
|
@ -31,9 +32,13 @@ module ActionController
|
|||
include ActionController::Streaming
|
||||
include ActionController::HttpAuthentication::Basic::ControllerMethods
|
||||
include ActionController::HttpAuthentication::Digest::ControllerMethods
|
||||
include ActionController::FilterParameterLogging
|
||||
include ActionController::Translation
|
||||
|
||||
# Add instrumentations hooks at the bottom, to ensure they instrument
|
||||
# all the methods properly.
|
||||
include ActionController::Instrumentation
|
||||
include ActionController::FilterParameterLogging
|
||||
|
||||
# TODO: Extract into its own module
|
||||
# This should be moved together with other normalizing behavior
|
||||
module ImplicitRender
|
||||
|
|
|
@ -60,17 +60,6 @@ module ActionController #:nodoc:
|
|||
def cache_configured?
|
||||
perform_caching && cache_store
|
||||
end
|
||||
|
||||
def log_event(name, before, after, instrumenter_id, payload)
|
||||
if name.to_s =~ /(read|write|cache|expire|exist)_(fragment|page)\??/
|
||||
key_or_path = payload[:key] || payload[:path]
|
||||
human_name = name.to_s.humanize
|
||||
duration = (after - before) * 1000
|
||||
logger.info("#{human_name} #{key_or_path.inspect} (%.1fms)" % duration)
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def caching_allowed?
|
||||
|
|
|
@ -36,8 +36,8 @@ module ActionController #:nodoc:
|
|||
|
||||
def fragment_for(buffer, name = {}, options = nil, &block) #:nodoc:
|
||||
if perform_caching
|
||||
if fragment_exist?(name,options)
|
||||
buffer.concat(read_fragment(name, options))
|
||||
if fragment_exist?(name, options)
|
||||
buffer.safe_concat(read_fragment(name, options))
|
||||
else
|
||||
pos = buffer.length
|
||||
block.call
|
||||
|
@ -53,7 +53,7 @@ module ActionController #:nodoc:
|
|||
return content unless cache_configured?
|
||||
key = fragment_cache_key(key)
|
||||
|
||||
ActiveSupport::Notifications.instrument(:write_fragment, :key => key) do
|
||||
instrument_fragment_cache :write_fragment, key do
|
||||
cache_store.write(key, content, options)
|
||||
end
|
||||
content
|
||||
|
@ -64,7 +64,7 @@ module ActionController #:nodoc:
|
|||
return unless cache_configured?
|
||||
key = fragment_cache_key(key)
|
||||
|
||||
ActiveSupport::Notifications.instrument(:read_fragment, :key => key) do
|
||||
instrument_fragment_cache :read_fragment, key do
|
||||
cache_store.read(key, options)
|
||||
end
|
||||
end
|
||||
|
@ -74,7 +74,7 @@ module ActionController #:nodoc:
|
|||
return unless cache_configured?
|
||||
key = fragment_cache_key(key)
|
||||
|
||||
ActiveSupport::Notifications.instrument(:exist_fragment?, :key => key) do
|
||||
instrument_fragment_cache :exist_fragment?, key do
|
||||
cache_store.exist?(key, options)
|
||||
end
|
||||
end
|
||||
|
@ -101,16 +101,18 @@ module ActionController #:nodoc:
|
|||
key = fragment_cache_key(key) unless key.is_a?(Regexp)
|
||||
message = nil
|
||||
|
||||
ActiveSupport::Notifications.instrument(:expire_fragment, :key => key) do
|
||||
instrument_fragment_cache :expire_fragment, key do
|
||||
if key.is_a?(Regexp)
|
||||
message = "Expired fragments matching: #{key.source}"
|
||||
cache_store.delete_matched(key, options)
|
||||
else
|
||||
message = "Expired fragment: #{key}"
|
||||
cache_store.delete(key, options)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def instrument_fragment_cache(name, key)
|
||||
ActiveSupport::Notifications.instrument("action_controller.#{name}", :key => key){ yield }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -64,7 +64,7 @@ module ActionController #:nodoc:
|
|||
return unless perform_caching
|
||||
path = page_cache_path(path)
|
||||
|
||||
ActiveSupport::Notifications.instrument(:expire_page, :path => path) do
|
||||
instrument_page_cache :expire_page, path do
|
||||
File.delete(path) if File.exist?(path)
|
||||
end
|
||||
end
|
||||
|
@ -75,7 +75,7 @@ module ActionController #:nodoc:
|
|||
return unless perform_caching
|
||||
path = page_cache_path(path)
|
||||
|
||||
ActiveSupport::Notifications.instrument(:cache_page, :path => path) do
|
||||
instrument_page_cache :write_page, path do
|
||||
FileUtils.makedirs(File.dirname(path))
|
||||
File.open(path, "wb+") { |f| f.write(content) }
|
||||
end
|
||||
|
@ -107,6 +107,10 @@ module ActionController #:nodoc:
|
|||
def page_cache_path(path)
|
||||
page_cache_directory + page_cache_file(path)
|
||||
end
|
||||
|
||||
def instrument_page_cache(name, path)
|
||||
ActiveSupport::Notifications.instrument("action_controller.#{name}", :path => path){ yield }
|
||||
end
|
||||
end
|
||||
|
||||
# Expires the page that was cached with the +options+ as a key. Example:
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
module ActionController
|
||||
class Dispatcher
|
||||
cattr_accessor :prepare_each_request
|
||||
self.prepare_each_request = false
|
||||
|
||||
class << self
|
||||
def before_dispatch(*args, &block)
|
||||
ActiveSupport::Deprecation.warn "ActionController::Dispatcher.before_dispatch is deprecated. " <<
|
||||
"Please use ActionDispatch::Callbacks.before instead.", caller
|
||||
ActionDispatch::Callbacks.before(*args, &block)
|
||||
end
|
||||
|
||||
def after_dispatch(*args, &block)
|
||||
ActiveSupport::Deprecation.warn "ActionController::Dispatcher.after_dispatch is deprecated. " <<
|
||||
"Please use ActionDispatch::Callbacks.after instead.", caller
|
||||
ActionDispatch::Callbacks.after(*args, &block)
|
||||
end
|
||||
|
||||
def to_prepare(*args, &block)
|
||||
ActiveSupport::Deprecation.warn "ActionController::Dispatcher.to_prepare is deprecated. " <<
|
||||
"Please use ActionDispatch::Callbacks.to_prepare instead.", caller
|
||||
ActionDispatch::Callbacks.after(*args, &block)
|
||||
end
|
||||
|
||||
def new
|
||||
ActiveSupport::Deprecation.warn "ActionController::Dispatcher.new is deprecated, use Rails.application instead."
|
||||
Rails.application
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,52 +0,0 @@
|
|||
require 'active_support/core_ext/module/delegation'
|
||||
|
||||
module ActionController
|
||||
# Dispatches requests to the appropriate controller and takes care of
|
||||
# reloading the app after each request when Dependencies.load? is true.
|
||||
class Dispatcher
|
||||
cattr_accessor :prepare_each_request
|
||||
self.prepare_each_request = false
|
||||
|
||||
class << self
|
||||
def define_dispatcher_callbacks(cache_classes)
|
||||
unless cache_classes
|
||||
# Run prepare callbacks before every request in development mode
|
||||
self.prepare_each_request = true
|
||||
|
||||
ActionDispatch::Callbacks.after_dispatch do
|
||||
# Cleanup the application before processing the current request.
|
||||
ActiveRecord::Base.reset_subclasses if defined?(ActiveRecord)
|
||||
ActiveSupport::Dependencies.clear
|
||||
ActiveRecord::Base.clear_reloadable_connections! if defined?(ActiveRecord)
|
||||
end
|
||||
|
||||
ActionView::Helpers::AssetTagHelper.cache_asset_timestamps = false
|
||||
end
|
||||
|
||||
if defined?(ActiveRecord)
|
||||
to_prepare(:activerecord_instantiate_observers) do
|
||||
ActiveRecord::Base.instantiate_observers
|
||||
end
|
||||
end
|
||||
|
||||
if Base.logger && Base.logger.respond_to?(:flush)
|
||||
after_dispatch do
|
||||
Base.logger.flush
|
||||
end
|
||||
end
|
||||
|
||||
to_prepare do
|
||||
I18n.reload!
|
||||
end
|
||||
end
|
||||
|
||||
delegate :to_prepare, :before_dispatch, :around_dispatch, :after_dispatch,
|
||||
:to => ActionDispatch::Callbacks
|
||||
|
||||
def new
|
||||
# DEPRECATE Rails application fallback
|
||||
Rails.application
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -21,6 +21,8 @@ module ActionController
|
|||
|
||||
class << self
|
||||
delegate :default_charset=, :to => "ActionDispatch::Response"
|
||||
delegate :resources_path_names, :to => "ActionController::Routing::Routes"
|
||||
delegate :resources_path_names=, :to => "ActionController::Routing::Routes"
|
||||
end
|
||||
|
||||
# cattr_reader :protected_instance_variables
|
||||
|
@ -29,15 +31,7 @@ module ActionController
|
|||
@variables_added @request_origin @url
|
||||
@parent_controller @action_name
|
||||
@before_filter_chain_aborted @_headers @_params
|
||||
@_flash @_response)
|
||||
|
||||
# Indicates whether or not optimise the generated named
|
||||
# route helper methods
|
||||
cattr_accessor :optimise_named_routes
|
||||
self.optimise_named_routes = true
|
||||
|
||||
cattr_accessor :resources_path_names
|
||||
self.resources_path_names = { :new => 'new', :edit => 'edit' }
|
||||
@_response)
|
||||
|
||||
# Controls the resource action separator
|
||||
cattr_accessor :resource_action_separator
|
||||
|
|
|
@ -2,6 +2,8 @@ module ActionController
|
|||
module FilterParameterLogging
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
INTERNAL_PARAMS = %w(controller action format _method only_path)
|
||||
|
||||
module ClassMethods
|
||||
# Replace sensitive parameter data from the request log.
|
||||
# Filters parameters that have any of the arguments as a substring.
|
||||
|
@ -48,27 +50,19 @@ module ActionController
|
|||
filtered_params[key] = value
|
||||
end
|
||||
|
||||
filtered_params
|
||||
filtered_params.except!(*INTERNAL_PARAMS)
|
||||
end
|
||||
protected :filter_parameters
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Overwrite log_process_action to include parameters information.
|
||||
# If this method is invoked, it means logger is defined, so don't
|
||||
# worry with such scenario here.
|
||||
def log_process_action(controller) #:nodoc:
|
||||
params = controller.send(:filter_parameters, controller.request.params)
|
||||
logger.info " Parameters: #{params.inspect}" unless params.empty?
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
INTERNAL_PARAMS = [:controller, :action, :format, :_method, :only_path]
|
||||
|
||||
protected
|
||||
|
||||
def append_info_to_payload(payload)
|
||||
super
|
||||
payload[:params] = filter_parameters(request.params)
|
||||
end
|
||||
|
||||
def filter_parameters(params)
|
||||
params.dup.except!(*INTERNAL_PARAMS)
|
||||
end
|
||||
|
|
|
@ -1,187 +1,14 @@
|
|||
module ActionController #:nodoc:
|
||||
# The flash provides a way to pass temporary objects between actions. Anything you place in the flash will be exposed
|
||||
# to the very next action and then cleared out. This is a great way of doing notices and alerts, such as a create
|
||||
# action that sets <tt>flash[:notice] = "Successfully created"</tt> before redirecting to a display action that can
|
||||
# then expose the flash to its template. Actually, that exposure is automatically done. Example:
|
||||
#
|
||||
# class PostsController < ActionController::Base
|
||||
# def create
|
||||
# # save post
|
||||
# flash[:notice] = "Successfully created post"
|
||||
# redirect_to @post
|
||||
# end
|
||||
#
|
||||
# def show
|
||||
# # doesn't need to assign the flash notice to the template, that's done automatically
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# show.html.erb
|
||||
# <% if flash[:notice] %>
|
||||
# <div class="notice"><%= flash[:notice] %></div>
|
||||
# <% end %>
|
||||
#
|
||||
# This example just places a string in the flash, but you can put any object in there. And of course, you can put as
|
||||
# many as you like at a time too. Just remember: They'll be gone by the time the next action has been performed.
|
||||
#
|
||||
# See docs on the FlashHash class for more details about the flash.
|
||||
module Flash
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
delegate :flash, :to => :request
|
||||
delegate :alert, :notice, :to => "request.flash"
|
||||
helper_method :alert, :notice
|
||||
end
|
||||
|
||||
class FlashNow #:nodoc:
|
||||
def initialize(flash)
|
||||
@flash = flash
|
||||
end
|
||||
|
||||
def []=(k, v)
|
||||
@flash[k] = v
|
||||
@flash.discard(k)
|
||||
v
|
||||
end
|
||||
|
||||
def [](k)
|
||||
@flash[k]
|
||||
end
|
||||
end
|
||||
|
||||
class FlashHash < Hash
|
||||
def initialize #:nodoc:
|
||||
super
|
||||
@used = Set.new
|
||||
end
|
||||
|
||||
def []=(k, v) #:nodoc:
|
||||
keep(k)
|
||||
super
|
||||
end
|
||||
|
||||
def update(h) #:nodoc:
|
||||
h.keys.each { |k| keep(k) }
|
||||
super
|
||||
end
|
||||
|
||||
alias :merge! :update
|
||||
|
||||
def replace(h) #:nodoc:
|
||||
@used = Set.new
|
||||
super
|
||||
end
|
||||
|
||||
# Sets a flash that will not be available to the next action, only to the current.
|
||||
#
|
||||
# flash.now[:message] = "Hello current action"
|
||||
#
|
||||
# This method enables you to use the flash as a central messaging system in your app.
|
||||
# When you need to pass an object to the next action, you use the standard flash assign (<tt>[]=</tt>).
|
||||
# When you need to pass an object to the current action, you use <tt>now</tt>, and your object will
|
||||
# vanish when the current action is done.
|
||||
#
|
||||
# Entries set via <tt>now</tt> are accessed the same way as standard entries: <tt>flash['my-key']</tt>.
|
||||
def now
|
||||
FlashNow.new(self)
|
||||
end
|
||||
|
||||
# Keeps either the entire current flash or a specific flash entry available for the next action:
|
||||
#
|
||||
# flash.keep # keeps the entire flash
|
||||
# flash.keep(:notice) # keeps only the "notice" entry, the rest of the flash is discarded
|
||||
def keep(k = nil)
|
||||
use(k, false)
|
||||
end
|
||||
|
||||
# Marks the entire flash or a single flash entry to be discarded by the end of the current action:
|
||||
#
|
||||
# flash.discard # discard the entire flash at the end of the current action
|
||||
# flash.discard(:warning) # discard only the "warning" entry at the end of the current action
|
||||
def discard(k = nil)
|
||||
use(k)
|
||||
end
|
||||
|
||||
# Mark for removal entries that were kept, and delete unkept ones.
|
||||
#
|
||||
# This method is called automatically by filters, so you generally don't need to care about it.
|
||||
def sweep #:nodoc:
|
||||
keys.each do |k|
|
||||
unless @used.include?(k)
|
||||
@used << k
|
||||
else
|
||||
delete(k)
|
||||
@used.delete(k)
|
||||
end
|
||||
end
|
||||
|
||||
# clean up after keys that could have been left over by calling reject! or shift on the flash
|
||||
(@used - keys).each{ |k| @used.delete(k) }
|
||||
end
|
||||
|
||||
def store(session)
|
||||
return if self.empty?
|
||||
session["flash"] = self
|
||||
end
|
||||
|
||||
private
|
||||
# Used internally by the <tt>keep</tt> and <tt>discard</tt> methods
|
||||
# use() # marks the entire flash as used
|
||||
# use('msg') # marks the "msg" entry as used
|
||||
# use(nil, false) # marks the entire flash as unused (keeps it around for one more action)
|
||||
# use('msg', false) # marks the "msg" entry as unused (keeps it around for one more action)
|
||||
# Returns the single value for the key you asked to be marked (un)used or the FlashHash itself
|
||||
# if no key is passed.
|
||||
def use(key = nil, used = true)
|
||||
Array(key || keys).each { |k| used ? @used << k : @used.delete(k) }
|
||||
return key ? self[key] : self
|
||||
end
|
||||
end
|
||||
|
||||
# Access the contents of the flash. Use <tt>flash["notice"]</tt> to
|
||||
# read a notice you put there or <tt>flash["notice"] = "hello"</tt>
|
||||
# to put a new one.
|
||||
def flash #:doc:
|
||||
unless @_flash
|
||||
@_flash = session["flash"] || FlashHash.new
|
||||
@_flash.sweep
|
||||
end
|
||||
|
||||
@_flash
|
||||
end
|
||||
|
||||
# Convenience accessor for flash[:alert]
|
||||
def alert
|
||||
flash[:alert]
|
||||
end
|
||||
|
||||
# Convenience accessor for flash[:alert]=
|
||||
def alert=(message)
|
||||
flash[:alert] = message
|
||||
end
|
||||
|
||||
# Convenience accessor for flash[:notice]
|
||||
def notice
|
||||
flash[:notice]
|
||||
end
|
||||
|
||||
# Convenience accessor for flash[:notice]=
|
||||
def notice=(message)
|
||||
flash[:notice] = message
|
||||
end
|
||||
|
||||
protected
|
||||
def process_action(method_name)
|
||||
@_flash = nil
|
||||
super
|
||||
@_flash.store(session) if @_flash
|
||||
@_flash = nil
|
||||
end
|
||||
|
||||
def reset_session
|
||||
super
|
||||
@_flash = nil
|
||||
end
|
||||
|
||||
def redirect_to(options = {}, response_status_and_flash = {}) #:doc:
|
||||
if alert = response_status_and_flash.delete(:alert)
|
||||
flash[:alert] = alert
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
module ActionController
|
||||
module Head
|
||||
include UrlFor
|
||||
extend ActiveSupport::Concern
|
||||
include ActionController::UrlFor
|
||||
|
||||
# Return a response that has no content (merely headers). The options
|
||||
# argument is interpreted to be a hash of header names and values.
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
require 'abstract_controller/logger'
|
||||
|
||||
module ActionController
|
||||
# Adds instrumentation to several ends in ActionController::Base. It also provides
|
||||
# some hooks related with process_action, this allows an ORM like ActiveRecord
|
||||
# and/or DataMapper to plug in ActionController and show related information.
|
||||
#
|
||||
# Check ActiveRecord::Railties::ControllerRuntime for an example.
|
||||
module Instrumentation
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
include AbstractController::Logger
|
||||
end
|
||||
|
||||
attr_internal :view_runtime
|
||||
|
||||
def process_action(action, *args)
|
||||
ActiveSupport::Notifications.instrument("action_controller.process_action") do |payload|
|
||||
result = super
|
||||
payload[:controller] = self.class.name
|
||||
payload[:action] = self.action_name
|
||||
payload[:formats] = request.formats.map(&:to_s)
|
||||
payload[:remote_ip] = request.remote_ip
|
||||
payload[:method] = request.method
|
||||
payload[:status] = response.status
|
||||
payload[:request_uri] = request.request_uri rescue "unknown"
|
||||
append_info_to_payload(payload)
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
def render(*args, &block)
|
||||
if logger
|
||||
render_output = nil
|
||||
|
||||
self.view_runtime = cleanup_view_runtime do
|
||||
Benchmark.ms { render_output = super }
|
||||
end
|
||||
|
||||
render_output
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def send_file(path, options={})
|
||||
ActiveSupport::Notifications.instrument("action_controller.send_file",
|
||||
options.merge(:path => path)) do
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def send_data(data, options = {})
|
||||
ActiveSupport::Notifications.instrument("action_controller.send_data", options) do
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def redirect_to(*args)
|
||||
ActiveSupport::Notifications.instrument("action_controller.redirect_to") do |payload|
|
||||
result = super
|
||||
payload[:status] = self.status
|
||||
payload[:location] = self.location
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# A hook which allows you to clean up any time taken into account in
|
||||
# views wrongly, like database querying time.
|
||||
#
|
||||
# def cleanup_view_runtime
|
||||
# super - time_taken_in_something_expensive
|
||||
# end
|
||||
#
|
||||
# :api: plugin
|
||||
def cleanup_view_runtime #:nodoc:
|
||||
yield
|
||||
end
|
||||
|
||||
# Everytime after an action is processed, this method is invoked
|
||||
# with the payload, so you can add more information.
|
||||
# :api: plugin
|
||||
def append_info_to_payload(payload) #:nodoc:
|
||||
payload[:view_runtime] = view_runtime
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# A hook which allows other frameworks to log what happened during
|
||||
# controller process action. This method should return an array
|
||||
# with the messages to be added.
|
||||
# :api: plugin
|
||||
def log_process_action(payload) #:nodoc:
|
||||
messages, view_runtime = [], payload[:view_runtime]
|
||||
messages << ("Views: %.1fms" % view_runtime.to_f) if view_runtime
|
||||
messages
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,89 +0,0 @@
|
|||
require 'abstract_controller/logger'
|
||||
|
||||
module ActionController
|
||||
# Adds instrumentation to <tt>process_action</tt> and a <tt>log_event</tt> method
|
||||
# responsible to log events from ActiveSupport::Notifications. This module handles
|
||||
# :process_action and :render_template events but allows any other module to hook
|
||||
# into log_event and provide its own logging facilities (as in ActionController::Caching).
|
||||
module Logger
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
include AbstractController::Logger
|
||||
end
|
||||
|
||||
attr_internal :view_runtime
|
||||
|
||||
def process_action(action)
|
||||
ActiveSupport::Notifications.instrument(:process_action, :controller => self, :action => action) do
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def render(*args, &block)
|
||||
if logger
|
||||
render_output = nil
|
||||
|
||||
self.view_runtime = cleanup_view_runtime do
|
||||
Benchmark.ms { render_output = super }
|
||||
end
|
||||
|
||||
render_output
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
# If you want to remove any time taken into account in :view_runtime
|
||||
# wrongly, you can do it here:
|
||||
#
|
||||
# def cleanup_view_runtime
|
||||
# super - time_taken_in_something_expensive
|
||||
# end
|
||||
#
|
||||
# :api: plugin
|
||||
def cleanup_view_runtime #:nodoc:
|
||||
yield
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# This is the hook invoked by ActiveSupport::Notifications.subscribe.
|
||||
# If you need to log any event, overwrite the method and do it here.
|
||||
def log_event(name, before, after, instrumenter_id, payload) #:nodoc:
|
||||
if name == :process_action
|
||||
duration = [(after - before) * 1000, 0.01].max
|
||||
controller = payload[:controller]
|
||||
request = controller.request
|
||||
|
||||
logger.info "\n\nProcessed #{controller.class.name}##{payload[:action]} " \
|
||||
"to #{request.formats} (for #{request.remote_ip} at #{before.to_s(:db)}) " \
|
||||
"[#{request.method.to_s.upcase}]"
|
||||
|
||||
log_process_action(controller)
|
||||
|
||||
message = "Completed in %.0fms" % duration
|
||||
message << " | #{controller.response.status}"
|
||||
message << " [#{request.request_uri rescue "unknown"}]"
|
||||
|
||||
logger.info(message)
|
||||
elsif name == :render_template
|
||||
# TODO Make render_template logging work if you are using just ActionView
|
||||
duration = (after - before) * 1000
|
||||
message = "Rendered #{payload[:identifier]}"
|
||||
message << " within #{payload[:layout]}" if payload[:layout]
|
||||
message << (" (%.1fms)" % duration)
|
||||
logger.info(message)
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# A hook which allows logging what happened during controller process action.
|
||||
# :api: plugin
|
||||
def log_process_action(controller) #:nodoc:
|
||||
view_runtime = controller.send :view_runtime
|
||||
logger.info(" View runtime: %.1fms" % view_runtime.to_f) if view_runtime
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -215,7 +215,10 @@ module ActionController #:nodoc:
|
|||
# a proc to it.
|
||||
#
|
||||
def respond_with(*resources, &block)
|
||||
if response = retrieve_response_from_mimes([], &block)
|
||||
raise "In order to use respond_with, first you need to declare the formats your " <<
|
||||
"controller responds to in the class level" if mimes_for_respond_to.empty?
|
||||
|
||||
if response = retrieve_response_from_mimes(&block)
|
||||
options = resources.extract_options!
|
||||
options.merge!(:default_response => response)
|
||||
(options.delete(:responder) || responder).call(self, resources, options)
|
||||
|
@ -246,9 +249,9 @@ module ActionController #:nodoc:
|
|||
# Collects mimes and return the response for the negotiated format. Returns
|
||||
# nil if :not_acceptable was sent to the client.
|
||||
#
|
||||
def retrieve_response_from_mimes(mimes, &block)
|
||||
def retrieve_response_from_mimes(mimes=nil, &block)
|
||||
collector = Collector.new { default_render }
|
||||
mimes = collect_mimes_from_class_level if mimes.empty?
|
||||
mimes ||= collect_mimes_from_class_level
|
||||
mimes.each { |mime| collector.send(mime) }
|
||||
block.call(collector) if block_given?
|
||||
|
||||
|
|
|
@ -9,7 +9,9 @@ module ActionController
|
|||
|
||||
module Redirecting
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
include AbstractController::Logger
|
||||
include ActionController::UrlFor
|
||||
|
||||
# Redirects the browser to the target specified in +options+. This parameter can take one of three forms:
|
||||
#
|
||||
|
@ -55,8 +57,6 @@ module ActionController
|
|||
self.status = _extract_redirect_to_status(options, response_status)
|
||||
self.location = _compute_redirect_to_location(options)
|
||||
self.response_body = "<html><body>You are being <a href=\"#{ERB::Util.h(location)}\">redirected</a>.</body></html>"
|
||||
|
||||
logger.info("Redirected to #{location}") if logger && logger.info?
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -88,13 +88,11 @@ module ActionController #:nodoc:
|
|||
@performed_render = false
|
||||
|
||||
if options[:x_sendfile]
|
||||
logger.info "Sending #{X_SENDFILE_HEADER} header #{path}" if logger
|
||||
head options[:status], X_SENDFILE_HEADER => path
|
||||
else
|
||||
if options[:stream]
|
||||
# TODO : Make render :text => proc {} work with the new base
|
||||
render :status => options[:status], :text => Proc.new { |response, output|
|
||||
logger.info "Streaming file #{path}" unless logger.nil?
|
||||
len = options[:buffer_size] || 4096
|
||||
File.open(path, 'rb') do |file|
|
||||
while buf = file.read(len)
|
||||
|
@ -103,7 +101,6 @@ module ActionController #:nodoc:
|
|||
end
|
||||
}
|
||||
else
|
||||
logger.info "Sending file #{path}" unless logger.nil?
|
||||
File.open(path, 'rb') { |file| render :status => options[:status], :text => file.read }
|
||||
end
|
||||
end
|
||||
|
@ -141,7 +138,6 @@ module ActionController #:nodoc:
|
|||
# data to the browser, then use <tt>render :text => proc { ... }</tt>
|
||||
# instead. See ActionController::Base#render for more information.
|
||||
def send_data(data, options = {}) #:doc:
|
||||
logger.info "Sending data #{options[:filename]}" if logger
|
||||
send_file_headers! options.merge(:length => data.bytesize)
|
||||
render :status => options[:status], :text => data
|
||||
end
|
||||
|
|
|
@ -2,40 +2,14 @@ module ActionController
|
|||
module UrlFor
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
include RackDelegation
|
||||
include AbstractController::UrlFor
|
||||
include ActionController::RackDelegation
|
||||
|
||||
# Overwrite to implement a number of default options that all url_for-based methods will use. The default options should come in
|
||||
# the form of a hash, just like the one you would use for url_for directly. Example:
|
||||
#
|
||||
# def default_url_options(options)
|
||||
# { :project => @project.active? ? @project.url_name : "unknown" }
|
||||
# end
|
||||
#
|
||||
# As you can infer from the example, this is mostly useful for situations where you want to centralize dynamic decisions about the
|
||||
# urls as they stem from the business domain. Please note that any individual url_for call can always override the defaults set
|
||||
# by this method.
|
||||
def default_url_options(options = nil)
|
||||
end
|
||||
protected
|
||||
|
||||
def rewrite_options(options) #:nodoc:
|
||||
if defaults = default_url_options(options)
|
||||
defaults.merge(options)
|
||||
else
|
||||
options
|
||||
end
|
||||
end
|
||||
|
||||
def url_for(options = {})
|
||||
options ||= {}
|
||||
case options
|
||||
when String
|
||||
options
|
||||
when Hash
|
||||
@url ||= UrlRewriter.new(request, params)
|
||||
@url.rewrite(rewrite_options(options))
|
||||
else
|
||||
polymorphic_url(options)
|
||||
end
|
||||
def _url_rewriter
|
||||
return ActionController::UrlRewriter unless request
|
||||
@_url_rewriter ||= ActionController::UrlRewriter.new(request, params)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,6 +5,9 @@ module ActionController
|
|||
class Railtie < Rails::Railtie
|
||||
plugin_name :action_controller
|
||||
|
||||
require "action_controller/railties/subscriber"
|
||||
subscriber ActionController::Railties::Subscriber.new
|
||||
|
||||
initializer "action_controller.set_configs" do |app|
|
||||
app.config.action_controller.each do |k,v|
|
||||
ActionController::Base.send "#{k}=", v
|
||||
|
@ -23,26 +26,6 @@ module ActionController
|
|||
app.reload_routes!
|
||||
end
|
||||
|
||||
# Include middleware to serve up static assets
|
||||
initializer "action_controller.initialize_static_server" do |app|
|
||||
if app.config.serve_static_assets
|
||||
app.config.middleware.use(ActionDispatch::Static, Rails.public_path)
|
||||
end
|
||||
end
|
||||
|
||||
initializer "action_controller.initialize_middleware_stack" do |app|
|
||||
middleware = app.config.middleware
|
||||
middleware.use(::Rack::Lock, :if => lambda { ActionController::Base.allow_concurrency })
|
||||
middleware.use(::Rack::Runtime)
|
||||
middleware.use(ActionDispatch::ShowExceptions, lambda { ActionController::Base.consider_all_requests_local })
|
||||
middleware.use(ActionDispatch::Callbacks, lambda { ActionController::Dispatcher.prepare_each_request })
|
||||
middleware.use(lambda { ActionController::Base.session_store }, lambda { ActionController::Base.session_options })
|
||||
middleware.use(ActionDispatch::ParamsParser)
|
||||
middleware.use(::Rack::MethodOverride)
|
||||
middleware.use(::Rack::Head)
|
||||
middleware.use(ActionDispatch::StringCoercion)
|
||||
end
|
||||
|
||||
initializer "action_controller.initialize_framework_caches" do
|
||||
ActionController::Base.cache_store ||= RAILS_CACHE
|
||||
end
|
||||
|
@ -57,19 +40,41 @@ module ActionController
|
|||
ActionController::Base.view_paths = view_path if ActionController::Base.view_paths.blank?
|
||||
end
|
||||
|
||||
initializer "action_controller.initialize_metal" do |app|
|
||||
Rails::Rack::Metal.requested_metals = app.config.metals
|
||||
class MetalMiddlewareBuilder
|
||||
def initialize(metals)
|
||||
@metals = metals
|
||||
end
|
||||
|
||||
app.config.middleware.insert_before(:"ActionDispatch::ParamsParser",
|
||||
Rails::Rack::Metal, :if => Rails::Rack::Metal.metals.any?)
|
||||
def new(app)
|
||||
ActionDispatch::Cascade.new(@metals, app)
|
||||
end
|
||||
|
||||
def name
|
||||
ActionDispatch::Cascade.name
|
||||
end
|
||||
alias_method :to_s, :name
|
||||
end
|
||||
|
||||
# # Prepare dispatcher callbacks and run 'prepare' callbacks
|
||||
initializer "action_controller.initialize_metal" do |app|
|
||||
metal_root = "#{Rails.root}/app/metal"
|
||||
load_list = app.config.metals || Dir["#{metal_root}/**/*.rb"]
|
||||
|
||||
metals = load_list.map { |metal|
|
||||
metal = File.basename(metal.gsub("#{metal_root}/", ''), '.rb')
|
||||
require_dependency metal
|
||||
metal.camelize.constantize
|
||||
}.compact
|
||||
|
||||
middleware = MetalMiddlewareBuilder.new(metals)
|
||||
app.config.middleware.insert_before(:"ActionDispatch::ParamsParser", middleware)
|
||||
end
|
||||
|
||||
# Prepare dispatcher callbacks and run 'prepare' callbacks
|
||||
initializer "action_controller.prepare_dispatcher" do |app|
|
||||
# TODO: This used to say unless defined?(Dispatcher). Find out why and fix.
|
||||
# Notice that at this point, ActionDispatch::Callbacks were already loaded.
|
||||
require 'rails/dispatcher'
|
||||
|
||||
Dispatcher.define_dispatcher_callbacks(app.config.cache_classes)
|
||||
ActionController::Dispatcher.prepare_each_request = true unless app.config.cache_classes
|
||||
|
||||
unless app.config.cache_classes
|
||||
# Setup dev mode route reloading
|
||||
|
@ -80,15 +85,7 @@ module ActionController
|
|||
app.reload_routes!
|
||||
end
|
||||
end
|
||||
ActionDispatch::Callbacks.before_dispatch { |callbacks| reload_routes.call }
|
||||
end
|
||||
end
|
||||
|
||||
initializer "action_controller.notifications" do |app|
|
||||
require 'active_support/notifications'
|
||||
|
||||
ActiveSupport::Notifications.subscribe do |*args|
|
||||
ActionController::Base.log_event(*args) if ActionController::Base.logger
|
||||
ActionDispatch::Callbacks.before { |callbacks| reload_routes.call }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
module ActionController
|
||||
module Railties
|
||||
class Subscriber < Rails::Subscriber
|
||||
def process_action(event)
|
||||
payload = event.payload
|
||||
|
||||
info "\nProcessed #{payload[:controller]}##{payload[:action]} " \
|
||||
"to #{payload[:formats].join(', ')} (for #{payload[:remote_ip]} at #{event.time.to_s(:db)}) " \
|
||||
"[#{payload[:method].to_s.upcase}]"
|
||||
|
||||
info " Parameters: #{payload[:params].inspect}" unless payload[:params].blank?
|
||||
|
||||
additions = ActionController::Base.log_process_action(payload)
|
||||
|
||||
message = "Completed in %.0fms" % event.duration
|
||||
message << " (#{additions.join(" | ")})" unless additions.blank?
|
||||
message << " | #{payload[:status]} [#{payload[:request_uri]}]\n\n"
|
||||
|
||||
info(message)
|
||||
end
|
||||
|
||||
def send_file(event)
|
||||
message = if event.payload[:x_sendfile]
|
||||
header = ActionController::Streaming::X_SENDFILE_HEADER
|
||||
"Sent #{header} header %s"
|
||||
elsif event.payload[:stream]
|
||||
"Streamed file %s"
|
||||
else
|
||||
"Sent file %s"
|
||||
end
|
||||
|
||||
message << " (%.1fms)"
|
||||
info(message % [event.payload[:path], event.duration])
|
||||
end
|
||||
|
||||
def redirect_to(event)
|
||||
info "Redirected to #{event.payload[:location]}"
|
||||
end
|
||||
|
||||
def send_data(event)
|
||||
info("Sent data %s (%.1fms)" % [event.payload[:filename], event.duration])
|
||||
end
|
||||
|
||||
%w(write_fragment read_fragment exist_fragment?
|
||||
expire_fragment expire_page write_page).each do |method|
|
||||
class_eval <<-METHOD, __FILE__, __LINE__ + 1
|
||||
def #{method}(event)
|
||||
key_or_path = event.payload[:key] || event.payload[:path]
|
||||
human_name = #{method.to_s.humanize.inspect}
|
||||
info("\#{human_name} \#{key_or_path} (%.1fms)" % event.duration)
|
||||
end
|
||||
METHOD
|
||||
end
|
||||
|
||||
def logger
|
||||
ActionController::Base.logger
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,6 +1,5 @@
|
|||
require 'active_support/test_case'
|
||||
require 'rack/session/abstract/id'
|
||||
require 'action_controller/metal/testing'
|
||||
require 'action_view/test_case'
|
||||
|
||||
module ActionController
|
||||
class TestRequest < ActionDispatch::TestRequest #:nodoc:
|
||||
|
@ -240,13 +239,15 @@ module ActionController
|
|||
@request.assign_parameters(@controller.class.name.underscore.sub(/_controller$/, ''), action.to_s, parameters)
|
||||
|
||||
@request.session = ActionController::TestSession.new(session) unless session.nil?
|
||||
@request.session["flash"] = ActionController::Flash::FlashHash.new.update(flash) if flash
|
||||
@request.session["flash"] = @request.flash.update(flash || {})
|
||||
@request.session["flash"].sweep
|
||||
|
||||
@controller.request = @request
|
||||
@controller.params.merge!(parameters)
|
||||
build_request_uri(action, parameters)
|
||||
Base.class_eval { include Testing }
|
||||
@controller.process_with_new_base_test(@request, @response)
|
||||
@request.session.delete('flash') if @request.session['flash'].blank?
|
||||
@response
|
||||
end
|
||||
|
||||
|
|
|
@ -1,153 +1,21 @@
|
|||
require 'active_support/core_ext/hash/except'
|
||||
|
||||
module ActionController
|
||||
# In <b>routes.rb</b> one defines URL-to-controller mappings, but the reverse
|
||||
# is also possible: an URL can be generated from one of your routing definitions.
|
||||
# URL generation functionality is centralized in this module.
|
||||
#
|
||||
# See ActionController::Routing and ActionController::Resources for general
|
||||
# information about routing and routes.rb.
|
||||
#
|
||||
# <b>Tip:</b> If you need to generate URLs from your models or some other place,
|
||||
# then ActionController::UrlWriter is what you're looking for. Read on for
|
||||
# an introduction.
|
||||
#
|
||||
# == URL generation from parameters
|
||||
#
|
||||
# As you may know, some functions - such as ActionController::Base#url_for
|
||||
# and ActionView::Helpers::UrlHelper#link_to, can generate URLs given a set
|
||||
# of parameters. For example, you've probably had the chance to write code
|
||||
# like this in one of your views:
|
||||
#
|
||||
# <%= link_to('Click here', :controller => 'users',
|
||||
# :action => 'new', :message => 'Welcome!') %>
|
||||
#
|
||||
# #=> Generates a link to: /users/new?message=Welcome%21
|
||||
#
|
||||
# link_to, and all other functions that require URL generation functionality,
|
||||
# actually use ActionController::UrlWriter under the hood. And in particular,
|
||||
# they use the ActionController::UrlWriter#url_for method. One can generate
|
||||
# the same path as the above example by using the following code:
|
||||
#
|
||||
# include UrlWriter
|
||||
# url_for(:controller => 'users',
|
||||
# :action => 'new',
|
||||
# :message => 'Welcome!',
|
||||
# :only_path => true)
|
||||
# # => "/users/new?message=Welcome%21"
|
||||
#
|
||||
# Notice the <tt>:only_path => true</tt> part. This is because UrlWriter has no
|
||||
# information about the website hostname that your Rails app is serving. So if you
|
||||
# want to include the hostname as well, then you must also pass the <tt>:host</tt>
|
||||
# argument:
|
||||
#
|
||||
# include UrlWriter
|
||||
# url_for(:controller => 'users',
|
||||
# :action => 'new',
|
||||
# :message => 'Welcome!',
|
||||
# :host => 'www.example.com') # Changed this.
|
||||
# # => "http://www.example.com/users/new?message=Welcome%21"
|
||||
#
|
||||
# By default, all controllers and views have access to a special version of url_for,
|
||||
# that already knows what the current hostname is. So if you use url_for in your
|
||||
# controllers or your views, then you don't need to explicitly pass the <tt>:host</tt>
|
||||
# argument.
|
||||
#
|
||||
# For convenience reasons, mailers provide a shortcut for ActionController::UrlWriter#url_for.
|
||||
# So within mailers, you only have to type 'url_for' instead of 'ActionController::UrlWriter#url_for'
|
||||
# in full. However, mailers don't have hostname information, and what's why you'll still
|
||||
# have to specify the <tt>:host</tt> argument when generating URLs in mailers.
|
||||
#
|
||||
#
|
||||
# == URL generation for named routes
|
||||
#
|
||||
# UrlWriter also allows one to access methods that have been auto-generated from
|
||||
# named routes. For example, suppose that you have a 'users' resource in your
|
||||
# <b>routes.rb</b>:
|
||||
#
|
||||
# map.resources :users
|
||||
#
|
||||
# This generates, among other things, the method <tt>users_path</tt>. By default,
|
||||
# this method is accessible from your controllers, views and mailers. If you need
|
||||
# to access this auto-generated method from other places (such as a model), then
|
||||
# you can do that by including ActionController::UrlWriter in your class:
|
||||
#
|
||||
# class User < ActiveRecord::Base
|
||||
# include ActionController::UrlWriter
|
||||
#
|
||||
# def base_uri
|
||||
# user_path(self)
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# User.find(1).base_uri # => "/users/1"
|
||||
module UrlWriter
|
||||
def self.included(base) #:nodoc:
|
||||
ActionController::Routing::Routes.install_helpers(base)
|
||||
base.mattr_accessor :default_url_options
|
||||
|
||||
# The default options for urls written by this writer. Typically a <tt>:host</tt> pair is provided.
|
||||
base.default_url_options ||= {}
|
||||
end
|
||||
|
||||
# Generate a url based on the options provided, default_url_options and the
|
||||
# routes defined in routes.rb. The following options are supported:
|
||||
#
|
||||
# * <tt>:only_path</tt> - If true, the relative url is returned. Defaults to +false+.
|
||||
# * <tt>:protocol</tt> - The protocol to connect to. Defaults to 'http'.
|
||||
# * <tt>:host</tt> - Specifies the host the link should be targeted at.
|
||||
# If <tt>:only_path</tt> is false, this option must be
|
||||
# provided either explicitly, or via +default_url_options+.
|
||||
# * <tt>:port</tt> - Optionally specify the port to connect to.
|
||||
# * <tt>:anchor</tt> - An anchor name to be appended to the path.
|
||||
# * <tt>:skip_relative_url_root</tt> - If true, the url is not constructed using the
|
||||
# +relative_url_root+ set in ActionController::Base.relative_url_root.
|
||||
# * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in "/archive/2009/"
|
||||
#
|
||||
# Any other key (<tt>:controller</tt>, <tt>:action</tt>, etc.) given to
|
||||
# +url_for+ is forwarded to the Routes module.
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :port=>'8080' # => 'http://somehost.org:8080/tasks/testing'
|
||||
# url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :anchor => 'ok', :only_path => true # => '/tasks/testing#ok'
|
||||
# url_for :controller => 'tasks', :action => 'testing', :trailing_slash=>true # => 'http://somehost.org/tasks/testing/'
|
||||
# url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :number => '33' # => 'http://somehost.org/tasks/testing?number=33'
|
||||
def url_for(options)
|
||||
options = self.class.default_url_options.merge(options)
|
||||
|
||||
url = ''
|
||||
|
||||
unless options.delete(:only_path)
|
||||
url << (options.delete(:protocol) || 'http')
|
||||
url << '://' unless url.match("://")
|
||||
|
||||
raise "Missing host to link to! Please provide :host parameter or set default_url_options[:host]" unless options[:host]
|
||||
|
||||
url << options.delete(:host)
|
||||
url << ":#{options.delete(:port)}" if options.key?(:port)
|
||||
else
|
||||
# Delete the unused options to prevent their appearance in the query string.
|
||||
[:protocol, :host, :port, :skip_relative_url_root].each { |k| options.delete(k) }
|
||||
end
|
||||
trailing_slash = options.delete(:trailing_slash) if options.key?(:trailing_slash)
|
||||
url << ActionController::Base.relative_url_root.to_s unless options[:skip_relative_url_root]
|
||||
anchor = "##{CGI.escape options.delete(:anchor).to_param.to_s}" if options[:anchor]
|
||||
generated = Routing::Routes.generate(options, {})
|
||||
url << (trailing_slash ? generated.sub(/\?|\z/) { "/" + $& } : generated)
|
||||
url << anchor if anchor
|
||||
|
||||
url
|
||||
end
|
||||
end
|
||||
|
||||
# Rewrites URLs for Base.redirect_to and Base.url_for in the controller.
|
||||
class UrlRewriter #:nodoc:
|
||||
RESERVED_OPTIONS = [:anchor, :params, :only_path, :host, :protocol, :port, :trailing_slash, :skip_relative_url_root]
|
||||
|
||||
def initialize(request, parameters)
|
||||
@request, @parameters = request, parameters
|
||||
end
|
||||
|
||||
def rewrite(options = {})
|
||||
rewrite_url(options)
|
||||
options[:host] ||= @request.host_with_port
|
||||
options[:protocol] ||= @request.protocol
|
||||
|
||||
self.class.rewrite(options, @request.symbolized_path_parameters) do |options|
|
||||
process_path_options(options)
|
||||
end
|
||||
end
|
||||
|
||||
def to_str
|
||||
|
@ -156,49 +24,53 @@ module ActionController
|
|||
|
||||
alias_method :to_s, :to_str
|
||||
|
||||
private
|
||||
# Given a path and options, returns a rewritten URL string
|
||||
def rewrite_url(options)
|
||||
rewritten_url = ""
|
||||
def self.rewrite(options, path_segments=nil)
|
||||
rewritten_url = ""
|
||||
|
||||
unless options[:only_path]
|
||||
rewritten_url << (options[:protocol] || @request.protocol)
|
||||
rewritten_url << "://" unless rewritten_url.match("://")
|
||||
rewritten_url << rewrite_authentication(options)
|
||||
rewritten_url << (options[:host] || @request.host_with_port)
|
||||
rewritten_url << ":#{options.delete(:port)}" if options.key?(:port)
|
||||
end
|
||||
unless options[:only_path]
|
||||
rewritten_url << (options[:protocol] || "http")
|
||||
rewritten_url << "://" unless rewritten_url.match("://")
|
||||
rewritten_url << rewrite_authentication(options)
|
||||
|
||||
path = rewrite_path(options)
|
||||
rewritten_url << ActionController::Base.relative_url_root.to_s unless options[:skip_relative_url_root]
|
||||
rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path)
|
||||
rewritten_url << "##{CGI.escape(options[:anchor].to_param.to_s)}" if options[:anchor]
|
||||
raise "Missing host to link to! Please provide :host parameter or set default_url_options[:host]" unless options[:host]
|
||||
|
||||
rewritten_url
|
||||
rewritten_url << options[:host]
|
||||
rewritten_url << ":#{options.delete(:port)}" if options.key?(:port)
|
||||
end
|
||||
|
||||
# Given a Hash of options, generates a route
|
||||
def rewrite_path(options)
|
||||
options = options.symbolize_keys
|
||||
options.update(options[:params].symbolize_keys) if options[:params]
|
||||
path_options = options.except(*RESERVED_OPTIONS)
|
||||
path_options = yield(path_options) if block_given?
|
||||
path = Routing::Routes.generate(path_options, path_segments || {})
|
||||
|
||||
if (overwrite = options.delete(:overwrite_params))
|
||||
options.update(@parameters.symbolize_keys)
|
||||
options.update(overwrite.symbolize_keys)
|
||||
end
|
||||
rewritten_url << ActionController::Base.relative_url_root.to_s unless options[:skip_relative_url_root]
|
||||
rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path)
|
||||
rewritten_url << "##{Rack::Utils.escape(options[:anchor].to_param.to_s)}" if options[:anchor]
|
||||
|
||||
RESERVED_OPTIONS.each { |k| options.delete(k) }
|
||||
rewritten_url
|
||||
end
|
||||
|
||||
# Generates the query string, too
|
||||
Routing::Routes.generate(options, @request.symbolized_path_parameters)
|
||||
protected
|
||||
|
||||
def self.rewrite_authentication(options)
|
||||
if options[:user] && options[:password]
|
||||
"#{Rack::Utils.escape(options.delete(:user))}:#{Rack::Utils.escape(options.delete(:password))}@"
|
||||
else
|
||||
""
|
||||
end
|
||||
end
|
||||
|
||||
# Given a Hash of options, generates a route
|
||||
def process_path_options(options)
|
||||
options = options.symbolize_keys
|
||||
options.update(options[:params].symbolize_keys) if options[:params]
|
||||
|
||||
if (overwrite = options.delete(:overwrite_params))
|
||||
options.update(@parameters.symbolize_keys)
|
||||
options.update(overwrite.symbolize_keys)
|
||||
end
|
||||
|
||||
def rewrite_authentication(options)
|
||||
if options[:user] && options[:password]
|
||||
"#{CGI.escape(options.delete(:user))}:#{CGI.escape(options.delete(:password))}@"
|
||||
else
|
||||
""
|
||||
end
|
||||
end
|
||||
options
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
@ -43,18 +43,27 @@ module ActionDispatch
|
|||
autoload_under 'middleware' do
|
||||
autoload :Callbacks
|
||||
autoload :Cascade
|
||||
autoload :Flash
|
||||
autoload :Head
|
||||
autoload :ParamsParser
|
||||
autoload :Rescue
|
||||
autoload :ShowExceptions
|
||||
autoload :Static
|
||||
autoload :StringCoercion
|
||||
end
|
||||
|
||||
autoload :MiddlewareStack, 'action_dispatch/middleware/stack'
|
||||
autoload :Routing
|
||||
|
||||
module Http
|
||||
autoload :Headers, 'action_dispatch/http/headers'
|
||||
extend ActiveSupport::Autoload
|
||||
|
||||
autoload :Cache
|
||||
autoload :Headers
|
||||
autoload :MimeNegotiation
|
||||
autoload :Parameters
|
||||
autoload :Upload
|
||||
autoload :UploadedFile, 'action_dispatch/http/upload'
|
||||
autoload :URL
|
||||
end
|
||||
|
||||
module Session
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
module ActionDispatch
|
||||
module Http
|
||||
module Cache
|
||||
module Request
|
||||
def if_modified_since
|
||||
if since = env['HTTP_IF_MODIFIED_SINCE']
|
||||
Time.rfc2822(since) rescue nil
|
||||
end
|
||||
end
|
||||
|
||||
def if_none_match
|
||||
env['HTTP_IF_NONE_MATCH']
|
||||
end
|
||||
|
||||
def not_modified?(modified_at)
|
||||
if_modified_since && modified_at && if_modified_since >= modified_at
|
||||
end
|
||||
|
||||
def etag_matches?(etag)
|
||||
if_none_match && if_none_match == etag
|
||||
end
|
||||
|
||||
# Check response freshness (Last-Modified and ETag) against request
|
||||
# If-Modified-Since and If-None-Match conditions. If both headers are
|
||||
# supplied, both must match, or the request is not considered fresh.
|
||||
def fresh?(response)
|
||||
last_modified = if_modified_since
|
||||
etag = if_none_match
|
||||
|
||||
return false unless last_modified || etag
|
||||
|
||||
success = true
|
||||
success &&= not_modified?(response.last_modified) if last_modified
|
||||
success &&= etag_matches?(response.etag) if etag
|
||||
success
|
||||
end
|
||||
end
|
||||
|
||||
module Response
|
||||
def cache_control
|
||||
@cache_control ||= {}
|
||||
end
|
||||
|
||||
def last_modified
|
||||
if last = headers['Last-Modified']
|
||||
Time.httpdate(last)
|
||||
end
|
||||
end
|
||||
|
||||
def last_modified?
|
||||
headers.include?('Last-Modified')
|
||||
end
|
||||
|
||||
def last_modified=(utc_time)
|
||||
headers['Last-Modified'] = utc_time.httpdate
|
||||
end
|
||||
|
||||
def etag
|
||||
@etag
|
||||
end
|
||||
|
||||
def etag?
|
||||
@etag
|
||||
end
|
||||
|
||||
def etag=(etag)
|
||||
key = ActiveSupport::Cache.expand_cache_key(etag)
|
||||
@etag = %("#{Digest::MD5.hexdigest(key)}")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def handle_conditional_get!
|
||||
if etag? || last_modified? || !@cache_control.empty?
|
||||
set_conditional_cache_control!
|
||||
elsif nonempty_ok_response?
|
||||
self.etag = @body
|
||||
|
||||
if request && request.etag_matches?(etag)
|
||||
self.status = 304
|
||||
self.body = []
|
||||
end
|
||||
|
||||
set_conditional_cache_control!
|
||||
else
|
||||
headers["Cache-Control"] = "no-cache"
|
||||
end
|
||||
end
|
||||
|
||||
def nonempty_ok_response?
|
||||
@status == 200 && string_body?
|
||||
end
|
||||
|
||||
def string_body?
|
||||
!@blank && @body.respond_to?(:all?) && @body.all? { |part| part.is_a?(String) }
|
||||
end
|
||||
|
||||
DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate"
|
||||
|
||||
def set_conditional_cache_control!
|
||||
control = @cache_control
|
||||
|
||||
if control.empty?
|
||||
headers["Cache-Control"] = DEFAULT_CACHE_CONTROL
|
||||
elsif @cache_control[:no_cache]
|
||||
headers["Cache-Control"] = "no-cache"
|
||||
else
|
||||
extras = control[:extras]
|
||||
max_age = control[:max_age]
|
||||
|
||||
options = []
|
||||
options << "max-age=#{max_age.to_i}" if max_age
|
||||
options << (control[:public] ? "public" : "private")
|
||||
options << "must-revalidate" if control[:must_revalidate]
|
||||
options.concat(extras) if extras
|
||||
|
||||
headers["Cache-Control"] = options.join(", ")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,101 @@
|
|||
module ActionDispatch
|
||||
module Http
|
||||
module MimeNegotiation
|
||||
# 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
|
||||
@env["action_dispatch.request.content_type"] ||= begin
|
||||
if @env['CONTENT_TYPE'] =~ /^([^,\;]*)/
|
||||
Mime::Type.lookup($1.strip.downcase)
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the accepted MIME type for the request.
|
||||
def accepts
|
||||
@env["action_dispatch.request.accepts"] ||= begin
|
||||
header = @env['HTTP_ACCEPT'].to_s.strip
|
||||
|
||||
if header.empty?
|
||||
[content_type]
|
||||
else
|
||||
Mime::Type.parse(header)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the Mime type for the \format used in the request.
|
||||
#
|
||||
# GET /posts/5.xml | request.format => Mime::XML
|
||||
# GET /posts/5.xhtml | request.format => Mime::HTML
|
||||
# GET /posts/5 | request.format => Mime::HTML or MIME::JS, or request.accepts.first depending on the value of <tt>ActionController::Base.use_accept_header</tt>
|
||||
#
|
||||
def format(view_path = [])
|
||||
formats.first
|
||||
end
|
||||
|
||||
def formats
|
||||
accept = @env['HTTP_ACCEPT']
|
||||
|
||||
@env["action_dispatch.request.formats"] ||=
|
||||
if parameters[:format]
|
||||
Array(Mime[parameters[:format]])
|
||||
elsif xhr? || (accept && !accept.include?(?,))
|
||||
accepts
|
||||
else
|
||||
[Mime::HTML]
|
||||
end
|
||||
end
|
||||
|
||||
# Sets the \format by string extension, which can be used to force custom formats
|
||||
# that are not controlled by the extension.
|
||||
#
|
||||
# class ApplicationController < ActionController::Base
|
||||
# before_filter :adjust_format_for_iphone
|
||||
#
|
||||
# private
|
||||
# def adjust_format_for_iphone
|
||||
# request.format = :iphone if request.env["HTTP_USER_AGENT"][/iPhone/]
|
||||
# end
|
||||
# end
|
||||
def format=(extension)
|
||||
parameters[:format] = extension.to_s
|
||||
@env["action_dispatch.request.formats"] = [Mime::Type.lookup_by_extension(parameters[:format])]
|
||||
end
|
||||
|
||||
# Returns a symbolized version of the <tt>:format</tt> parameter of the request.
|
||||
# If no \format is given it returns <tt>:js</tt>for Ajax requests and <tt>:html</tt>
|
||||
# otherwise.
|
||||
def template_format
|
||||
parameter_format = parameters[:format]
|
||||
|
||||
if parameter_format
|
||||
parameter_format
|
||||
elsif xhr?
|
||||
:js
|
||||
else
|
||||
:html
|
||||
end
|
||||
end
|
||||
|
||||
# Receives an array of mimes and return the first user sent mime that
|
||||
# matches the order array.
|
||||
#
|
||||
def negotiate_mime(order)
|
||||
formats.each do |priority|
|
||||
if priority == Mime::ALL
|
||||
return order.first
|
||||
elsif order.include?(priority)
|
||||
return priority
|
||||
end
|
||||
end
|
||||
|
||||
order.include?(Mime::ALL) ? formats.first : nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,50 @@
|
|||
require 'active_support/core_ext/hash/keys'
|
||||
|
||||
module ActionDispatch
|
||||
module Http
|
||||
module Parameters
|
||||
# Returns both GET and POST \parameters in a single hash.
|
||||
def parameters
|
||||
@env["action_dispatch.request.parameters"] ||= request_parameters.merge(query_parameters).update(path_parameters).with_indifferent_access
|
||||
end
|
||||
alias :params :parameters
|
||||
|
||||
def path_parameters=(parameters) #:nodoc:
|
||||
@env.delete("action_dispatch.request.symbolized_path_parameters")
|
||||
@env.delete("action_dispatch.request.parameters")
|
||||
@env["action_dispatch.request.path_parameters"] = parameters
|
||||
end
|
||||
|
||||
# The same as <tt>path_parameters</tt> with explicitly symbolized keys.
|
||||
def symbolized_path_parameters
|
||||
@env["action_dispatch.request.symbolized_path_parameters"] ||= path_parameters.symbolize_keys
|
||||
end
|
||||
|
||||
# Returns a hash with the \parameters used to form the \path of the request.
|
||||
# Returned hash keys are strings:
|
||||
#
|
||||
# {'action' => 'my_action', 'controller' => 'my_controller'}
|
||||
#
|
||||
# See <tt>symbolized_path_parameters</tt> for symbolized keys.
|
||||
def path_parameters
|
||||
@env["action_dispatch.request.path_parameters"] ||= {}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Convert nested Hashs to HashWithIndifferentAccess
|
||||
def normalize_parameters(value)
|
||||
case value
|
||||
when Hash
|
||||
h = {}
|
||||
value.each { |k, v| h[k] = normalize_parameters(v) }
|
||||
h.with_indifferent_access
|
||||
when Array
|
||||
value.map { |e| normalize_parameters(e) }
|
||||
else
|
||||
value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -2,14 +2,17 @@ require 'tempfile'
|
|||
require 'stringio'
|
||||
require 'strscan'
|
||||
|
||||
require 'active_support/memoizable'
|
||||
require 'active_support/core_ext/array/wrap'
|
||||
require 'active_support/core_ext/hash/indifferent_access'
|
||||
require 'active_support/core_ext/string/access'
|
||||
require 'action_dispatch/http/headers'
|
||||
|
||||
module ActionDispatch
|
||||
class Request < Rack::Request
|
||||
include ActionDispatch::Http::Cache::Request
|
||||
include ActionDispatch::Http::MimeNegotiation
|
||||
include ActionDispatch::Http::Parameters
|
||||
include ActionDispatch::Http::Upload
|
||||
include ActionDispatch::Http::URL
|
||||
|
||||
%w[ AUTH_TYPE GATEWAY_INTERFACE
|
||||
PATH_TRANSLATED REMOTE_HOST
|
||||
|
@ -19,9 +22,11 @@ module ActionDispatch
|
|||
HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING
|
||||
HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM
|
||||
HTTP_NEGOTIATE HTTP_PRAGMA ].each do |env|
|
||||
define_method(env.sub(/^HTTP_/n, '').downcase) do
|
||||
@env[env]
|
||||
end
|
||||
class_eval <<-METHOD, __FILE__, __LINE__ + 1
|
||||
def #{env.sub(/^HTTP_/n, '').downcase}
|
||||
@env["#{env}"]
|
||||
end
|
||||
METHOD
|
||||
end
|
||||
|
||||
def key?(key)
|
||||
|
@ -35,7 +40,8 @@ module ActionDispatch
|
|||
# <tt>:get</tt>. If the request \method is not listed in the HTTP_METHODS
|
||||
# constant above, an UnknownHttpMethod exception is raised.
|
||||
def request_method
|
||||
HTTP_METHOD_LOOKUP[super] || raise(ActionController::UnknownHttpMethod, "#{super}, accepted HTTP methods are #{HTTP_METHODS.to_sentence(:locale => :en)}")
|
||||
method = env["rack.methodoverride.original_method"] || env["REQUEST_METHOD"]
|
||||
HTTP_METHOD_LOOKUP[method] || raise(ActionController::UnknownHttpMethod, "#{method}, accepted HTTP methods are #{HTTP_METHODS.to_sentence(:locale => :en)}")
|
||||
end
|
||||
|
||||
# Returns the HTTP request \method used for action processing as a
|
||||
|
@ -43,7 +49,8 @@ module ActionDispatch
|
|||
# method returns <tt>:get</tt> for a HEAD request because the two are
|
||||
# functionally equivalent from the application's perspective.)
|
||||
def method
|
||||
request_method == :head ? :get : request_method
|
||||
method = env["REQUEST_METHOD"]
|
||||
HTTP_METHOD_LOOKUP[method] || raise(ActionController::UnknownHttpMethod, "#{method}, accepted HTTP methods are #{HTTP_METHODS.to_sentence(:locale => :en)}")
|
||||
end
|
||||
|
||||
# Is this a GET (or HEAD) request? Equivalent to <tt>request.method == :get</tt>.
|
||||
|
@ -53,17 +60,17 @@ module ActionDispatch
|
|||
|
||||
# Is this a POST request? Equivalent to <tt>request.method == :post</tt>.
|
||||
def post?
|
||||
request_method == :post
|
||||
method == :post
|
||||
end
|
||||
|
||||
# Is this a PUT request? Equivalent to <tt>request.method == :put</tt>.
|
||||
def put?
|
||||
request_method == :put
|
||||
method == :put
|
||||
end
|
||||
|
||||
# Is this a DELETE request? Equivalent to <tt>request.method == :delete</tt>.
|
||||
def delete?
|
||||
request_method == :delete
|
||||
method == :delete
|
||||
end
|
||||
|
||||
# Is this a HEAD request? Since <tt>request.method</tt> sees HEAD as <tt>:get</tt>,
|
||||
|
@ -79,25 +86,6 @@ module ActionDispatch
|
|||
Http::Headers.new(@env)
|
||||
end
|
||||
|
||||
# Returns the content length of the request as an integer.
|
||||
def content_length
|
||||
super.to_i
|
||||
end
|
||||
|
||||
# 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
|
||||
@env["action_dispatch.request.content_type"] ||= begin
|
||||
if @env['CONTENT_TYPE'] =~ /^([^,\;]*)/
|
||||
Mime::Type.lookup($1.strip.downcase)
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def forgery_whitelisted?
|
||||
method == :get || xhr? || content_type.nil? || !content_type.verify_request?
|
||||
end
|
||||
|
@ -106,104 +94,9 @@ module ActionDispatch
|
|||
content_type.to_s
|
||||
end
|
||||
|
||||
# Returns the accepted MIME type for the request.
|
||||
def accepts
|
||||
@env["action_dispatch.request.accepts"] ||= begin
|
||||
header = @env['HTTP_ACCEPT'].to_s.strip
|
||||
|
||||
if header.empty?
|
||||
[content_type]
|
||||
else
|
||||
Mime::Type.parse(header)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def if_modified_since
|
||||
if since = env['HTTP_IF_MODIFIED_SINCE']
|
||||
Time.rfc2822(since) rescue nil
|
||||
end
|
||||
end
|
||||
|
||||
def if_none_match
|
||||
env['HTTP_IF_NONE_MATCH']
|
||||
end
|
||||
|
||||
def not_modified?(modified_at)
|
||||
if_modified_since && modified_at && if_modified_since >= modified_at
|
||||
end
|
||||
|
||||
def etag_matches?(etag)
|
||||
if_none_match && if_none_match == etag
|
||||
end
|
||||
|
||||
# Check response freshness (Last-Modified and ETag) against request
|
||||
# If-Modified-Since and If-None-Match conditions. If both headers are
|
||||
# supplied, both must match, or the request is not considered fresh.
|
||||
def fresh?(response)
|
||||
last_modified = if_modified_since
|
||||
etag = if_none_match
|
||||
|
||||
return false unless last_modified || etag
|
||||
|
||||
success = true
|
||||
success &&= not_modified?(response.last_modified) if last_modified
|
||||
success &&= etag_matches?(response.etag) if etag
|
||||
success
|
||||
end
|
||||
|
||||
# Returns the Mime type for the \format used in the request.
|
||||
#
|
||||
# GET /posts/5.xml | request.format => Mime::XML
|
||||
# GET /posts/5.xhtml | request.format => Mime::HTML
|
||||
# GET /posts/5 | request.format => Mime::HTML or MIME::JS, or request.accepts.first depending on the value of <tt>ActionController::Base.use_accept_header</tt>
|
||||
#
|
||||
def format(view_path = [])
|
||||
formats.first
|
||||
end
|
||||
|
||||
def formats
|
||||
accept = @env['HTTP_ACCEPT']
|
||||
|
||||
@env["action_dispatch.request.formats"] ||=
|
||||
if parameters[:format]
|
||||
Array.wrap(Mime[parameters[:format]])
|
||||
elsif xhr? || (accept && !accept.include?(?,))
|
||||
accepts
|
||||
else
|
||||
[Mime::HTML]
|
||||
end
|
||||
end
|
||||
|
||||
# Sets the \format by string extension, which can be used to force custom formats
|
||||
# that are not controlled by the extension.
|
||||
#
|
||||
# class ApplicationController < ActionController::Base
|
||||
# before_filter :adjust_format_for_iphone
|
||||
#
|
||||
# private
|
||||
# def adjust_format_for_iphone
|
||||
# request.format = :iphone if request.env["HTTP_USER_AGENT"][/iPhone/]
|
||||
# end
|
||||
# end
|
||||
def format=(extension)
|
||||
parameters[:format] = extension.to_s
|
||||
@env["action_dispatch.request.formats"] = [Mime::Type.lookup_by_extension(parameters[:format])]
|
||||
end
|
||||
|
||||
# Returns a symbolized version of the <tt>:format</tt> parameter of the request.
|
||||
# If no \format is given it returns <tt>:js</tt>for Ajax requests and <tt>:html</tt>
|
||||
# otherwise.
|
||||
def template_format
|
||||
parameter_format = parameters[:format]
|
||||
|
||||
if parameter_format
|
||||
parameter_format
|
||||
elsif xhr?
|
||||
:js
|
||||
else
|
||||
:html
|
||||
end
|
||||
# Returns the content length of the request as an integer.
|
||||
def content_length
|
||||
super.to_i
|
||||
end
|
||||
|
||||
# Returns true if the request's "X-Requested-With" header contains
|
||||
|
@ -236,7 +129,7 @@ module ActionDispatch
|
|||
if @env.include? 'HTTP_CLIENT_IP'
|
||||
if ActionController::Base.ip_spoofing_check && remote_ips && !remote_ips.include?(@env['HTTP_CLIENT_IP'])
|
||||
# We don't know which came from the proxy, and which from the user
|
||||
raise ActionController::ActionControllerError.new(<<EOM)
|
||||
raise ActionController::ActionControllerError.new <<EOM
|
||||
IP spoofing attack?!
|
||||
HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect}
|
||||
HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}
|
||||
|
@ -262,124 +155,6 @@ EOM
|
|||
(@env['SERVER_SOFTWARE'] && /^([a-zA-Z]+)/ =~ @env['SERVER_SOFTWARE']) ? $1.downcase : nil
|
||||
end
|
||||
|
||||
# Returns the complete URL used for this request.
|
||||
def url
|
||||
protocol + host_with_port + request_uri
|
||||
end
|
||||
|
||||
# Returns 'https://' if this is an SSL request and 'http://' otherwise.
|
||||
def protocol
|
||||
ssl? ? 'https://' : 'http://'
|
||||
end
|
||||
|
||||
# Is this an SSL request?
|
||||
def ssl?
|
||||
@env['HTTPS'] == 'on' || @env['HTTP_X_FORWARDED_PROTO'] == 'https'
|
||||
end
|
||||
|
||||
# Returns the \host for this request, such as "example.com".
|
||||
def raw_host_with_port
|
||||
if forwarded = env["HTTP_X_FORWARDED_HOST"]
|
||||
forwarded.split(/,\s?/).last
|
||||
else
|
||||
env['HTTP_HOST'] || "#{env['SERVER_NAME'] || env['SERVER_ADDR']}:#{env['SERVER_PORT']}"
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the host for this request, such as example.com.
|
||||
def host
|
||||
raw_host_with_port.sub(/:\d+$/, '')
|
||||
end
|
||||
|
||||
# Returns a \host:\port string for this request, such as "example.com" or
|
||||
# "example.com:8080".
|
||||
def host_with_port
|
||||
"#{host}#{port_string}"
|
||||
end
|
||||
|
||||
# Returns the port number of this request as an integer.
|
||||
def port
|
||||
if raw_host_with_port =~ /:(\d+)$/
|
||||
$1.to_i
|
||||
else
|
||||
standard_port
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the standard \port number for this request's protocol.
|
||||
def standard_port
|
||||
case protocol
|
||||
when 'https://' then 443
|
||||
else 80
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a \port suffix like ":8080" if the \port number of this request
|
||||
# is not the default HTTP \port 80 or HTTPS \port 443.
|
||||
def port_string
|
||||
port == standard_port ? '' : ":#{port}"
|
||||
end
|
||||
|
||||
def server_port
|
||||
@env['SERVER_PORT'].to_i
|
||||
end
|
||||
|
||||
# Returns the \domain part of a \host, such as "rubyonrails.org" in "www.rubyonrails.org". You can specify
|
||||
# a different <tt>tld_length</tt>, such as 2 to catch rubyonrails.co.uk in "www.rubyonrails.co.uk".
|
||||
def domain(tld_length = 1)
|
||||
return nil unless named_host?(host)
|
||||
|
||||
host.split('.').last(1 + tld_length).join('.')
|
||||
end
|
||||
|
||||
# Returns all the \subdomains as an array, so <tt>["dev", "www"]</tt> would be
|
||||
# returned for "dev.www.rubyonrails.org". You can specify a different <tt>tld_length</tt>,
|
||||
# such as 2 to catch <tt>["www"]</tt> instead of <tt>["www", "rubyonrails"]</tt>
|
||||
# in "www.rubyonrails.co.uk".
|
||||
def subdomains(tld_length = 1)
|
||||
return [] unless named_host?(host)
|
||||
parts = host.split('.')
|
||||
parts[0..-(tld_length+2)]
|
||||
end
|
||||
|
||||
# Returns the query string, accounting for server idiosyncrasies.
|
||||
def query_string
|
||||
@env['QUERY_STRING'].present? ? @env['QUERY_STRING'] : (@env['REQUEST_URI'].to_s.split('?', 2)[1] || '')
|
||||
end
|
||||
|
||||
# Returns the request URI, accounting for server idiosyncrasies.
|
||||
# WEBrick includes the full URL. IIS leaves REQUEST_URI blank.
|
||||
def request_uri
|
||||
if uri = @env['REQUEST_URI']
|
||||
# Remove domain, which webrick puts into the request_uri.
|
||||
(%r{^\w+\://[^/]+(/.*|$)$} =~ uri) ? $1 : uri
|
||||
else
|
||||
# Construct IIS missing REQUEST_URI from SCRIPT_NAME and PATH_INFO.
|
||||
uri = @env['PATH_INFO'].to_s
|
||||
|
||||
if script_filename = @env['SCRIPT_NAME'].to_s.match(%r{[^/]+$})
|
||||
uri = uri.sub(/#{script_filename}\//, '')
|
||||
end
|
||||
|
||||
env_qs = @env['QUERY_STRING'].to_s
|
||||
uri += "?#{env_qs}" unless env_qs.empty?
|
||||
|
||||
if uri.blank?
|
||||
@env.delete('REQUEST_URI')
|
||||
else
|
||||
@env['REQUEST_URI'] = uri
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the interpreted \path to requested resource after all the installation
|
||||
# directory of this application was taken into account.
|
||||
def path
|
||||
path = request_uri.to_s[/\A[^\?]*/]
|
||||
path.sub!(/\A#{ActionController::Base.relative_url_root}/, '')
|
||||
path
|
||||
end
|
||||
|
||||
# Read the request \body. This is useful for web services that need to
|
||||
# work with raw requests directly.
|
||||
def raw_post
|
||||
|
@ -390,33 +165,6 @@ EOM
|
|||
@env['RAW_POST_DATA']
|
||||
end
|
||||
|
||||
# Returns both GET and POST \parameters in a single hash.
|
||||
def parameters
|
||||
@env["action_dispatch.request.parameters"] ||= request_parameters.merge(query_parameters).update(path_parameters).with_indifferent_access
|
||||
end
|
||||
alias_method :params, :parameters
|
||||
|
||||
def path_parameters=(parameters) #:nodoc:
|
||||
@env.delete("action_dispatch.request.symbolized_path_parameters")
|
||||
@env.delete("action_dispatch.request.parameters")
|
||||
@env["action_dispatch.request.path_parameters"] = parameters
|
||||
end
|
||||
|
||||
# The same as <tt>path_parameters</tt> with explicitly symbolized keys.
|
||||
def symbolized_path_parameters
|
||||
@env["action_dispatch.request.symbolized_path_parameters"] ||= path_parameters.symbolize_keys
|
||||
end
|
||||
|
||||
# Returns a hash with the \parameters used to form the \path of the request.
|
||||
# Returned hash keys are strings:
|
||||
#
|
||||
# {'action' => 'my_action', 'controller' => 'my_controller'}
|
||||
#
|
||||
# See <tt>symbolized_path_parameters</tt> for symbolized keys.
|
||||
def path_parameters
|
||||
@env["action_dispatch.request.path_parameters"] ||= {}
|
||||
end
|
||||
|
||||
# The request body is an IO input stream. If the RAW_POST_DATA environment
|
||||
# variable is already set, wrap it in a StringIO.
|
||||
def body
|
||||
|
@ -432,18 +180,6 @@ EOM
|
|||
FORM_DATA_MEDIA_TYPES.include?(content_type.to_s)
|
||||
end
|
||||
|
||||
# Override Rack's GET method to support indifferent access
|
||||
def GET
|
||||
@env["action_dispatch.request.query_parameters"] ||= normalize_parameters(super)
|
||||
end
|
||||
alias_method :query_parameters, :GET
|
||||
|
||||
# Override Rack's POST method to support indifferent access
|
||||
def POST
|
||||
@env["action_dispatch.request.request_parameters"] ||= normalize_parameters(super)
|
||||
end
|
||||
alias_method :request_parameters, :POST
|
||||
|
||||
def body_stream #:nodoc:
|
||||
@env['rack.input']
|
||||
end
|
||||
|
@ -461,9 +197,18 @@ EOM
|
|||
@env['rack.session.options'] = options
|
||||
end
|
||||
|
||||
def flash
|
||||
session['flash'] || {}
|
||||
# Override Rack's GET method to support indifferent access
|
||||
def GET
|
||||
@env["action_dispatch.request.query_parameters"] ||= normalize_parameters(super)
|
||||
end
|
||||
alias :query_parameters :GET
|
||||
|
||||
# Override Rack's POST method to support indifferent access
|
||||
def POST
|
||||
@env["action_dispatch.request.request_parameters"] ||= normalize_parameters(super)
|
||||
end
|
||||
alias :request_parameters :POST
|
||||
|
||||
|
||||
# Returns the authorization header regardless of whether it was specified directly or through one of the
|
||||
# proxy alternatives.
|
||||
|
@ -473,77 +218,5 @@ EOM
|
|||
@env['X_HTTP_AUTHORIZATION'] ||
|
||||
@env['REDIRECT_X_HTTP_AUTHORIZATION']
|
||||
end
|
||||
|
||||
# Receives an array of mimes and return the first user sent mime that
|
||||
# matches the order array.
|
||||
#
|
||||
def negotiate_mime(order)
|
||||
formats.each do |priority|
|
||||
if priority == Mime::ALL
|
||||
return order.first
|
||||
elsif order.include?(priority)
|
||||
return priority
|
||||
end
|
||||
end
|
||||
|
||||
order.include?(Mime::ALL) ? formats.first : nil
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def named_host?(host)
|
||||
!(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host))
|
||||
end
|
||||
|
||||
module UploadedFile
|
||||
def self.extended(object)
|
||||
object.class_eval do
|
||||
attr_accessor :original_path, :content_type
|
||||
alias_method :local_path, :path if method_defined?(:path)
|
||||
end
|
||||
end
|
||||
|
||||
# Take the basename of the upload's original filename.
|
||||
# This handles the full Windows paths given by Internet Explorer
|
||||
# (and perhaps other broken user agents) without affecting
|
||||
# those which give the lone filename.
|
||||
# The Windows regexp is adapted from Perl's File::Basename.
|
||||
def original_filename
|
||||
unless defined? @original_filename
|
||||
@original_filename =
|
||||
unless original_path.blank?
|
||||
if original_path =~ /^(?:.*[:\\\/])?(.*)/m
|
||||
$1
|
||||
else
|
||||
File.basename original_path
|
||||
end
|
||||
end
|
||||
end
|
||||
@original_filename
|
||||
end
|
||||
end
|
||||
|
||||
# Convert nested Hashs to HashWithIndifferentAccess and replace
|
||||
# file upload hashs with UploadedFile objects
|
||||
def normalize_parameters(value)
|
||||
case value
|
||||
when Hash
|
||||
if value.has_key?(:tempfile)
|
||||
upload = value[:tempfile]
|
||||
upload.extend(UploadedFile)
|
||||
upload.original_path = value[:filename]
|
||||
upload.content_type = value[:type]
|
||||
upload
|
||||
else
|
||||
h = {}
|
||||
value.each { |k, v| h[k] = normalize_parameters(v) }
|
||||
h.with_indifferent_access
|
||||
end
|
||||
when Array
|
||||
value.map { |e| normalize_parameters(e) }
|
||||
else
|
||||
value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -32,6 +32,8 @@ module ActionDispatch # :nodoc:
|
|||
# end
|
||||
# end
|
||||
class Response < Rack::Response
|
||||
include ActionDispatch::Http::Cache::Response
|
||||
|
||||
attr_accessor :request, :blank
|
||||
|
||||
attr_writer :header, :sending_file
|
||||
|
@ -55,10 +57,6 @@ module ActionDispatch # :nodoc:
|
|||
yield self if block_given?
|
||||
end
|
||||
|
||||
def cache_control
|
||||
@cache_control ||= {}
|
||||
end
|
||||
|
||||
def status=(status)
|
||||
@status = Rack::Utils.status_code(status)
|
||||
end
|
||||
|
@ -114,33 +112,6 @@ module ActionDispatch # :nodoc:
|
|||
# information.
|
||||
attr_accessor :charset, :content_type
|
||||
|
||||
def last_modified
|
||||
if last = headers['Last-Modified']
|
||||
Time.httpdate(last)
|
||||
end
|
||||
end
|
||||
|
||||
def last_modified?
|
||||
headers.include?('Last-Modified')
|
||||
end
|
||||
|
||||
def last_modified=(utc_time)
|
||||
headers['Last-Modified'] = utc_time.httpdate
|
||||
end
|
||||
|
||||
def etag
|
||||
@etag
|
||||
end
|
||||
|
||||
def etag?
|
||||
@etag
|
||||
end
|
||||
|
||||
def etag=(etag)
|
||||
key = ActiveSupport::Cache.expand_cache_key(etag)
|
||||
@etag = %("#{Digest::MD5.hexdigest(key)}")
|
||||
end
|
||||
|
||||
CONTENT_TYPE = "Content-Type"
|
||||
|
||||
cattr_accessor(:default_charset) { "utf-8" }
|
||||
|
@ -222,31 +193,6 @@ module ActionDispatch # :nodoc:
|
|||
end
|
||||
|
||||
private
|
||||
def handle_conditional_get!
|
||||
if etag? || last_modified? || !@cache_control.empty?
|
||||
set_conditional_cache_control!
|
||||
elsif nonempty_ok_response?
|
||||
self.etag = @body
|
||||
|
||||
if request && request.etag_matches?(etag)
|
||||
self.status = 304
|
||||
self.body = []
|
||||
end
|
||||
|
||||
set_conditional_cache_control!
|
||||
else
|
||||
headers["Cache-Control"] = "no-cache"
|
||||
end
|
||||
end
|
||||
|
||||
def nonempty_ok_response?
|
||||
@status == 200 && string_body?
|
||||
end
|
||||
|
||||
def string_body?
|
||||
!@blank && @body.respond_to?(:all?) && @body.all? { |part| part.is_a?(String) }
|
||||
end
|
||||
|
||||
def assign_default_content_type_and_charset!
|
||||
return if headers[CONTENT_TYPE].present?
|
||||
|
||||
|
@ -259,27 +205,5 @@ module ActionDispatch # :nodoc:
|
|||
headers[CONTENT_TYPE] = type
|
||||
end
|
||||
|
||||
DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate"
|
||||
|
||||
def set_conditional_cache_control!
|
||||
control = @cache_control
|
||||
|
||||
if control.empty?
|
||||
headers["Cache-Control"] = DEFAULT_CACHE_CONTROL
|
||||
elsif @cache_control[:no_cache]
|
||||
headers["Cache-Control"] = "no-cache"
|
||||
else
|
||||
extras = control[:extras]
|
||||
max_age = control[:max_age]
|
||||
|
||||
options = []
|
||||
options << "max-age=#{max_age.to_i}" if max_age
|
||||
options << (control[:public] ? "public" : "private")
|
||||
options << "must-revalidate" if control[:must_revalidate]
|
||||
options.concat(extras) if extras
|
||||
|
||||
headers["Cache-Control"] = options.join(", ")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
module ActionDispatch
|
||||
module Http
|
||||
module UploadedFile
|
||||
def self.extended(object)
|
||||
object.class_eval do
|
||||
attr_accessor :original_path, :content_type
|
||||
alias_method :local_path, :path if method_defined?(:path)
|
||||
end
|
||||
end
|
||||
|
||||
# Take the basename of the upload's original filename.
|
||||
# This handles the full Windows paths given by Internet Explorer
|
||||
# (and perhaps other broken user agents) without affecting
|
||||
# those which give the lone filename.
|
||||
# The Windows regexp is adapted from Perl's File::Basename.
|
||||
def original_filename
|
||||
unless defined? @original_filename
|
||||
@original_filename =
|
||||
unless original_path.blank?
|
||||
if original_path =~ /^(?:.*[:\\\/])?(.*)/m
|
||||
$1
|
||||
else
|
||||
File.basename original_path
|
||||
end
|
||||
end
|
||||
end
|
||||
@original_filename
|
||||
end
|
||||
end
|
||||
|
||||
module Upload
|
||||
# Convert nested Hashs to HashWithIndifferentAccess and replace
|
||||
# file upload hashs with UploadedFile objects
|
||||
def normalize_parameters(value)
|
||||
if Hash === value && value.has_key?(:tempfile)
|
||||
upload = value[:tempfile]
|
||||
upload.extend(UploadedFile)
|
||||
upload.original_path = value[:filename]
|
||||
upload.content_type = value[:type]
|
||||
upload
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
private :normalize_parameters
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,129 @@
|
|||
module ActionDispatch
|
||||
module Http
|
||||
module URL
|
||||
# Returns the complete URL used for this request.
|
||||
def url
|
||||
protocol + host_with_port + request_uri
|
||||
end
|
||||
|
||||
# Returns 'https://' if this is an SSL request and 'http://' otherwise.
|
||||
def protocol
|
||||
ssl? ? 'https://' : 'http://'
|
||||
end
|
||||
|
||||
# Is this an SSL request?
|
||||
def ssl?
|
||||
@env['HTTPS'] == 'on' || @env['HTTP_X_FORWARDED_PROTO'] == 'https'
|
||||
end
|
||||
|
||||
# Returns the \host for this request, such as "example.com".
|
||||
def raw_host_with_port
|
||||
if forwarded = env["HTTP_X_FORWARDED_HOST"]
|
||||
forwarded.split(/,\s?/).last
|
||||
else
|
||||
env['HTTP_HOST'] || "#{env['SERVER_NAME'] || env['SERVER_ADDR']}:#{env['SERVER_PORT']}"
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the host for this request, such as example.com.
|
||||
def host
|
||||
raw_host_with_port.sub(/:\d+$/, '')
|
||||
end
|
||||
|
||||
# Returns a \host:\port string for this request, such as "example.com" or
|
||||
# "example.com:8080".
|
||||
def host_with_port
|
||||
"#{host}#{port_string}"
|
||||
end
|
||||
|
||||
# Returns the port number of this request as an integer.
|
||||
def port
|
||||
if raw_host_with_port =~ /:(\d+)$/
|
||||
$1.to_i
|
||||
else
|
||||
standard_port
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the standard \port number for this request's protocol.
|
||||
def standard_port
|
||||
case protocol
|
||||
when 'https://' then 443
|
||||
else 80
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a \port suffix like ":8080" if the \port number of this request
|
||||
# is not the default HTTP \port 80 or HTTPS \port 443.
|
||||
def port_string
|
||||
port == standard_port ? '' : ":#{port}"
|
||||
end
|
||||
|
||||
def server_port
|
||||
@env['SERVER_PORT'].to_i
|
||||
end
|
||||
|
||||
# Returns the \domain part of a \host, such as "rubyonrails.org" in "www.rubyonrails.org". You can specify
|
||||
# a different <tt>tld_length</tt>, such as 2 to catch rubyonrails.co.uk in "www.rubyonrails.co.uk".
|
||||
def domain(tld_length = 1)
|
||||
return nil unless named_host?(host)
|
||||
|
||||
host.split('.').last(1 + tld_length).join('.')
|
||||
end
|
||||
|
||||
# Returns all the \subdomains as an array, so <tt>["dev", "www"]</tt> would be
|
||||
# returned for "dev.www.rubyonrails.org". You can specify a different <tt>tld_length</tt>,
|
||||
# such as 2 to catch <tt>["www"]</tt> instead of <tt>["www", "rubyonrails"]</tt>
|
||||
# in "www.rubyonrails.co.uk".
|
||||
def subdomains(tld_length = 1)
|
||||
return [] unless named_host?(host)
|
||||
parts = host.split('.')
|
||||
parts[0..-(tld_length+2)]
|
||||
end
|
||||
|
||||
# Returns the query string, accounting for server idiosyncrasies.
|
||||
def query_string
|
||||
@env['QUERY_STRING'].present? ? @env['QUERY_STRING'] : (@env['REQUEST_URI'].to_s.split('?', 2)[1] || '')
|
||||
end
|
||||
|
||||
# Returns the request URI, accounting for server idiosyncrasies.
|
||||
# WEBrick includes the full URL. IIS leaves REQUEST_URI blank.
|
||||
def request_uri
|
||||
if uri = @env['REQUEST_URI']
|
||||
# Remove domain, which webrick puts into the request_uri.
|
||||
(%r{^\w+\://[^/]+(/.*|$)$} =~ uri) ? $1 : uri
|
||||
else
|
||||
# Construct IIS missing REQUEST_URI from SCRIPT_NAME and PATH_INFO.
|
||||
uri = @env['PATH_INFO'].to_s
|
||||
|
||||
if script_filename = @env['SCRIPT_NAME'].to_s.match(%r{[^/]+$})
|
||||
uri = uri.sub(/#{script_filename}\//, '')
|
||||
end
|
||||
|
||||
env_qs = @env['QUERY_STRING'].to_s
|
||||
uri += "?#{env_qs}" unless env_qs.empty?
|
||||
|
||||
if uri.blank?
|
||||
@env.delete('REQUEST_URI')
|
||||
else
|
||||
@env['REQUEST_URI'] = uri
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the interpreted \path to requested resource after all the installation
|
||||
# directory of this application was taken into account.
|
||||
def path
|
||||
path = request_uri.to_s[/\A[^\?]*/]
|
||||
path.sub!(/\A#{ActionController::Base.relative_url_root}/, '')
|
||||
path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def named_host?(host)
|
||||
!(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,4 +1,10 @@
|
|||
module ActionDispatch
|
||||
# Provide callbacks to be executed before and after the request dispatch.
|
||||
#
|
||||
# It also provides a to_prepare callback, which is performed in all requests
|
||||
# in development by only once in production and notification callback for async
|
||||
# operations.
|
||||
#
|
||||
class Callbacks
|
||||
include ActiveSupport::Callbacks
|
||||
|
||||
|
@ -29,12 +35,6 @@ module ActionDispatch
|
|||
set_callback(:call, :after, *args, &block)
|
||||
end
|
||||
|
||||
class << self
|
||||
# DEPRECATED
|
||||
alias_method :before_dispatch, :before
|
||||
alias_method :after_dispatch, :after
|
||||
end
|
||||
|
||||
def initialize(app, prepare_each_request = false)
|
||||
@app, @prepare_each_request = app, prepare_each_request
|
||||
run_callbacks(:prepare)
|
||||
|
@ -45,6 +45,8 @@ module ActionDispatch
|
|||
run_callbacks(:prepare) if @prepare_each_request
|
||||
@app.call(env)
|
||||
end
|
||||
ensure
|
||||
ActiveSupport::Notifications.instrument "action_dispatch.callback"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,174 @@
|
|||
module ActionDispatch
|
||||
class Request
|
||||
# Access the contents of the flash. Use <tt>flash["notice"]</tt> to
|
||||
# read a notice you put there or <tt>flash["notice"] = "hello"</tt>
|
||||
# to put a new one.
|
||||
def flash
|
||||
session['flash'] ||= Flash::FlashHash.new
|
||||
end
|
||||
end
|
||||
|
||||
# The flash provides a way to pass temporary objects between actions. Anything you place in the flash will be exposed
|
||||
# to the very next action and then cleared out. This is a great way of doing notices and alerts, such as a create
|
||||
# action that sets <tt>flash[:notice] = "Successfully created"</tt> before redirecting to a display action that can
|
||||
# then expose the flash to its template. Actually, that exposure is automatically done. Example:
|
||||
#
|
||||
# class PostsController < ActionController::Base
|
||||
# def create
|
||||
# # save post
|
||||
# flash[:notice] = "Successfully created post"
|
||||
# redirect_to posts_path(@post)
|
||||
# end
|
||||
#
|
||||
# def show
|
||||
# # doesn't need to assign the flash notice to the template, that's done automatically
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# show.html.erb
|
||||
# <% if flash[:notice] %>
|
||||
# <div class="notice"><%= flash[:notice] %></div>
|
||||
# <% end %>
|
||||
#
|
||||
# This example just places a string in the flash, but you can put any object in there. And of course, you can put as
|
||||
# many as you like at a time too. Just remember: They'll be gone by the time the next action has been performed.
|
||||
#
|
||||
# See docs on the FlashHash class for more details about the flash.
|
||||
class Flash
|
||||
class FlashNow #:nodoc:
|
||||
def initialize(flash)
|
||||
@flash = flash
|
||||
end
|
||||
|
||||
def []=(k, v)
|
||||
@flash[k] = v
|
||||
@flash.discard(k)
|
||||
v
|
||||
end
|
||||
|
||||
def [](k)
|
||||
@flash[k]
|
||||
end
|
||||
end
|
||||
|
||||
class FlashHash < Hash
|
||||
def initialize #:nodoc:
|
||||
super
|
||||
@used = Set.new
|
||||
end
|
||||
|
||||
def []=(k, v) #:nodoc:
|
||||
keep(k)
|
||||
super
|
||||
end
|
||||
|
||||
def update(h) #:nodoc:
|
||||
h.keys.each { |k| keep(k) }
|
||||
super
|
||||
end
|
||||
|
||||
alias :merge! :update
|
||||
|
||||
def replace(h) #:nodoc:
|
||||
@used = Set.new
|
||||
super
|
||||
end
|
||||
|
||||
# Sets a flash that will not be available to the next action, only to the current.
|
||||
#
|
||||
# flash.now[:message] = "Hello current action"
|
||||
#
|
||||
# This method enables you to use the flash as a central messaging system in your app.
|
||||
# When you need to pass an object to the next action, you use the standard flash assign (<tt>[]=</tt>).
|
||||
# When you need to pass an object to the current action, you use <tt>now</tt>, and your object will
|
||||
# vanish when the current action is done.
|
||||
#
|
||||
# Entries set via <tt>now</tt> are accessed the same way as standard entries: <tt>flash['my-key']</tt>.
|
||||
def now
|
||||
FlashNow.new(self)
|
||||
end
|
||||
|
||||
# Keeps either the entire current flash or a specific flash entry available for the next action:
|
||||
#
|
||||
# flash.keep # keeps the entire flash
|
||||
# flash.keep(:notice) # keeps only the "notice" entry, the rest of the flash is discarded
|
||||
def keep(k = nil)
|
||||
use(k, false)
|
||||
end
|
||||
|
||||
# Marks the entire flash or a single flash entry to be discarded by the end of the current action:
|
||||
#
|
||||
# flash.discard # discard the entire flash at the end of the current action
|
||||
# flash.discard(:warning) # discard only the "warning" entry at the end of the current action
|
||||
def discard(k = nil)
|
||||
use(k)
|
||||
end
|
||||
|
||||
# Mark for removal entries that were kept, and delete unkept ones.
|
||||
#
|
||||
# This method is called automatically by filters, so you generally don't need to care about it.
|
||||
def sweep #:nodoc:
|
||||
keys.each do |k|
|
||||
unless @used.include?(k)
|
||||
@used << k
|
||||
else
|
||||
delete(k)
|
||||
@used.delete(k)
|
||||
end
|
||||
end
|
||||
|
||||
# clean up after keys that could have been left over by calling reject! or shift on the flash
|
||||
(@used - keys).each{ |k| @used.delete(k) }
|
||||
end
|
||||
|
||||
# Convenience accessor for flash[:alert]
|
||||
def alert
|
||||
self[:alert]
|
||||
end
|
||||
|
||||
# Convenience accessor for flash[:alert]=
|
||||
def alert=(message)
|
||||
self[:alert] = message
|
||||
end
|
||||
|
||||
# Convenience accessor for flash[:notice]
|
||||
def notice
|
||||
self[:notice]
|
||||
end
|
||||
|
||||
# Convenience accessor for flash[:notice]=
|
||||
def notice=(message)
|
||||
self[:notice] = message
|
||||
end
|
||||
|
||||
private
|
||||
# Used internally by the <tt>keep</tt> and <tt>discard</tt> methods
|
||||
# use() # marks the entire flash as used
|
||||
# use('msg') # marks the "msg" entry as used
|
||||
# use(nil, false) # marks the entire flash as unused (keeps it around for one more action)
|
||||
# use('msg', false) # marks the "msg" entry as unused (keeps it around for one more action)
|
||||
# Returns the single value for the key you asked to be marked (un)used or the FlashHash itself
|
||||
# if no key is passed.
|
||||
def use(key = nil, used = true)
|
||||
Array(key || keys).each { |k| used ? @used << k : @used.delete(k) }
|
||||
return key ? self[key] : self
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(app)
|
||||
@app = app
|
||||
end
|
||||
|
||||
def call(env)
|
||||
if (session = env['rack.session']) && (flash = session['flash'])
|
||||
flash.sweep
|
||||
end
|
||||
|
||||
@app.call(env)
|
||||
ensure
|
||||
if (session = env['rack.session']) && (flash = session['flash']) && flash.empty?
|
||||
session.delete('flash')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,18 @@
|
|||
module ActionDispatch
|
||||
class Head
|
||||
def initialize(app)
|
||||
@app = app
|
||||
end
|
||||
|
||||
def call(env)
|
||||
if env["REQUEST_METHOD"] == "HEAD"
|
||||
env["REQUEST_METHOD"] = "GET"
|
||||
env["rack.methodoverride.original_method"] = "HEAD"
|
||||
status, headers, body = @app.call(env)
|
||||
[status, headers, []]
|
||||
else
|
||||
@app.call(env)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -102,7 +102,7 @@ module ActionDispatch
|
|||
# Note that the regexp does not allow $1 to end with a ':'
|
||||
$1.constantize
|
||||
rescue LoadError, NameError => const_error
|
||||
raise ActionDispatch::SessionRestoreError, "Session contains objects whose class definition isn't available.\nRemember to require the classes for all objects kept in the session.\n(Original exception: #{const_error.message} [#{const_error.class}])\n"
|
||||
raise ActionDispatch::Session::SessionRestoreError, "Session contains objects whose class definition isn't available.\nRemember to require the classes for all objects kept in the session.\n(Original exception: #{const_error.message} [#{const_error.class}])\n"
|
||||
end
|
||||
|
||||
retry
|
||||
|
|
|
@ -1,7 +1,24 @@
|
|||
require 'active_support/core_ext/exception'
|
||||
require 'active_support/notifications'
|
||||
require 'action_dispatch/http/request'
|
||||
|
||||
module ActionDispatch
|
||||
# This middleware rescues any exception returned by the application and renders
|
||||
# nice exception pages if it's being rescued locally.
|
||||
#
|
||||
# Every time an exception is caught, a notification is published, becoming a good API
|
||||
# to deal with exceptions. So, if you want send an e-mail through ActionMailer
|
||||
# everytime this notification is published, you just need to do the following:
|
||||
#
|
||||
# ActiveSupport::Notifications.subscribe "action_dispatch.show_exception" do |name, start, end, instrumentation_id, payload|
|
||||
# ExceptionNotifier.deliver_exception(start, payload)
|
||||
# end
|
||||
#
|
||||
# The payload is a hash which has two pairs:
|
||||
#
|
||||
# * :env - Contains the rack env for the given request;
|
||||
# * :exception - The exception raised;
|
||||
#
|
||||
class ShowExceptions
|
||||
LOCALHOST = '127.0.0.1'.freeze
|
||||
|
||||
|
@ -44,8 +61,11 @@ module ActionDispatch
|
|||
def call(env)
|
||||
@app.call(env)
|
||||
rescue Exception => exception
|
||||
raise exception if env['action_dispatch.show_exceptions'] == false
|
||||
render_exception(env, exception)
|
||||
ActiveSupport::Notifications.instrument 'action_dispatch.show_exception',
|
||||
:env => env, :exception => exception do
|
||||
raise exception if env['action_dispatch.show_exceptions'] == false
|
||||
render_exception(env, exception)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
module ActionDispatch
|
||||
class StringCoercion
|
||||
class UglyBody < ActiveSupport::BasicObject
|
||||
def initialize(body)
|
||||
@body = body
|
||||
end
|
||||
|
||||
def each
|
||||
@body.each do |part|
|
||||
yield part.to_s
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def method_missing(*args, &block)
|
||||
@body.__send__(*args, &block)
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(app)
|
||||
@app = app
|
||||
end
|
||||
|
||||
def call(env)
|
||||
status, headers, body = @app.call(env)
|
||||
[status, headers, UglyBody.new(body)]
|
||||
end
|
||||
end
|
||||
end
|
|
@ -68,16 +68,11 @@ module ActionDispatch
|
|||
end
|
||||
|
||||
def normalize_path(path)
|
||||
path = nil if path == ""
|
||||
path = "#{@scope[:path]}#{path}" if @scope[:path]
|
||||
path = Rack::Mount::Utils.normalize_path(path) if path
|
||||
|
||||
raise ArgumentError, "path is required" unless path
|
||||
|
||||
path
|
||||
path = "#{@scope[:path]}/#{path}"
|
||||
raise ArgumentError, "path is required" if path.empty?
|
||||
Mapper.normalize_path(path)
|
||||
end
|
||||
|
||||
|
||||
def app
|
||||
Constraints.new(
|
||||
to.respond_to?(:call) ? to : Routing::RouteSet::Dispatcher.new(:defaults => defaults),
|
||||
|
@ -123,7 +118,6 @@ module ActionDispatch
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
def blocks
|
||||
if @options[:constraints].present? && !@options[:constraints].is_a?(Hash)
|
||||
block = @options[:constraints]
|
||||
|
@ -162,6 +156,14 @@ module ActionDispatch
|
|||
end
|
||||
end
|
||||
|
||||
# Invokes Rack::Mount::Utils.normalize path and ensure that
|
||||
# (:locale) becomes (/:locale) instead of /(:locale).
|
||||
def self.normalize_path(path)
|
||||
path = Rack::Mount::Utils.normalize_path(path)
|
||||
path.sub!(%r{/\(+/?:}, '(/:')
|
||||
path
|
||||
end
|
||||
|
||||
module Base
|
||||
def initialize(set)
|
||||
@set = set
|
||||
|
@ -200,13 +202,22 @@ module ActionDispatch
|
|||
path = args.shift || block
|
||||
path_proc = path.is_a?(Proc) ? path : proc { |params| path % params }
|
||||
status = options[:status] || 301
|
||||
body = 'Moved Permanently'
|
||||
|
||||
lambda do |env|
|
||||
req = Rack::Request.new(env)
|
||||
params = path_proc.call(env["action_dispatch.request.path_parameters"])
|
||||
url = req.scheme + '://' + req.host + params
|
||||
req = Request.new(env)
|
||||
|
||||
[ status, {'Location' => url, 'Content-Type' => 'text/html'}, ['Moved Permanently'] ]
|
||||
uri = URI.parse(path_proc.call(req.params.symbolize_keys))
|
||||
uri.scheme ||= req.scheme
|
||||
uri.host ||= req.host
|
||||
uri.port ||= req.port unless req.port == 80
|
||||
|
||||
headers = {
|
||||
'Location' => uri.to_s,
|
||||
'Content-Type' => 'text/html',
|
||||
'Content-Length' => body.length.to_s
|
||||
}
|
||||
[ status, headers, [body] ]
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -236,46 +247,35 @@ module ActionDispatch
|
|||
options[:controller] = args.first
|
||||
end
|
||||
|
||||
if path = options.delete(:path)
|
||||
path_set = true
|
||||
path, @scope[:path] = @scope[:path], Rack::Mount::Utils.normalize_path(@scope[:path].to_s + path.to_s)
|
||||
else
|
||||
path_set = false
|
||||
recover = {}
|
||||
|
||||
options[:constraints] ||= {}
|
||||
unless options[:constraints].is_a?(Hash)
|
||||
block, options[:constraints] = options[:constraints], {}
|
||||
end
|
||||
|
||||
if name_prefix = options.delete(:name_prefix)
|
||||
name_prefix_set = true
|
||||
name_prefix, @scope[:name_prefix] = @scope[:name_prefix], (@scope[:name_prefix] ? "#{@scope[:name_prefix]}_#{name_prefix}" : name_prefix)
|
||||
else
|
||||
name_prefix_set = false
|
||||
scope_options.each do |option|
|
||||
if value = options.delete(option)
|
||||
recover[option] = @scope[option]
|
||||
@scope[option] = send("merge_#{option}_scope", @scope[option], value)
|
||||
end
|
||||
end
|
||||
|
||||
if controller = options.delete(:controller)
|
||||
controller_set = true
|
||||
controller, @scope[:controller] = @scope[:controller], controller
|
||||
else
|
||||
controller_set = false
|
||||
end
|
||||
recover[:block] = @scope[:blocks]
|
||||
@scope[:blocks] = merge_blocks_scope(@scope[:blocks], block)
|
||||
|
||||
constraints = options.delete(:constraints) || {}
|
||||
unless constraints.is_a?(Hash)
|
||||
block, constraints = constraints, {}
|
||||
end
|
||||
constraints, @scope[:constraints] = @scope[:constraints], (@scope[:constraints] || {}).merge(constraints)
|
||||
blocks, @scope[:blocks] = @scope[:blocks], (@scope[:blocks] || []) + [block]
|
||||
|
||||
options, @scope[:options] = @scope[:options], (@scope[:options] || {}).merge(options)
|
||||
recover[:options] = @scope[:options]
|
||||
@scope[:options] = merge_options_scope(@scope[:options], options)
|
||||
|
||||
yield
|
||||
|
||||
self
|
||||
ensure
|
||||
@scope[:path] = path if path_set
|
||||
@scope[:name_prefix] = name_prefix if name_prefix_set
|
||||
@scope[:controller] = controller if controller_set
|
||||
@scope[:options] = options
|
||||
@scope[:blocks] = blocks
|
||||
@scope[:constraints] = constraints
|
||||
scope_options.each do |option|
|
||||
@scope[option] = recover[option] if recover.has_key?(option)
|
||||
end
|
||||
|
||||
@scope[:options] = recover[:options]
|
||||
@scope[:blocks] = recover[:block]
|
||||
end
|
||||
|
||||
def controller(controller)
|
||||
|
@ -283,7 +283,7 @@ module ActionDispatch
|
|||
end
|
||||
|
||||
def namespace(path)
|
||||
scope("/#{path}") { yield }
|
||||
scope(path.to_s, :name_prefix => path.to_s, :namespace => path.to_s) { yield }
|
||||
end
|
||||
|
||||
def constraints(constraints = {})
|
||||
|
@ -304,25 +304,83 @@ module ActionDispatch
|
|||
args.push(options)
|
||||
super(*args)
|
||||
end
|
||||
|
||||
private
|
||||
def scope_options
|
||||
@scope_options ||= private_methods.grep(/^merge_(.+)_scope$/) { $1.to_sym }
|
||||
end
|
||||
|
||||
def merge_path_scope(parent, child)
|
||||
Mapper.normalize_path("#{parent}/#{child}")
|
||||
end
|
||||
|
||||
def merge_name_prefix_scope(parent, child)
|
||||
parent ? "#{parent}_#{child}" : child
|
||||
end
|
||||
|
||||
def merge_namespace_scope(parent, child)
|
||||
parent ? "#{parent}/#{child}" : child
|
||||
end
|
||||
|
||||
def merge_controller_scope(parent, child)
|
||||
@scope[:namespace] ? "#{@scope[:namespace]}/#{child}" : child
|
||||
end
|
||||
|
||||
def merge_resources_path_names_scope(parent, child)
|
||||
merge_options_scope(parent, child)
|
||||
end
|
||||
|
||||
def merge_constraints_scope(parent, child)
|
||||
merge_options_scope(parent, child)
|
||||
end
|
||||
|
||||
def merge_blocks_scope(parent, child)
|
||||
(parent || []) + [child]
|
||||
end
|
||||
|
||||
def merge_options_scope(parent, child)
|
||||
(parent || {}).merge(child)
|
||||
end
|
||||
end
|
||||
|
||||
module Resources
|
||||
CRUD_ACTIONS = [:index, :show, :create, :update, :destroy]
|
||||
|
||||
class Resource #:nodoc:
|
||||
attr_reader :plural, :singular
|
||||
def self.default_actions
|
||||
[:index, :create, :new, :show, :update, :destroy, :edit]
|
||||
end
|
||||
|
||||
attr_reader :plural, :singular, :options
|
||||
|
||||
def initialize(entities, options = {})
|
||||
entities = entities.to_s
|
||||
@options = options
|
||||
|
||||
@plural = entities.pluralize
|
||||
@singular = entities.singularize
|
||||
end
|
||||
|
||||
def default_actions
|
||||
self.class.default_actions
|
||||
end
|
||||
|
||||
def actions
|
||||
if only = options[:only]
|
||||
only.map(&:to_sym)
|
||||
elsif except = options[:except]
|
||||
default_actions - except.map(&:to_sym)
|
||||
else
|
||||
default_actions
|
||||
end
|
||||
end
|
||||
|
||||
def name
|
||||
plural
|
||||
options[:as] || plural
|
||||
end
|
||||
|
||||
def controller
|
||||
plural
|
||||
options[:controller] || plural
|
||||
end
|
||||
|
||||
def member_name
|
||||
|
@ -339,15 +397,24 @@ module ActionDispatch
|
|||
end
|
||||
|
||||
class SingletonResource < Resource #:nodoc:
|
||||
def self.default_actions
|
||||
[:show, :create, :update, :destroy, :new, :edit]
|
||||
end
|
||||
|
||||
def initialize(entity, options = {})
|
||||
super
|
||||
end
|
||||
|
||||
def name
|
||||
singular
|
||||
options[:as] || singular
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(*args)
|
||||
super
|
||||
@scope[:resources_path_names] = @set.resources_path_names
|
||||
end
|
||||
|
||||
def resource(*resources, &block)
|
||||
options = resources.extract_options!
|
||||
|
||||
|
@ -357,7 +424,14 @@ module ActionDispatch
|
|||
return self
|
||||
end
|
||||
|
||||
resource = SingletonResource.new(resources.pop)
|
||||
if path_names = options.delete(:path_names)
|
||||
scope(:resources_path_names => path_names) do
|
||||
resource(resources, options)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
resource = SingletonResource.new(resources.pop, options)
|
||||
|
||||
if @scope[:scope_level] == :resources
|
||||
nested do
|
||||
|
@ -366,16 +440,16 @@ module ActionDispatch
|
|||
return self
|
||||
end
|
||||
|
||||
scope(:path => "/#{resource.name}", :controller => resource.controller) do
|
||||
scope(:path => resource.name.to_s, :controller => resource.controller) do
|
||||
with_scope_level(:resource, resource) do
|
||||
yield if block_given?
|
||||
|
||||
get "(.:format)", :to => :show, :as => resource.member_name
|
||||
post "(.:format)", :to => :create
|
||||
put "(.:format)", :to => :update
|
||||
delete "(.:format)", :to => :destroy
|
||||
get "/new(.:format)", :to => :new, :as => "new_#{resource.singular}"
|
||||
get "/edit(.:format)", :to => :edit, :as => "edit_#{resource.singular}"
|
||||
get :show, :as => resource.member_name if resource.actions.include?(:show)
|
||||
post :create if resource.actions.include?(:create)
|
||||
put :update if resource.actions.include?(:update)
|
||||
delete :destroy if resource.actions.include?(:destroy)
|
||||
get :new, :as => resource.singular if resource.actions.include?(:new)
|
||||
get :edit, :as => resource.singular if resource.actions.include?(:edit)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -391,7 +465,14 @@ module ActionDispatch
|
|||
return self
|
||||
end
|
||||
|
||||
resource = Resource.new(resources.pop)
|
||||
if path_names = options.delete(:path_names)
|
||||
scope(:resources_path_names => path_names) do
|
||||
resources(resources, options)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
resource = Resource.new(resources.pop, options)
|
||||
|
||||
if @scope[:scope_level] == :resources
|
||||
nested do
|
||||
|
@ -400,28 +481,22 @@ module ActionDispatch
|
|||
return self
|
||||
end
|
||||
|
||||
scope(:path => "/#{resource.name}", :controller => resource.controller) do
|
||||
scope(:path => resource.name.to_s, :controller => resource.controller) do
|
||||
with_scope_level(:resources, resource) do
|
||||
yield if block_given?
|
||||
|
||||
with_scope_level(:collection) do
|
||||
get "(.:format)", :to => :index, :as => resource.collection_name
|
||||
post "(.:format)", :to => :create
|
||||
|
||||
with_exclusive_name_prefix :new do
|
||||
get "/new(.:format)", :to => :new, :as => resource.singular
|
||||
end
|
||||
get :index, :as => resource.collection_name if resource.actions.include?(:index)
|
||||
post :create if resource.actions.include?(:create)
|
||||
get :new, :as => resource.singular if resource.actions.include?(:new)
|
||||
end
|
||||
|
||||
with_scope_level(:member) do
|
||||
scope("/:id") do
|
||||
get "(.:format)", :to => :show, :as => resource.member_name
|
||||
put "(.:format)", :to => :update
|
||||
delete "(.:format)", :to => :destroy
|
||||
|
||||
with_exclusive_name_prefix :edit do
|
||||
get "/edit(.:format)", :to => :edit, :as => resource.singular
|
||||
end
|
||||
scope(':id') do
|
||||
get :show, :as => resource.member_name if resource.actions.include?(:show)
|
||||
put :update if resource.actions.include?(:update)
|
||||
delete :destroy if resource.actions.include?(:destroy)
|
||||
get :edit, :as => resource.singular if resource.actions.include?(:edit)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -448,7 +523,7 @@ module ActionDispatch
|
|||
end
|
||||
|
||||
with_scope_level(:member) do
|
||||
scope("/:id", :name_prefix => parent_resource.member_name, :as => "") do
|
||||
scope(':id', :name_prefix => parent_resource.member_name, :as => "") do
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
@ -460,7 +535,7 @@ module ActionDispatch
|
|||
end
|
||||
|
||||
with_scope_level(:nested) do
|
||||
scope("/#{parent_resource.id_segment}", :name_prefix => parent_resource.member_name) do
|
||||
scope(parent_resource.id_segment, :name_prefix => parent_resource.member_name) do
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
@ -474,9 +549,22 @@ module ActionDispatch
|
|||
return self
|
||||
end
|
||||
|
||||
resources_path_names = options.delete(:path_names)
|
||||
|
||||
if args.first.is_a?(Symbol)
|
||||
with_exclusive_name_prefix(args.first) do
|
||||
return match("/#{args.first}(.:format)", options.merge(:to => args.first.to_sym))
|
||||
action = args.first
|
||||
if CRUD_ACTIONS.include?(action)
|
||||
begin
|
||||
old_path = @scope[:path]
|
||||
@scope[:path] = "#{@scope[:path]}(.:format)"
|
||||
return match(options.reverse_merge(:to => action))
|
||||
ensure
|
||||
@scope[:path] = old_path
|
||||
end
|
||||
else
|
||||
with_exclusive_name_prefix(action) do
|
||||
return match("#{action_path(action, resources_path_names)}(.:format)", options.reverse_merge(:to => action))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -502,6 +590,11 @@ module ActionDispatch
|
|||
end
|
||||
|
||||
private
|
||||
def action_path(name, path_names = nil)
|
||||
path_names ||= @scope[:resources_path_names]
|
||||
path_names[name.to_sym] || name.to_s
|
||||
end
|
||||
|
||||
def with_exclusive_name_prefix(prefix)
|
||||
begin
|
||||
old_name_prefix = @scope[:name_prefix]
|
||||
|
|
|
@ -74,9 +74,8 @@ module ActionDispatch
|
|||
@routes = {}
|
||||
@helpers = []
|
||||
|
||||
@module ||= Module.new
|
||||
@module.instance_methods.each do |selector|
|
||||
@module.class_eval { remove_method selector }
|
||||
@module ||= Module.new do
|
||||
instance_methods.each { |selector| remove_method(selector) }
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -138,67 +137,87 @@ module ActionDispatch
|
|||
end
|
||||
end
|
||||
|
||||
def named_helper_module_eval(code, *args)
|
||||
@module.module_eval(code, *args)
|
||||
end
|
||||
|
||||
def define_hash_access(route, name, kind, options)
|
||||
selector = hash_access_name(name, kind)
|
||||
named_helper_module_eval <<-end_eval # We use module_eval to avoid leaks
|
||||
|
||||
# We use module_eval to avoid leaks
|
||||
@module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1
|
||||
def #{selector}(options = nil) # def hash_for_users_url(options = nil)
|
||||
options ? #{options.inspect}.merge(options) : #{options.inspect} # options ? {:only_path=>false}.merge(options) : {:only_path=>false}
|
||||
end # end
|
||||
protected :#{selector} # protected :hash_for_users_url
|
||||
end_eval
|
||||
END_EVAL
|
||||
helpers << selector
|
||||
end
|
||||
|
||||
# Create a url helper allowing ordered parameters to be associated
|
||||
# with corresponding dynamic segments, so you can do:
|
||||
#
|
||||
# foo_url(bar, baz, bang)
|
||||
#
|
||||
# Instead of:
|
||||
#
|
||||
# foo_url(:bar => bar, :baz => baz, :bang => bang)
|
||||
#
|
||||
# Also allow options hash, so you can do:
|
||||
#
|
||||
# foo_url(bar, baz, bang, :sort_by => 'baz')
|
||||
#
|
||||
def define_url_helper(route, name, kind, options)
|
||||
selector = url_helper_name(name, kind)
|
||||
# The segment keys used for positional parameters
|
||||
|
||||
hash_access_method = hash_access_name(name, kind)
|
||||
|
||||
# allow ordered parameters to be associated with corresponding
|
||||
# dynamic segments, so you can do
|
||||
# We use module_eval to avoid leaks.
|
||||
#
|
||||
# foo_url(bar, baz, bang)
|
||||
# def users_url(*args)
|
||||
# if args.empty? || Hash === args.first
|
||||
# options = hash_for_users_url(args.first || {})
|
||||
# else
|
||||
# options = hash_for_users_url(args.extract_options!)
|
||||
# default = default_url_options(options) if self.respond_to?(:default_url_options, true)
|
||||
# options = (default ||= {}).merge(options)
|
||||
#
|
||||
# instead of
|
||||
# keys = []
|
||||
# keys -= options.keys if args.size < keys.size - 1
|
||||
#
|
||||
# foo_url(:bar => bar, :baz => baz, :bang => bang)
|
||||
# args = args.zip(keys).inject({}) do |h, (v, k)|
|
||||
# h[k] = v
|
||||
# h
|
||||
# end
|
||||
#
|
||||
# Also allow options hash, so you can do
|
||||
# # Tell url_for to skip default_url_options
|
||||
# options[:use_defaults] = false
|
||||
# options.merge!(args)
|
||||
# end
|
||||
#
|
||||
# foo_url(bar, baz, bang, :sort_by => 'baz')
|
||||
#
|
||||
named_helper_module_eval <<-end_eval # We use module_eval to avoid leaks
|
||||
def #{selector}(*args) # def users_url(*args)
|
||||
#
|
||||
opts = if args.empty? || Hash === args.first # opts = if args.empty? || Hash === args.first
|
||||
args.first || {} # args.first || {}
|
||||
else # else
|
||||
options = args.extract_options! # options = args.extract_options!
|
||||
args = args.zip(#{route.segment_keys.inspect}).inject({}) do |h, (v, k)| # args = args.zip([]).inject({}) do |h, (v, k)|
|
||||
h[k] = v # h[k] = v
|
||||
h # h
|
||||
end # end
|
||||
options.merge(args) # options.merge(args)
|
||||
end # end
|
||||
#
|
||||
url_for(#{hash_access_method}(opts)) # url_for(hash_for_users_url(opts))
|
||||
#
|
||||
end # end
|
||||
#Add an alias to support the now deprecated formatted_* URL. # #Add an alias to support the now deprecated formatted_* URL.
|
||||
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)
|
||||
#{selector}(*args) # users_url(*args)
|
||||
end # end
|
||||
protected :#{selector} # protected :users_url
|
||||
end_eval
|
||||
# url_for(options)
|
||||
# end
|
||||
@module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1
|
||||
def #{selector}(*args)
|
||||
if args.empty? || Hash === args.first
|
||||
options = #{hash_access_method}(args.first || {})
|
||||
else
|
||||
options = #{hash_access_method}(args.extract_options!)
|
||||
default = default_url_options(options) if self.respond_to?(:default_url_options, true)
|
||||
options = (default ||= {}).merge(options)
|
||||
|
||||
keys = #{route.segment_keys.inspect}
|
||||
keys -= options.keys if args.size < keys.size - 1 # take format into account
|
||||
|
||||
args = args.zip(keys).inject({}) do |h, (v, k)|
|
||||
h[k] = v
|
||||
h
|
||||
end
|
||||
|
||||
# Tell url_for to skip default_url_options
|
||||
options[:use_defaults] = false
|
||||
options.merge!(args)
|
||||
end
|
||||
|
||||
url_for(options)
|
||||
end
|
||||
protected :#{selector}
|
||||
END_EVAL
|
||||
helpers << selector
|
||||
end
|
||||
end
|
||||
|
@ -206,9 +225,16 @@ module ActionDispatch
|
|||
attr_accessor :routes, :named_routes
|
||||
attr_accessor :disable_clear_and_finalize
|
||||
|
||||
def self.default_resources_path_names
|
||||
{ :new => 'new', :edit => 'edit' }
|
||||
end
|
||||
|
||||
attr_accessor :resources_path_names
|
||||
|
||||
def initialize
|
||||
self.routes = []
|
||||
self.named_routes = NamedRouteCollection.new
|
||||
self.resources_path_names = self.class.default_resources_path_names
|
||||
|
||||
@disable_clear_and_finalize = false
|
||||
end
|
||||
|
|
|
@ -2,6 +2,15 @@ module ActionDispatch
|
|||
module Assertions
|
||||
# A small suite of assertions that test responses from Rails applications.
|
||||
module ResponseAssertions
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
# TODO: Need to pull in AV::Template monkey patches that track which
|
||||
# templates are rendered. assert_template should probably be part
|
||||
# of AV instead of AD.
|
||||
require 'action_view/test_case'
|
||||
end
|
||||
|
||||
# Asserts that the response is one of the following types:
|
||||
#
|
||||
# * <tt>:success</tt> - Status code was 200
|
||||
|
|
|
@ -524,7 +524,7 @@ module ActionDispatch
|
|||
|
||||
fix_content = lambda do |node|
|
||||
# Gets around a bug in the Rails 1.1 HTML parser.
|
||||
node.content.gsub(/<!\[CDATA\[(.*)(\]\]>)?/m) { CGI.escapeHTML($1) }
|
||||
node.content.gsub(/<!\[CDATA\[(.*)(\]\]>)?/m) { Rack::Utils.escapeHTML($1) }
|
||||
end
|
||||
|
||||
selected = elements.map do |element|
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
require 'stringio'
|
||||
require 'uri'
|
||||
require 'active_support/test_case'
|
||||
require 'active_support/core_ext/object/metaclass'
|
||||
require 'rack/test'
|
||||
|
||||
|
|
|
@ -52,6 +52,8 @@ module ActionView
|
|||
autoload :TemplateHandler, 'action_view/template'
|
||||
autoload :TemplateHandlers, 'action_view/template'
|
||||
end
|
||||
|
||||
autoload :TestCase, 'action_view/test_case'
|
||||
end
|
||||
|
||||
require 'action_view/erb/util'
|
||||
|
|
|
@ -216,7 +216,7 @@ module ActionView
|
|||
end
|
||||
options[:object_name] ||= params.first
|
||||
|
||||
I18n.with_options :locale => options[:locale], :scope => [:activemodel, :errors, :template] do |locale|
|
||||
I18n.with_options :locale => options[:locale], :scope => [:errors, :template] do |locale|
|
||||
header_message = if options.include?(:header_message)
|
||||
options[:header_message]
|
||||
else
|
||||
|
|
|
@ -616,7 +616,7 @@ module ActionView
|
|||
|
||||
build_selects_from_types(order)
|
||||
else
|
||||
"#{select_date}#{@options[:datetime_separator]}#{select_time}"
|
||||
"#{select_date}#{@options[:datetime_separator]}#{select_time}".html_safe!
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -835,7 +835,7 @@ module ActionView
|
|||
select_html << prompt_option_tag(type, @options[:prompt]) + "\n" if @options[:prompt]
|
||||
select_html << select_options_as_html.to_s
|
||||
|
||||
content_tag(:select, select_html, select_options) + "\n"
|
||||
(content_tag(:select, select_html, select_options) + "\n").html_safe!
|
||||
end
|
||||
|
||||
# Builds a prompt option tag with supplied options or from default options
|
||||
|
@ -860,12 +860,12 @@ module ActionView
|
|||
# build_hidden(:year, 2008)
|
||||
# => "<input id="post_written_on_1i" name="post[written_on(1i)]" type="hidden" value="2008" />"
|
||||
def build_hidden(type, value)
|
||||
tag(:input, {
|
||||
(tag(:input, {
|
||||
:type => "hidden",
|
||||
:id => input_id_from_type(type),
|
||||
:name => input_name_from_type(type),
|
||||
:value => value
|
||||
}) + "\n"
|
||||
}) + "\n").html_safe!
|
||||
end
|
||||
|
||||
# Returns the name attribute for the input tag
|
||||
|
@ -896,7 +896,7 @@ module ActionView
|
|||
separator = separator(type) unless type == order.first # don't add on last field
|
||||
select.insert(0, separator.to_s + send("select_#{type}").to_s)
|
||||
end
|
||||
select
|
||||
select.html_safe!
|
||||
end
|
||||
|
||||
# Returns the separator for a given datetime component
|
||||
|
|
|
@ -505,7 +505,7 @@ module ActionView
|
|||
|
||||
# Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object
|
||||
# assigned to the template (identified by +object+). The text of label will default to the attribute name unless a translation
|
||||
# is found in the current I18n locale (through views.labels.<modelname>.<attribute>) or you specify it explicitly.
|
||||
# is found in the current I18n locale (through helpers.label.<modelname>.<attribute>) or you specify it explicitly.
|
||||
# Additional options on the label tag can be passed as a hash with +options+. These options will be tagged
|
||||
# onto the HTML as an HTML element attribute as in the example shown, except for the <tt>:value</tt> option, which is designed to
|
||||
# target labels for radio_button tags (where the value is used in the ID of the input tag).
|
||||
|
@ -517,8 +517,8 @@ module ActionView
|
|||
# You can localize your labels based on model and attribute names.
|
||||
# For example you can define the following in your locale (e.g. en.yml)
|
||||
#
|
||||
# views:
|
||||
# labels:
|
||||
# helpers:
|
||||
# label:
|
||||
# post:
|
||||
# body: "Write your entire text here"
|
||||
#
|
||||
|
@ -777,7 +777,7 @@ module ActionView
|
|||
options["for"] ||= name_and_id["id"]
|
||||
|
||||
content = if text.blank?
|
||||
I18n.t("views.labels.#{object_name}.#{method_name}", :default => "").presence
|
||||
I18n.t("helpers.label.#{object_name}.#{method_name}", :default => "").presence
|
||||
else
|
||||
text.to_s
|
||||
end
|
||||
|
@ -798,7 +798,7 @@ module ActionView
|
|||
if field_type == "hidden"
|
||||
options.delete("size")
|
||||
end
|
||||
options["type"] = field_type
|
||||
options["type"] ||= field_type
|
||||
options["value"] ||= value_before_type_cast(object) unless field_type == "file"
|
||||
options["value"] &&= html_escape(options["value"])
|
||||
add_default_name_and_id(options)
|
||||
|
@ -842,7 +842,12 @@ module ActionView
|
|||
checked = self.class.check_box_checked?(value(object), checked_value)
|
||||
end
|
||||
options["checked"] = "checked" if checked
|
||||
add_default_name_and_id(options)
|
||||
if options["multiple"]
|
||||
add_default_name_and_id_for_value(checked_value, options)
|
||||
options.delete("multiple")
|
||||
else
|
||||
add_default_name_and_id(options)
|
||||
end
|
||||
hidden = tag("input", "name" => options["name"], "type" => "hidden", "value" => options['disabled'] && checked ? checked_value : unchecked_value)
|
||||
checkbox = tag("input", options)
|
||||
(hidden + checkbox).html_safe!
|
||||
|
@ -1058,7 +1063,7 @@ module ActionView
|
|||
def radio_button(method, tag_value, options = {})
|
||||
@template.radio_button(@object_name, method, tag_value, objectify_options(options))
|
||||
end
|
||||
|
||||
|
||||
def hidden_field(method, options = {})
|
||||
@emitted_hidden_id = true if method == :id
|
||||
@template.hidden_field(@object_name, method, objectify_options(options))
|
||||
|
@ -1072,7 +1077,36 @@ module ActionView
|
|||
@template.error_messages_for(@object_name, objectify_options(options))
|
||||
end
|
||||
|
||||
def submit(value = "Save changes", options = {})
|
||||
# Add the submit button for the given form. When no value is given, it checks
|
||||
# if the object is a new resource or not to create the proper label:
|
||||
#
|
||||
# <% form_for @post do |f| %>
|
||||
# <%= f.submit %>
|
||||
# <% end %>
|
||||
#
|
||||
# In the example above, if @post is a new record, it will use "Create Post" as
|
||||
# submit button label, otherwise, it uses "Update Post".
|
||||
#
|
||||
# Those labels can be customized using I18n, under the helpers.submit key and accept
|
||||
# the {{model}} as translation interpolation:
|
||||
#
|
||||
# en:
|
||||
# helpers:
|
||||
# submit:
|
||||
# create: "Create a {{model}}"
|
||||
# update: "Confirm changes to {{model}}"
|
||||
#
|
||||
# It also searches for a key specific for the given object:
|
||||
#
|
||||
# en:
|
||||
# helpers:
|
||||
# submit:
|
||||
# post:
|
||||
# create: "Add {{model}}"
|
||||
#
|
||||
def submit(value=nil, options={})
|
||||
value, options = nil, value if value.is_a?(Hash)
|
||||
value ||= submit_default_value
|
||||
@template.submit_tag(value, options.reverse_merge(:id => "#{object_name}_submit"))
|
||||
end
|
||||
|
||||
|
@ -1085,6 +1119,24 @@ module ActionView
|
|||
@default_options.merge(options.merge(:object => @object))
|
||||
end
|
||||
|
||||
def submit_default_value
|
||||
object = @object.respond_to?(:to_model) ? @object.to_model : @object
|
||||
key = object ? (object.new_record? ? :create : :update) : :submit
|
||||
|
||||
model = if object.class.respond_to?(:model_name)
|
||||
object.class.model_name.human
|
||||
else
|
||||
@object_name.to_s.humanize
|
||||
end
|
||||
|
||||
defaults = []
|
||||
defaults << :"helpers.submit.#{object_name}.#{key}"
|
||||
defaults << :"helpers.submit.#{key}"
|
||||
defaults << "#{key.to_s.humanize} #{model}"
|
||||
|
||||
I18n.t(defaults.shift, :model => model, :default => defaults)
|
||||
end
|
||||
|
||||
def nested_attributes_association?(association_name)
|
||||
@object.respond_to?("#{association_name}_attributes=")
|
||||
end
|
||||
|
|
|
@ -571,7 +571,7 @@ module ActionView
|
|||
option_tags = "<option value=\"\">#{options[:include_blank] if options[:include_blank].kind_of?(String)}</option>\n" + option_tags
|
||||
end
|
||||
if value.blank? && options[:prompt]
|
||||
prompt = options[:prompt].kind_of?(String) ? options[:prompt] : I18n.translate('support.select.prompt', :default => 'Please select')
|
||||
prompt = options[:prompt].kind_of?(String) ? options[:prompt] : I18n.translate('helpers.select.prompt', :default => 'Please select')
|
||||
"<option value=\"\">#{prompt}</option>\n" + option_tags
|
||||
else
|
||||
option_tags
|
||||
|
|
|
@ -92,7 +92,7 @@ module ActionView
|
|||
:precision => precision,
|
||||
:delimiter => delimiter,
|
||||
:separator => separator)
|
||||
).gsub(/%u/, unit)
|
||||
).gsub(/%u/, unit).html_safe!
|
||||
rescue
|
||||
number
|
||||
end
|
||||
|
|
|
@ -1030,7 +1030,7 @@ module ActionView
|
|||
# page.hide 'spinner'
|
||||
# end
|
||||
def update_page(&block)
|
||||
JavaScriptGenerator.new(@template, &block).to_s
|
||||
JavaScriptGenerator.new(@template, &block).to_s.html_safe!
|
||||
end
|
||||
|
||||
# Works like update_page but wraps the generated JavaScript in a <script>
|
||||
|
|
|
@ -565,7 +565,7 @@ module ActionView
|
|||
end
|
||||
|
||||
link_text = block_given?? yield(href) : href
|
||||
href = 'http://' + href unless href.index('http') == 0
|
||||
href = 'http://' + href unless href =~ %r{^[a-z]+://}i
|
||||
|
||||
content_tag(:a, h(link_text), link_attributes.merge('href' => href)) + punctuation.reverse.join('')
|
||||
end
|
||||
|
|
|
@ -11,6 +11,11 @@ module ActionView
|
|||
module UrlHelper
|
||||
include JavaScriptHelper
|
||||
|
||||
# Need to map default url options to controller one.
|
||||
def default_url_options(*args) #:nodoc:
|
||||
@controller.send(:default_url_options, *args)
|
||||
end
|
||||
|
||||
# Returns the URL for the set of +options+ provided. This takes the
|
||||
# same options as +url_for+ in Action Controller (see the
|
||||
# documentation for <tt>ActionController::Base#url_for</tt>). Note that by default
|
||||
|
@ -461,10 +466,10 @@ module ActionView
|
|||
|
||||
string = ''
|
||||
extras = ''
|
||||
extras << "cc=#{CGI.escape(cc).gsub("+", "%20")}&" unless cc.nil?
|
||||
extras << "bcc=#{CGI.escape(bcc).gsub("+", "%20")}&" unless bcc.nil?
|
||||
extras << "body=#{CGI.escape(body).gsub("+", "%20")}&" unless body.nil?
|
||||
extras << "subject=#{CGI.escape(subject).gsub("+", "%20")}&" unless subject.nil?
|
||||
extras << "cc=#{Rack::Utils.escape(cc).gsub("+", "%20")}&" unless cc.nil?
|
||||
extras << "bcc=#{Rack::Utils.escape(bcc).gsub("+", "%20")}&" unless bcc.nil?
|
||||
extras << "body=#{Rack::Utils.escape(body).gsub("+", "%20")}&" unless body.nil?
|
||||
extras << "subject=#{Rack::Utils.escape(subject).gsub("+", "%20")}&" unless subject.nil?
|
||||
extras = "?" << extras.gsub!(/&?$/,"") unless extras.empty?
|
||||
|
||||
email_address = email_address.to_s
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
delimiter: ","
|
||||
# Number of decimals, behind the separator (the number 1 with a precision of 2 gives: 1.00)
|
||||
precision: 3
|
||||
|
||||
|
||||
# Used in number_to_currency()
|
||||
currency:
|
||||
format:
|
||||
|
@ -20,15 +20,15 @@
|
|||
separator: "."
|
||||
delimiter: ","
|
||||
precision: 2
|
||||
|
||||
|
||||
# Used in number_to_percentage()
|
||||
percentage:
|
||||
format:
|
||||
# These three are to override number.format and are optional
|
||||
# separator:
|
||||
# separator:
|
||||
delimiter: ""
|
||||
# precision:
|
||||
|
||||
# precision:
|
||||
|
||||
# Used in number_to_precision()
|
||||
precision:
|
||||
format:
|
||||
|
@ -36,12 +36,12 @@
|
|||
# separator:
|
||||
delimiter: ""
|
||||
# precision:
|
||||
|
||||
|
||||
# Used in number_to_human_size()
|
||||
human:
|
||||
format:
|
||||
# These three are to override number.format and are optional
|
||||
# separator:
|
||||
# separator:
|
||||
delimiter: ""
|
||||
precision: 1
|
||||
storage_units:
|
||||
|
@ -102,16 +102,22 @@
|
|||
minute: "Minute"
|
||||
second: "Seconds"
|
||||
|
||||
activemodel:
|
||||
errors:
|
||||
template:
|
||||
header:
|
||||
one: "1 error prohibited this {{model}} from being saved"
|
||||
other: "{{count}} errors prohibited this {{model}} from being saved"
|
||||
# The variable :count is also available
|
||||
body: "There were problems with the following fields:"
|
||||
errors:
|
||||
template:
|
||||
header:
|
||||
one: "1 error prohibited this {{model}} from being saved"
|
||||
other: "{{count}} errors prohibited this {{model}} from being saved"
|
||||
# The variable :count is also available
|
||||
body: "There were problems with the following fields:"
|
||||
|
||||
support:
|
||||
helpers:
|
||||
select:
|
||||
# default value for :prompt => true in FormOptionsHelper
|
||||
# Default value for :prompt => true in FormOptionsHelper
|
||||
prompt: "Please select"
|
||||
|
||||
# Default translation keys for submit FormHelper
|
||||
submit:
|
||||
create: 'Create {{model}}'
|
||||
update: 'Update {{model}}'
|
||||
submit: 'Save {{model}}'
|
||||
|
||||
|
|
|
@ -1,2 +1,17 @@
|
|||
require "action_view"
|
||||
require "rails"
|
||||
require "rails"
|
||||
|
||||
module ActionView
|
||||
class Railtie < Rails::Railtie
|
||||
plugin_name :action_view
|
||||
|
||||
require "action_view/railties/subscriber"
|
||||
subscriber ActionView::Railties::Subscriber.new
|
||||
|
||||
initializer "action_view.cache_asset_timestamps" do |app|
|
||||
unless app.config.cache_classes
|
||||
ActionView::Helpers::AssetTagHelper.cache_asset_timestamps = false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,24 @@
|
|||
module ActionView
|
||||
module Railties
|
||||
class Subscriber < Rails::Subscriber
|
||||
def render_template(event)
|
||||
message = "Rendered #{from_rails_root(event.payload[:identifier])}"
|
||||
message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout]
|
||||
message << (" (%.1fms)" % event.duration)
|
||||
info(message)
|
||||
end
|
||||
alias :render_partial :render_template
|
||||
alias :render_collection :render_template
|
||||
|
||||
def logger
|
||||
ActionController::Base.logger
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def from_rails_root(string)
|
||||
string.sub("#{Rails.root}/", "").sub(/^app\/views\//, "")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -212,34 +212,34 @@ module ActionView
|
|||
end
|
||||
|
||||
def render
|
||||
options = @options
|
||||
identifier = ((@template = find_template) ? @template.identifier : @path)
|
||||
|
||||
if @collection
|
||||
ActiveSupport::Notifications.instrument(:render_collection, :path => @path,
|
||||
:count => @collection.size) do
|
||||
ActiveSupport::Notifications.instrument("action_view.render_collection",
|
||||
:identifier => identifier || "collection", :count => @collection.size) do
|
||||
render_collection
|
||||
end
|
||||
else
|
||||
content = ActiveSupport::Notifications.instrument(:render_partial, :path => @path) do
|
||||
content = ActiveSupport::Notifications.instrument("action_view.render_partial",
|
||||
:identifier => identifier) do
|
||||
render_partial
|
||||
end
|
||||
|
||||
if !@block && options[:layout]
|
||||
content = @view._render_layout(find_template(options[:layout]), @locals){ content }
|
||||
if !@block && (layout = @options[:layout])
|
||||
content = @view._render_layout(find_template(layout), @locals){ content }
|
||||
end
|
||||
content
|
||||
end
|
||||
end
|
||||
|
||||
def render_collection
|
||||
@template = template = find_template
|
||||
return nil if @collection.blank?
|
||||
|
||||
if @options.key?(:spacer_template)
|
||||
spacer = find_template(@options[:spacer_template]).render(@view, @locals)
|
||||
end
|
||||
|
||||
result = template ? collection_with_template : collection_without_template
|
||||
result = @template ? collection_with_template : collection_without_template
|
||||
result.join(spacer).html_safe!
|
||||
end
|
||||
|
||||
|
@ -277,7 +277,6 @@ module ActionView
|
|||
end
|
||||
|
||||
def render_partial(object = @object)
|
||||
@template = template = find_template
|
||||
locals, view = @locals, @view
|
||||
|
||||
object ||= locals[template.variable_name]
|
||||
|
|
|
@ -93,25 +93,23 @@ module ActionView
|
|||
def _render_template(template, layout = nil, options = {})
|
||||
locals = options[:locals] || {}
|
||||
|
||||
content = ActiveSupport::Notifications.instrument(:render_template,
|
||||
:identifier => template.identifier, :layout => (layout ? layout.identifier : nil)) do
|
||||
template.render(self, locals)
|
||||
ActiveSupport::Notifications.instrument("action_view.render_template",
|
||||
:identifier => template.identifier, :layout => layout.try(:identifier)) do
|
||||
|
||||
content = template.render(self, locals)
|
||||
@_content_for[:layout] = content
|
||||
|
||||
if layout
|
||||
@_layout = layout.identifier
|
||||
content = _render_layout(layout, locals)
|
||||
end
|
||||
|
||||
content
|
||||
end
|
||||
|
||||
@_content_for[:layout] = content
|
||||
|
||||
if layout
|
||||
@_layout = layout.identifier
|
||||
content = _render_layout(layout, locals)
|
||||
end
|
||||
|
||||
content
|
||||
end
|
||||
|
||||
def _render_layout(layout, locals, &block)
|
||||
ActiveSupport::Notifications.instrument(:render_layout, :identifier => layout.identifier) do
|
||||
layout.render(self, locals){ |*name| _layout_for(*name, &block) }
|
||||
end
|
||||
layout.render(self, locals){ |*name| _layout_for(*name, &block) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -13,7 +13,7 @@ module ActionView #:nodoc:
|
|||
end
|
||||
|
||||
def identifier
|
||||
self
|
||||
'text template'
|
||||
end
|
||||
|
||||
def inspect
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
require 'active_support/test_case'
|
||||
require 'action_controller/test_case'
|
||||
|
||||
module ActionView
|
||||
class Base
|
||||
alias_method :initialize_without_template_tracking, :initialize
|
||||
|
|
|
@ -0,0 +1,272 @@
|
|||
require 'abstract_unit'
|
||||
|
||||
module AbstractController
|
||||
module Testing
|
||||
|
||||
class UrlForTests < ActionController::TestCase
|
||||
class W
|
||||
include AbstractController::UrlFor
|
||||
end
|
||||
|
||||
def teardown
|
||||
W.default_url_options.clear
|
||||
end
|
||||
|
||||
def add_host!
|
||||
W.default_url_options[:host] = 'www.basecamphq.com'
|
||||
end
|
||||
|
||||
def test_exception_is_thrown_without_host
|
||||
assert_raise RuntimeError do
|
||||
W.new.url_for :controller => 'c', :action => 'a', :id => 'i'
|
||||
end
|
||||
end
|
||||
|
||||
def test_anchor
|
||||
assert_equal('/c/a#anchor',
|
||||
W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => 'anchor')
|
||||
)
|
||||
end
|
||||
|
||||
def test_anchor_should_call_to_param
|
||||
assert_equal('/c/a#anchor',
|
||||
W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => Struct.new(:to_param).new('anchor'))
|
||||
)
|
||||
end
|
||||
|
||||
def test_anchor_should_be_cgi_escaped
|
||||
assert_equal('/c/a#anc%2Fhor',
|
||||
W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => Struct.new(:to_param).new('anc/hor'))
|
||||
)
|
||||
end
|
||||
|
||||
def test_default_host
|
||||
add_host!
|
||||
assert_equal('http://www.basecamphq.com/c/a/i',
|
||||
W.new.url_for(:controller => 'c', :action => 'a', :id => 'i')
|
||||
)
|
||||
end
|
||||
|
||||
def test_host_may_be_overridden
|
||||
add_host!
|
||||
assert_equal('http://37signals.basecamphq.com/c/a/i',
|
||||
W.new.url_for(:host => '37signals.basecamphq.com', :controller => 'c', :action => 'a', :id => 'i')
|
||||
)
|
||||
end
|
||||
|
||||
def test_port
|
||||
add_host!
|
||||
assert_equal('http://www.basecamphq.com:3000/c/a/i',
|
||||
W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :port => 3000)
|
||||
)
|
||||
end
|
||||
|
||||
def test_protocol
|
||||
add_host!
|
||||
assert_equal('https://www.basecamphq.com/c/a/i',
|
||||
W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https')
|
||||
)
|
||||
end
|
||||
|
||||
def test_protocol_with_and_without_separator
|
||||
add_host!
|
||||
assert_equal('https://www.basecamphq.com/c/a/i',
|
||||
W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https')
|
||||
)
|
||||
assert_equal('https://www.basecamphq.com/c/a/i',
|
||||
W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https://')
|
||||
)
|
||||
end
|
||||
|
||||
def test_trailing_slash
|
||||
add_host!
|
||||
options = {:controller => 'foo', :trailing_slash => true, :action => 'bar', :id => '33'}
|
||||
assert_equal('http://www.basecamphq.com/foo/bar/33/', W.new.url_for(options) )
|
||||
end
|
||||
|
||||
def test_trailing_slash_with_protocol
|
||||
add_host!
|
||||
options = { :trailing_slash => true,:protocol => 'https', :controller => 'foo', :action => 'bar', :id => '33'}
|
||||
assert_equal('https://www.basecamphq.com/foo/bar/33/', W.new.url_for(options) )
|
||||
assert_equal 'https://www.basecamphq.com/foo/bar/33/?query=string', W.new.url_for(options.merge({:query => 'string'}))
|
||||
end
|
||||
|
||||
def test_trailing_slash_with_only_path
|
||||
options = {:controller => 'foo', :trailing_slash => true}
|
||||
assert_equal '/foo/', W.new.url_for(options.merge({:only_path => true}))
|
||||
options.update({:action => 'bar', :id => '33'})
|
||||
assert_equal '/foo/bar/33/', W.new.url_for(options.merge({:only_path => true}))
|
||||
assert_equal '/foo/bar/33/?query=string', W.new.url_for(options.merge({:query => 'string',:only_path => true}))
|
||||
end
|
||||
|
||||
def test_trailing_slash_with_anchor
|
||||
options = {:trailing_slash => true, :controller => 'foo', :action => 'bar', :id => '33', :only_path => true, :anchor=> 'chapter7'}
|
||||
assert_equal '/foo/bar/33/#chapter7', W.new.url_for(options)
|
||||
assert_equal '/foo/bar/33/?query=string#chapter7', W.new.url_for(options.merge({:query => 'string'}))
|
||||
end
|
||||
|
||||
def test_trailing_slash_with_params
|
||||
url = W.new.url_for(:trailing_slash => true, :only_path => true, :controller => 'cont', :action => 'act', :p1 => 'cafe', :p2 => 'link')
|
||||
params = extract_params(url)
|
||||
assert_equal params[0], { :p1 => 'cafe' }.to_query
|
||||
assert_equal params[1], { :p2 => 'link' }.to_query
|
||||
end
|
||||
|
||||
def test_relative_url_root_is_respected
|
||||
orig_relative_url_root = ActionController::Base.relative_url_root
|
||||
ActionController::Base.relative_url_root = '/subdir'
|
||||
|
||||
add_host!
|
||||
assert_equal('https://www.basecamphq.com/subdir/c/a/i',
|
||||
W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https')
|
||||
)
|
||||
ensure
|
||||
ActionController::Base.relative_url_root = orig_relative_url_root
|
||||
end
|
||||
|
||||
def test_named_routes
|
||||
with_routing do |set|
|
||||
set.draw do |map|
|
||||
match 'this/is/verbose', :to => 'home#index', :as => :no_args
|
||||
match 'home/sweet/home/:user', :to => 'home#index', :as => :home
|
||||
end
|
||||
|
||||
# We need to create a new class in order to install the new named route.
|
||||
kls = Class.new { include AbstractController::UrlFor }
|
||||
controller = kls.new
|
||||
assert controller.respond_to?(:home_url)
|
||||
assert_equal 'http://www.basecamphq.com/home/sweet/home/again',
|
||||
controller.send(:home_url, :host => 'www.basecamphq.com', :user => 'again')
|
||||
|
||||
assert_equal("/home/sweet/home/alabama", controller.send(:home_path, :user => 'alabama', :host => 'unused'))
|
||||
assert_equal("http://www.basecamphq.com/home/sweet/home/alabama", controller.send(:home_url, :user => 'alabama', :host => 'www.basecamphq.com'))
|
||||
assert_equal("http://www.basecamphq.com/this/is/verbose", controller.send(:no_args_url, :host=>'www.basecamphq.com'))
|
||||
end
|
||||
end
|
||||
|
||||
def test_relative_url_root_is_respected_for_named_routes
|
||||
orig_relative_url_root = ActionController::Base.relative_url_root
|
||||
ActionController::Base.relative_url_root = '/subdir'
|
||||
|
||||
with_routing do |set|
|
||||
set.draw do |map|
|
||||
match '/home/sweet/home/:user', :to => 'home#index', :as => :home
|
||||
end
|
||||
|
||||
kls = Class.new { include AbstractController::UrlFor }
|
||||
controller = kls.new
|
||||
|
||||
assert_equal 'http://www.basecamphq.com/subdir/home/sweet/home/again',
|
||||
controller.send(:home_url, :host => 'www.basecamphq.com', :user => 'again')
|
||||
end
|
||||
ensure
|
||||
ActionController::Base.relative_url_root = orig_relative_url_root
|
||||
end
|
||||
|
||||
def test_only_path
|
||||
with_routing do |set|
|
||||
set.draw do |map|
|
||||
match 'home/sweet/home/:user', :to => 'home#index', :as => :home
|
||||
match ':controller/:action/:id'
|
||||
end
|
||||
|
||||
# We need to create a new class in order to install the new named route.
|
||||
kls = Class.new { include AbstractController::UrlFor }
|
||||
controller = kls.new
|
||||
assert controller.respond_to?(:home_url)
|
||||
assert_equal '/brave/new/world',
|
||||
controller.send(:url_for, :controller => 'brave', :action => 'new', :id => 'world', :only_path => true)
|
||||
|
||||
assert_equal("/home/sweet/home/alabama", controller.send(:home_url, :user => 'alabama', :host => 'unused', :only_path => true))
|
||||
assert_equal("/home/sweet/home/alabama", controller.send(:home_path, 'alabama'))
|
||||
end
|
||||
end
|
||||
|
||||
def test_one_parameter
|
||||
assert_equal('/c/a?param=val',
|
||||
W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :param => 'val')
|
||||
)
|
||||
end
|
||||
|
||||
def test_two_parameters
|
||||
url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :p1 => 'X1', :p2 => 'Y2')
|
||||
params = extract_params(url)
|
||||
assert_equal params[0], { :p1 => 'X1' }.to_query
|
||||
assert_equal params[1], { :p2 => 'Y2' }.to_query
|
||||
end
|
||||
|
||||
def test_hash_parameter
|
||||
url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :query => {:name => 'Bob', :category => 'prof'})
|
||||
params = extract_params(url)
|
||||
assert_equal params[0], { 'query[category]' => 'prof' }.to_query
|
||||
assert_equal params[1], { 'query[name]' => 'Bob' }.to_query
|
||||
end
|
||||
|
||||
def test_array_parameter
|
||||
url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :query => ['Bob', 'prof'])
|
||||
params = extract_params(url)
|
||||
assert_equal params[0], { 'query[]' => 'Bob' }.to_query
|
||||
assert_equal params[1], { 'query[]' => 'prof' }.to_query
|
||||
end
|
||||
|
||||
def test_hash_recursive_parameters
|
||||
url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :query => {:person => {:name => 'Bob', :position => 'prof'}, :hobby => 'piercing'})
|
||||
params = extract_params(url)
|
||||
assert_equal params[0], { 'query[hobby]' => 'piercing' }.to_query
|
||||
assert_equal params[1], { 'query[person][name]' => 'Bob' }.to_query
|
||||
assert_equal params[2], { 'query[person][position]' => 'prof' }.to_query
|
||||
end
|
||||
|
||||
def test_hash_recursive_and_array_parameters
|
||||
url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :id => 101, :query => {:person => {:name => 'Bob', :position => ['prof', 'art director']}, :hobby => 'piercing'})
|
||||
assert_match %r(^/c/a/101), url
|
||||
params = extract_params(url)
|
||||
assert_equal params[0], { 'query[hobby]' => 'piercing' }.to_query
|
||||
assert_equal params[1], { 'query[person][name]' => 'Bob' }.to_query
|
||||
assert_equal params[2], { 'query[person][position][]' => 'art director' }.to_query
|
||||
assert_equal params[3], { 'query[person][position][]' => 'prof' }.to_query
|
||||
end
|
||||
|
||||
def test_path_generation_for_symbol_parameter_keys
|
||||
assert_generates("/image", :controller=> :image)
|
||||
end
|
||||
|
||||
def test_named_routes_with_nil_keys
|
||||
with_routing do |set|
|
||||
set.draw do |map|
|
||||
match 'posts.:format', :to => 'posts#index', :as => :posts
|
||||
match '/', :to => 'posts#index', :as => :main
|
||||
end
|
||||
|
||||
# We need to create a new class in order to install the new named route.
|
||||
kls = Class.new { include AbstractController::UrlFor }
|
||||
kls.default_url_options[:host] = 'www.basecamphq.com'
|
||||
|
||||
controller = kls.new
|
||||
params = {:action => :index, :controller => :posts, :format => :xml}
|
||||
assert_equal("http://www.basecamphq.com/posts.xml", controller.send(:url_for, params))
|
||||
params[:format] = nil
|
||||
assert_equal("http://www.basecamphq.com/", controller.send(:url_for, params))
|
||||
end
|
||||
end
|
||||
|
||||
def test_multiple_includes_maintain_distinct_options
|
||||
first_class = Class.new { include AbstractController::UrlFor }
|
||||
second_class = Class.new { include AbstractController::UrlFor }
|
||||
|
||||
first_host, second_host = 'firsthost.com', 'secondhost.com'
|
||||
|
||||
first_class.default_url_options[:host] = first_host
|
||||
second_class.default_url_options[:host] = second_host
|
||||
|
||||
assert_equal first_class.default_url_options[:host], first_host
|
||||
assert_equal second_class.default_url_options[:host], second_host
|
||||
end
|
||||
|
||||
private
|
||||
def extract_params(url)
|
||||
url.split('?', 2).last.split('&').sort
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -19,8 +19,6 @@ require 'action_view'
|
|||
require 'action_view/base'
|
||||
require 'action_dispatch'
|
||||
require 'fixture_template'
|
||||
require 'active_support/test_case'
|
||||
require 'action_view/test_case'
|
||||
require 'active_support/dependencies'
|
||||
|
||||
activemodel_path = File.expand_path('../../../activemodel/lib', __FILE__)
|
||||
|
@ -50,14 +48,6 @@ ORIGINAL_LOCALES = I18n.available_locales.map {|locale| locale.to_s }.sort
|
|||
FIXTURE_LOAD_PATH = File.join(File.dirname(__FILE__), 'fixtures')
|
||||
FIXTURES = Pathname.new(FIXTURE_LOAD_PATH)
|
||||
|
||||
# Turn on notifications
|
||||
require 'active_support/notifications'
|
||||
Thread.abort_on_exception = true
|
||||
|
||||
ActiveSupport::Notifications.subscribe do |*args|
|
||||
ActionController::Base.log_event(*args) if ActionController::Base.logger
|
||||
end
|
||||
|
||||
module SetupOnce
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
|
@ -95,29 +85,15 @@ class ActiveSupport::TestCase
|
|||
end
|
||||
end
|
||||
|
||||
class MockLogger
|
||||
attr_reader :logged
|
||||
attr_accessor :level
|
||||
|
||||
def initialize
|
||||
@level = Logger::DEBUG
|
||||
@logged = []
|
||||
end
|
||||
|
||||
def method_missing(method, *args, &blk)
|
||||
@logged << args.first
|
||||
@logged << blk.call if block_given?
|
||||
end
|
||||
end
|
||||
|
||||
class ActionController::IntegrationTest < ActiveSupport::TestCase
|
||||
def self.build_app(routes = nil)
|
||||
ActionDispatch::Flash
|
||||
ActionDispatch::MiddlewareStack.new { |middleware|
|
||||
middleware.use "ActionDispatch::StringCoercion"
|
||||
middleware.use "ActionDispatch::ShowExceptions"
|
||||
middleware.use "ActionDispatch::Callbacks"
|
||||
middleware.use "ActionDispatch::ParamsParser"
|
||||
middleware.use "Rack::Head"
|
||||
middleware.use "ActionDispatch::Flash"
|
||||
middleware.use "ActionDispatch::Head"
|
||||
}.build(routes || ActionController::Routing::Routes)
|
||||
end
|
||||
|
||||
|
|
|
@ -17,7 +17,6 @@ unless defined?(ActiveRecord) && defined?(Fixtures)
|
|||
raise LoadError, "#{PATH_TO_AR} doesn't exist" unless File.directory?(PATH_TO_AR)
|
||||
$LOAD_PATH.unshift PATH_TO_AR
|
||||
require 'active_record'
|
||||
require 'active_record/fixtures'
|
||||
rescue LoadError => e
|
||||
$stderr.print "Failed to load Active Record. Skipping Active Record assertion tests: #{e}"
|
||||
ActiveRecordTestConnector.able_to_connect = false
|
||||
|
|
|
@ -1,39 +1,53 @@
|
|||
require 'active_record_unit'
|
||||
require 'active_record/railties/controller_runtime'
|
||||
require 'fixtures/project'
|
||||
require 'rails/subscriber/test_helper'
|
||||
require 'action_controller/railties/subscriber'
|
||||
|
||||
ActionController::Base.send :include, ActiveRecord::Railties::ControllerRuntime
|
||||
|
||||
class ARLoggingController < ActionController::Base
|
||||
def show
|
||||
render :inline => "<%= Project.all %>"
|
||||
module ControllerRuntimeSubscriberTest
|
||||
class SubscriberController < ActionController::Base
|
||||
def show
|
||||
render :inline => "<%= Project.all %>"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class ARLoggingTest < ActionController::TestCase
|
||||
tests ARLoggingController
|
||||
def self.included(base)
|
||||
base.tests SubscriberController
|
||||
end
|
||||
|
||||
def setup
|
||||
@old_logger = ActionController::Base.logger
|
||||
Rails::Subscriber.add(:action_controller, ActionController::Railties::Subscriber.new)
|
||||
super
|
||||
set_logger
|
||||
end
|
||||
|
||||
def wait
|
||||
ActiveSupport::Notifications.notifier.wait
|
||||
def teardown
|
||||
super
|
||||
Rails::Subscriber.subscribers.clear
|
||||
ActionController::Base.logger = @old_logger
|
||||
end
|
||||
|
||||
def set_logger(logger)
|
||||
ActionController::Base.logger = logger
|
||||
end
|
||||
|
||||
def test_log_with_active_record
|
||||
get :show
|
||||
wait
|
||||
assert_match /ActiveRecord runtime/, logs[3]
|
||||
|
||||
assert_equal 2, @logger.logged(:info).size
|
||||
assert_match /\(Views: [\d\.]+ms | ActiveRecord: [\d\.]+ms\)/, @logger.logged(:info)[1]
|
||||
end
|
||||
|
||||
private
|
||||
def set_logger
|
||||
@controller.logger = MockLogger.new
|
||||
end
|
||||
class SyncSubscriberTest < ActionController::TestCase
|
||||
include Rails::Subscriber::SyncTestHelper
|
||||
include ControllerRuntimeSubscriberTest
|
||||
end
|
||||
|
||||
def logs
|
||||
@logs ||= @controller.logger.logged.compact.map {|l| l.to_s.strip}
|
||||
end
|
||||
end
|
||||
class AsyncSubscriberTest < ActionController::TestCase
|
||||
include Rails::Subscriber::AsyncTestHelper
|
||||
include ControllerRuntimeSubscriberTest
|
||||
end
|
||||
end
|
|
@ -26,7 +26,7 @@ class Series < ActiveRecord::Base
|
|||
end
|
||||
|
||||
class PolymorphicRoutesTest < ActionController::TestCase
|
||||
include ActionController::UrlWriter
|
||||
include ActionController::UrlFor
|
||||
self.default_url_options[:host] = 'example.com'
|
||||
|
||||
def setup
|
||||
|
|
|
@ -6,6 +6,7 @@ require 'pp' # require 'pp' early to prevent hidden_methods from not picking up
|
|||
module Submodule
|
||||
class ContainedEmptyController < ActionController::Base
|
||||
end
|
||||
|
||||
class ContainedNonEmptyController < ActionController::Base
|
||||
def public_action
|
||||
render :nothing => true
|
||||
|
@ -20,12 +21,15 @@ module Submodule
|
|||
end
|
||||
hide_action :another_hidden_action
|
||||
end
|
||||
|
||||
class SubclassedController < ContainedNonEmptyController
|
||||
hide_action :public_action # Hiding it here should not affect the superclass.
|
||||
end
|
||||
end
|
||||
|
||||
class EmptyController < ActionController::Base
|
||||
end
|
||||
|
||||
class NonEmptyController < ActionController::Base
|
||||
def public_action
|
||||
render :nothing => true
|
||||
|
@ -37,7 +41,6 @@ class NonEmptyController < ActionController::Base
|
|||
end
|
||||
|
||||
class MethodMissingController < ActionController::Base
|
||||
|
||||
hide_action :shouldnt_be_called
|
||||
def shouldnt_be_called
|
||||
raise "NO WAY!"
|
||||
|
@ -48,16 +51,15 @@ protected
|
|||
def method_missing(selector)
|
||||
render :text => selector.to_s
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class DefaultUrlOptionsController < ActionController::Base
|
||||
def default_url_options_action
|
||||
render :nothing => true
|
||||
def from_view
|
||||
render :inline => "<%= #{params[:route]} %>"
|
||||
end
|
||||
|
||||
def default_url_options(options = nil)
|
||||
{ :host => 'www.override.com', :action => 'new', :bacon => 'chunky' }
|
||||
{ :host => 'www.override.com', :action => 'new', :locale => 'en' }
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -68,6 +70,7 @@ class ControllerClassTests < Test::Unit::TestCase
|
|||
assert_equal 'submodule/contained_empty', Submodule::ContainedEmptyController.controller_path
|
||||
assert_equal Submodule::ContainedEmptyController.controller_path, Submodule::ContainedEmptyController.new.controller_path
|
||||
end
|
||||
|
||||
def test_controller_name
|
||||
assert_equal 'empty', EmptyController.controller_name
|
||||
assert_equal 'contained_empty', Submodule::ContainedEmptyController.controller_name
|
||||
|
@ -86,41 +89,16 @@ class ControllerInstanceTests < Test::Unit::TestCase
|
|||
|
||||
def test_action_methods
|
||||
@empty_controllers.each do |c|
|
||||
hide_mocha_methods_from_controller(c)
|
||||
assert_equal Set.new, c.class.__send__(:action_methods), "#{c.controller_path} should be empty!"
|
||||
end
|
||||
|
||||
@non_empty_controllers.each do |c|
|
||||
hide_mocha_methods_from_controller(c)
|
||||
assert_equal Set.new(%w(public_action)), c.class.__send__(:action_methods), "#{c.controller_path} should not be empty!"
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
# Mocha adds some public instance methods to Object that would be
|
||||
# considered actions, so explicitly hide_action them.
|
||||
def hide_mocha_methods_from_controller(controller)
|
||||
mocha_methods = [
|
||||
:expects, :mocha, :mocha_inspect, :reset_mocha, :stubba_object,
|
||||
:stubba_method, :stubs, :verify, :__metaclass__, :__is_a__, :to_matcher,
|
||||
]
|
||||
controller.class.__send__(:hide_action, *mocha_methods)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class PerformActionTest < ActionController::TestCase
|
||||
class MockLogger
|
||||
attr_reader :logged
|
||||
|
||||
def initialize
|
||||
@logged = []
|
||||
end
|
||||
|
||||
def method_missing(method, *args)
|
||||
@logged << args.first.to_s
|
||||
end
|
||||
end
|
||||
|
||||
def use_controller(controller_class)
|
||||
@controller = controller_class.new
|
||||
|
||||
|
@ -128,9 +106,8 @@ class PerformActionTest < ActionController::TestCase
|
|||
# a more accurate simulation of what happens in "real life".
|
||||
@controller.logger = Logger.new(nil)
|
||||
|
||||
@request = ActionController::TestRequest.new
|
||||
@response = ActionController::TestResponse.new
|
||||
|
||||
@request = ActionController::TestRequest.new
|
||||
@response = ActionController::TestResponse.new
|
||||
@request.host = "www.nextangle.com"
|
||||
|
||||
rescue_action_in_public!
|
||||
|
@ -145,8 +122,7 @@ class PerformActionTest < ActionController::TestCase
|
|||
|
||||
def test_method_missing_is_not_an_action_name
|
||||
use_controller MethodMissingController
|
||||
|
||||
assert ! @controller.__send__(:action_method?, 'method_missing')
|
||||
assert !@controller.__send__(:action_method?, 'method_missing')
|
||||
|
||||
get :method_missing
|
||||
assert_response :success
|
||||
|
@ -172,16 +148,43 @@ class DefaultUrlOptionsTest < ActionController::TestCase
|
|||
def test_default_url_options_are_used_if_set
|
||||
with_routing do |set|
|
||||
set.draw do |map|
|
||||
match 'default_url_options', :to => 'default_url_options#default_url_options_action', :as => :default_url_options
|
||||
match 'from_view', :to => 'default_url_options#from_view', :as => :from_view
|
||||
match ':controller/:action'
|
||||
end
|
||||
|
||||
get :default_url_options_action # Make a dummy request so that the controller is initialized properly.
|
||||
get :from_view, :route => "from_view_url"
|
||||
|
||||
assert_equal 'http://www.override.com/default_url_options/new?bacon=chunky', @controller.url_for(:controller => 'default_url_options')
|
||||
assert_equal 'http://www.override.com/default_url_options?bacon=chunky', @controller.send(:default_url_options_url)
|
||||
assert_equal 'http://www.override.com/from_view?locale=en', @response.body
|
||||
assert_equal 'http://www.override.com/from_view?locale=en', @controller.send(:from_view_url)
|
||||
assert_equal 'http://www.override.com/default_url_options/new?locale=en', @controller.url_for(:controller => 'default_url_options')
|
||||
end
|
||||
end
|
||||
|
||||
def test_default_url_options_are_used_in_non_positional_parameters
|
||||
with_routing do |set|
|
||||
set.draw do |map|
|
||||
scope("/:locale") do
|
||||
resources :descriptions
|
||||
end
|
||||
match ':controller/:action'
|
||||
end
|
||||
|
||||
get :from_view, :route => "description_path(1)"
|
||||
|
||||
assert_equal '/en/descriptions/1', @response.body
|
||||
assert_equal '/en/descriptions', @controller.send(:descriptions_path)
|
||||
assert_equal '/pl/descriptions', @controller.send(:descriptions_path, "pl")
|
||||
assert_equal '/pl/descriptions', @controller.send(:descriptions_path, :locale => "pl")
|
||||
assert_equal '/pl/descriptions.xml', @controller.send(:descriptions_path, "pl", "xml")
|
||||
assert_equal '/en/descriptions.xml', @controller.send(:descriptions_path, :format => "xml")
|
||||
assert_equal '/en/descriptions/1', @controller.send(:description_path, 1)
|
||||
assert_equal '/pl/descriptions/1', @controller.send(:description_path, "pl", 1)
|
||||
assert_equal '/pl/descriptions/1', @controller.send(:description_path, 1, :locale => "pl")
|
||||
assert_equal '/pl/descriptions/1.xml', @controller.send(:description_path, "pl", 1, "xml")
|
||||
assert_equal '/en/descriptions/1.xml', @controller.send(:description_path, 1, :format => "xml")
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class EmptyUrlOptionsTest < ActionController::TestCase
|
||||
|
@ -197,15 +200,12 @@ class EmptyUrlOptionsTest < ActionController::TestCase
|
|||
get :public_action
|
||||
assert_equal "http://www.example.com/non_empty/public_action", @controller.url_for
|
||||
end
|
||||
end
|
||||
|
||||
class EnsureNamedRoutesWorksTicket22BugTest < ActionController::TestCase
|
||||
def test_named_routes_still_work
|
||||
def test_named_routes_with_path_without_doing_a_request_first
|
||||
with_routing do |set|
|
||||
set.draw do |map|
|
||||
resources :things
|
||||
end
|
||||
EmptyController.send :include, ActionController::UrlWriter
|
||||
|
||||
assert_equal '/things', EmptyController.new.send(:things_path)
|
||||
end
|
||||
|
|
|
@ -629,20 +629,6 @@ class FragmentCachingTest < ActionController::TestCase
|
|||
assert_equal 'generated till now -> fragment content', buffer
|
||||
end
|
||||
|
||||
def test_fragment_for_logging
|
||||
fragment_computed = false
|
||||
events = []
|
||||
ActiveSupport::Notifications.subscribe { |*args| events << args }
|
||||
|
||||
buffer = 'generated till now -> '
|
||||
@controller.fragment_for(buffer, 'expensive') { fragment_computed = true }
|
||||
|
||||
assert fragment_computed
|
||||
assert_equal 'generated till now -> ', buffer
|
||||
ActiveSupport::Notifications.notifier.wait
|
||||
assert_equal [:exist_fragment?, :write_fragment], events.map(&:first)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class FunctionalCachingController < ActionController::Base
|
||||
|
|
|
@ -1,73 +1,59 @@
|
|||
require 'abstract_unit'
|
||||
|
||||
class DispatcherTest < Test::Unit::TestCase
|
||||
Dispatcher = ActionController::Dispatcher
|
||||
|
||||
class Foo
|
||||
cattr_accessor :a, :b
|
||||
end
|
||||
# Ensure deprecated dispatcher works
|
||||
class DeprecatedDispatcherTest < ActiveSupport::TestCase
|
||||
class DummyApp
|
||||
def call(env)
|
||||
[200, {}, 'response']
|
||||
end
|
||||
end
|
||||
|
||||
def setup
|
||||
ENV['REQUEST_METHOD'] = 'GET'
|
||||
|
||||
# Clear callbacks as they are redefined by Dispatcher#define_dispatcher_callbacks
|
||||
ActionDispatch::Callbacks.reset_callbacks(:prepare)
|
||||
ActionDispatch::Callbacks.reset_callbacks(:call)
|
||||
|
||||
ActionController::Routing::Routes.stubs(:call).returns([200, {}, 'response'])
|
||||
Dispatcher.stubs(:require_dependency)
|
||||
end
|
||||
|
||||
def teardown
|
||||
ENV.delete 'REQUEST_METHOD'
|
||||
end
|
||||
def test_assert_deprecated_to_prepare
|
||||
a = nil
|
||||
|
||||
def test_clears_dependencies_after_dispatch_if_in_loading_mode
|
||||
ActiveSupport::Dependencies.expects(:clear).once
|
||||
dispatch(false)
|
||||
end
|
||||
assert_deprecated do
|
||||
ActionController::Dispatcher.to_prepare { a = 1 }
|
||||
end
|
||||
|
||||
def test_prepare_callbacks
|
||||
a = b = c = nil
|
||||
ActionDispatch::Callbacks.to_prepare { |*args| a = b = c = 1 }
|
||||
ActionDispatch::Callbacks.to_prepare { |*args| b = c = 2 }
|
||||
ActionDispatch::Callbacks.to_prepare { |*args| c = 3 }
|
||||
|
||||
# Ensure to_prepare callbacks are not run when defined
|
||||
assert_nil a || b || c
|
||||
|
||||
# Run callbacks
|
||||
assert_nil a
|
||||
dispatch
|
||||
|
||||
assert_equal 1, a
|
||||
assert_equal 2, b
|
||||
assert_equal 3, c
|
||||
|
||||
# Make sure they are only run once
|
||||
a = b = c = nil
|
||||
dispatch
|
||||
assert_nil a || b || c
|
||||
end
|
||||
|
||||
def test_to_prepare_with_identifier_replaces
|
||||
ActionDispatch::Callbacks.to_prepare(:unique_id) { |*args| Foo.a, Foo.b = 1, 1 }
|
||||
ActionDispatch::Callbacks.to_prepare(:unique_id) { |*args| Foo.a = 2 }
|
||||
def test_assert_deprecated_before_dispatch
|
||||
a = nil
|
||||
|
||||
assert_deprecated do
|
||||
ActionController::Dispatcher.before_dispatch { a = 1 }
|
||||
end
|
||||
|
||||
assert_nil a
|
||||
dispatch
|
||||
assert_equal 2, Foo.a
|
||||
assert_equal nil, Foo.b
|
||||
assert_equal 1, a
|
||||
end
|
||||
|
||||
def test_assert_deprecated_after_dispatch
|
||||
a = nil
|
||||
|
||||
assert_deprecated do
|
||||
ActionController::Dispatcher.after_dispatch { a = 1 }
|
||||
end
|
||||
|
||||
assert_nil a
|
||||
dispatch
|
||||
assert_equal 1, a
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def dispatch(cache_classes = true)
|
||||
ActionController::Dispatcher.prepare_each_request = false
|
||||
Dispatcher.define_dispatcher_callbacks(cache_classes)
|
||||
|
||||
@dispatcher ||= ActionDispatch::Callbacks.new(ActionController::Routing::Routes)
|
||||
@dispatcher.call({'rack.input' => StringIO.new(''), 'action_dispatch.show_exceptions' => false})
|
||||
@dispatcher ||= ActionDispatch::Callbacks.new(DummyApp.new, !cache_classes)
|
||||
@dispatcher.call({'rack.input' => StringIO.new('')})
|
||||
end
|
||||
|
||||
def assert_subclasses(howmany, klass, message = klass.subclasses.inspect)
|
||||
assert_equal howmany, klass.subclasses.size, message
|
||||
end
|
||||
end
|
||||
|
|
|
@ -66,18 +66,6 @@ class FilterParamTest < ActionController::TestCase
|
|||
assert_raise(NoMethodError) { @controller.filter_parameters([{'password' => '[FILTERED]'}]) }
|
||||
end
|
||||
|
||||
def test_filter_parameters_inside_logs
|
||||
FilterParamController.filter_parameter_logging(:lifo, :amount)
|
||||
|
||||
get :payment, :lifo => 'Pratik', :amount => '420', :step => '1'
|
||||
ActiveSupport::Notifications.notifier.wait
|
||||
|
||||
filtered_params_logs = logs.detect {|l| l =~ /\AParameters/ }
|
||||
assert filtered_params_logs.index('"amount"=>"[FILTERED]"')
|
||||
assert filtered_params_logs.index('"lifo"=>"[FILTERED]"')
|
||||
assert filtered_params_logs.index('"step"=>"1"')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_logger
|
||||
|
|
|
@ -159,7 +159,7 @@ class FlashTest < ActionController::TestCase
|
|||
end
|
||||
|
||||
def test_keep_and_discard_return_values
|
||||
flash = ActionController::Flash::FlashHash.new
|
||||
flash = ActionDispatch::Flash::FlashHash.new
|
||||
flash.update(:foo => :foo_indeed, :bar => :bar_indeed)
|
||||
|
||||
assert_equal(:foo_indeed, flash.discard(:foo)) # valid key passed
|
||||
|
@ -187,4 +187,42 @@ class FlashTest < ActionController::TestCase
|
|||
get :redirect_with_other_flashes
|
||||
assert_equal "Horses!", @controller.send(:flash)[:joyride]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class FlashIntegrationTest < ActionController::IntegrationTest
|
||||
SessionKey = '_myapp_session'
|
||||
SessionSecret = 'b3c631c314c0bbca50c1b2843150fe33'
|
||||
|
||||
class TestController < ActionController::Base
|
||||
def set_flash
|
||||
flash["that"] = "hello"
|
||||
head :ok
|
||||
end
|
||||
|
||||
def use_flash
|
||||
render :inline => "flash: #{flash["that"]}"
|
||||
end
|
||||
end
|
||||
|
||||
def test_flash
|
||||
with_test_route_set do
|
||||
get '/set_flash'
|
||||
assert_response :success
|
||||
assert_equal "hello", @request.flash["that"]
|
||||
|
||||
get '/use_flash'
|
||||
assert_response :success
|
||||
assert_equal "flash: hello", @response.body
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def with_test_route_set
|
||||
with_routing do |set|
|
||||
set.draw do |map|
|
||||
match ':action', :to => ActionDispatch::Session::CookieStore.new(TestController, :key => SessionKey, :secret => SessionSecret)
|
||||
end
|
||||
yield
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -254,6 +254,10 @@ class IntegrationProcessTest < ActionController::IntegrationTest
|
|||
render :text => "Created", :status => 201
|
||||
end
|
||||
|
||||
def method
|
||||
render :text => "method: #{request.method}"
|
||||
end
|
||||
|
||||
def cookie_monster
|
||||
cookies["cookie_1"] = nil
|
||||
cookies["cookie_3"] = "chocolate"
|
||||
|
@ -379,6 +383,14 @@ class IntegrationProcessTest < ActionController::IntegrationTest
|
|||
head '/post'
|
||||
assert_equal 201, status
|
||||
assert_equal "", body
|
||||
|
||||
get '/get/method'
|
||||
assert_equal 200, status
|
||||
assert_equal "method: get", body
|
||||
|
||||
head '/get/method'
|
||||
assert_equal 200, status
|
||||
assert_equal "", body
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -391,6 +403,7 @@ class IntegrationProcessTest < ActionController::IntegrationTest
|
|||
with_routing do |set|
|
||||
set.draw do |map|
|
||||
match ':action', :to => ::IntegrationProcessTest::IntegrationController
|
||||
get 'get/:action', :to => ::IntegrationProcessTest::IntegrationController
|
||||
end
|
||||
yield
|
||||
end
|
||||
|
|
|
@ -1,80 +0,0 @@
|
|||
require 'abstract_unit'
|
||||
|
||||
module Another
|
||||
class LoggingController < ActionController::Base
|
||||
layout "layouts/standard"
|
||||
|
||||
def show
|
||||
render :nothing => true
|
||||
end
|
||||
|
||||
def with_layout
|
||||
render :template => "test/hello_world", :layout => true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class LoggingTest < ActionController::TestCase
|
||||
tests Another::LoggingController
|
||||
|
||||
def setup
|
||||
super
|
||||
set_logger
|
||||
end
|
||||
|
||||
def get(*args)
|
||||
super
|
||||
wait
|
||||
end
|
||||
|
||||
def wait
|
||||
ActiveSupport::Notifications.notifier.wait
|
||||
end
|
||||
|
||||
def test_logging_without_parameters
|
||||
get :show
|
||||
assert_equal 4, logs.size
|
||||
assert_nil logs.detect {|l| l =~ /Parameters/ }
|
||||
end
|
||||
|
||||
def test_logging_with_parameters
|
||||
get :show, :id => '10'
|
||||
assert_equal 5, logs.size
|
||||
|
||||
params = logs.detect {|l| l =~ /Parameters/ }
|
||||
assert_equal 'Parameters: {"id"=>"10"}', params
|
||||
end
|
||||
|
||||
def test_log_controller_with_namespace_and_action
|
||||
get :show
|
||||
assert_match /Processed\sAnother::LoggingController#show/, logs[1]
|
||||
end
|
||||
|
||||
def test_log_view_runtime
|
||||
get :show
|
||||
assert_match /View runtime/, logs[2]
|
||||
end
|
||||
|
||||
def test_log_completed_status_and_request_uri
|
||||
get :show
|
||||
last = logs.last
|
||||
assert_match /Completed/, last
|
||||
assert_match /200/, last
|
||||
assert_match /another\/logging\/show/, last
|
||||
end
|
||||
|
||||
def test_logger_prints_layout_and_template_rendering_info
|
||||
get :with_layout
|
||||
logged = logs.find {|l| l =~ /render/i }
|
||||
assert_match /Rendered (.*)test\/hello_world.erb within (.*)layouts\/standard.html.erb/, logged
|
||||
end
|
||||
|
||||
private
|
||||
def set_logger
|
||||
@controller.logger = MockLogger.new
|
||||
end
|
||||
|
||||
def logs
|
||||
@logs ||= @controller.logger.logged.compact.map {|l| l.to_s.strip}
|
||||
end
|
||||
end
|
|
@ -461,31 +461,27 @@ end
|
|||
|
||||
class RespondWithController < ActionController::Base
|
||||
respond_to :html, :json
|
||||
respond_to :xml, :except => :using_defaults
|
||||
respond_to :js, :only => [ :using_defaults, :using_resource ]
|
||||
respond_to :xml, :except => :using_resource_with_block
|
||||
respond_to :js, :only => [ :using_resource_with_block, :using_resource ]
|
||||
|
||||
def using_defaults
|
||||
respond_to do |format|
|
||||
def using_resource
|
||||
respond_with(resource)
|
||||
end
|
||||
|
||||
def using_resource_with_block
|
||||
respond_with(resource) do |format|
|
||||
format.csv { render :text => "CSV" }
|
||||
end
|
||||
end
|
||||
|
||||
def using_defaults_with_type_list
|
||||
respond_to(:js, :xml)
|
||||
end
|
||||
|
||||
def default_overwritten
|
||||
respond_to do |format|
|
||||
def using_resource_with_overwrite_block
|
||||
respond_with(resource) do |format|
|
||||
format.html { render :text => "HTML" }
|
||||
end
|
||||
end
|
||||
|
||||
def using_resource
|
||||
respond_with(Customer.new("david", 13))
|
||||
end
|
||||
|
||||
def using_resource_with_collection
|
||||
respond_with([Customer.new("david", 13), Customer.new("jamis", 9)])
|
||||
respond_with([resource, Customer.new("jamis", 9)])
|
||||
end
|
||||
|
||||
def using_resource_with_parent
|
||||
|
@ -493,16 +489,16 @@ class RespondWithController < ActionController::Base
|
|||
end
|
||||
|
||||
def using_resource_with_status_and_location
|
||||
respond_with(Customer.new("david", 13), :location => "http://test.host/", :status => :created)
|
||||
respond_with(resource, :location => "http://test.host/", :status => :created)
|
||||
end
|
||||
|
||||
def using_resource_with_responder
|
||||
responder = proc { |c, r, o| c.render :text => "Resource name is #{r.first.name}" }
|
||||
respond_with(Customer.new("david", 13), :responder => responder)
|
||||
respond_with(resource, :responder => responder)
|
||||
end
|
||||
|
||||
def using_resource_with_action
|
||||
respond_with(Customer.new("david", 13), :action => :foo) do |format|
|
||||
respond_with(resource, :action => :foo) do |format|
|
||||
format.html { raise ActionView::MissingTemplate.new([], "method") }
|
||||
end
|
||||
end
|
||||
|
@ -511,11 +507,15 @@ class RespondWithController < ActionController::Base
|
|||
responder = Class.new(ActionController::Responder) do
|
||||
def respond; @controller.render :text => "respond #{format}"; end
|
||||
end
|
||||
respond_with(Customer.new("david", 13), :responder => responder)
|
||||
respond_with(resource, :responder => responder)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def resource
|
||||
Customer.new("david", 13)
|
||||
end
|
||||
|
||||
def _render_js(js, options)
|
||||
self.content_type ||= Mime::JS
|
||||
self.response_body = js.respond_to?(:to_js) ? js.to_js : js
|
||||
|
@ -527,12 +527,18 @@ class InheritedRespondWithController < RespondWithController
|
|||
respond_to :xml, :json
|
||||
|
||||
def index
|
||||
respond_with(Customer.new("david", 13)) do |format|
|
||||
respond_with(resource) do |format|
|
||||
format.json { render :text => "JSON" }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class EmptyRespondWithController < ActionController::Base
|
||||
def index
|
||||
respond_with(Customer.new("david", 13))
|
||||
end
|
||||
end
|
||||
|
||||
class RespondWithControllerTest < ActionController::TestCase
|
||||
tests RespondWithController
|
||||
|
||||
|
@ -547,41 +553,6 @@ class RespondWithControllerTest < ActionController::TestCase
|
|||
ActionController::Base.use_accept_header = false
|
||||
end
|
||||
|
||||
def test_using_defaults
|
||||
@request.accept = "*/*"
|
||||
get :using_defaults
|
||||
assert_equal "text/html", @response.content_type
|
||||
assert_equal 'Hello world!', @response.body
|
||||
|
||||
@request.accept = "text/csv"
|
||||
get :using_defaults
|
||||
assert_equal "text/csv", @response.content_type
|
||||
assert_equal "CSV", @response.body
|
||||
|
||||
@request.accept = "text/javascript"
|
||||
get :using_defaults
|
||||
assert_equal "text/javascript", @response.content_type
|
||||
assert_equal '$("body").visualEffect("highlight");', @response.body
|
||||
end
|
||||
|
||||
def test_using_defaults_with_type_list
|
||||
@request.accept = "*/*"
|
||||
get :using_defaults_with_type_list
|
||||
assert_equal "text/javascript", @response.content_type
|
||||
assert_equal '$("body").visualEffect("highlight");', @response.body
|
||||
|
||||
@request.accept = "application/xml"
|
||||
get :using_defaults_with_type_list
|
||||
assert_equal "application/xml", @response.content_type
|
||||
assert_equal "<p>Hello world!</p>\n", @response.body
|
||||
end
|
||||
|
||||
def test_default_overwritten
|
||||
get :default_overwritten
|
||||
assert_equal "text/html", @response.content_type
|
||||
assert_equal "HTML", @response.body
|
||||
end
|
||||
|
||||
def test_using_resource
|
||||
@request.accept = "text/javascript"
|
||||
get :using_resource
|
||||
|
@ -599,6 +570,39 @@ class RespondWithControllerTest < ActionController::TestCase
|
|||
end
|
||||
end
|
||||
|
||||
def test_using_resource_with_block
|
||||
@request.accept = "*/*"
|
||||
get :using_resource_with_block
|
||||
assert_equal "text/html", @response.content_type
|
||||
assert_equal 'Hello world!', @response.body
|
||||
|
||||
@request.accept = "text/csv"
|
||||
get :using_resource_with_block
|
||||
assert_equal "text/csv", @response.content_type
|
||||
assert_equal "CSV", @response.body
|
||||
|
||||
@request.accept = "application/xml"
|
||||
get :using_resource
|
||||
assert_equal "application/xml", @response.content_type
|
||||
assert_equal "<name>david</name>", @response.body
|
||||
end
|
||||
|
||||
def test_using_resource_with_overwrite_block
|
||||
get :using_resource_with_overwrite_block
|
||||
assert_equal "text/html", @response.content_type
|
||||
assert_equal "HTML", @response.body
|
||||
end
|
||||
|
||||
def test_not_acceptable
|
||||
@request.accept = "application/xml"
|
||||
get :using_resource_with_block
|
||||
assert_equal 406, @response.status
|
||||
|
||||
@request.accept = "text/javascript"
|
||||
get :using_resource_with_overwrite_block
|
||||
assert_equal 406, @response.status
|
||||
end
|
||||
|
||||
def test_using_resource_for_post_with_html_redirects_on_success
|
||||
with_test_route_set do
|
||||
post :using_resource
|
||||
|
@ -831,22 +835,12 @@ class RespondWithControllerTest < ActionController::TestCase
|
|||
RespondWithController.responder = ActionController::Responder
|
||||
end
|
||||
|
||||
def test_not_acceptable
|
||||
@request.accept = "application/xml"
|
||||
get :using_defaults
|
||||
assert_equal 406, @response.status
|
||||
|
||||
@request.accept = "text/html"
|
||||
get :using_defaults_with_type_list
|
||||
assert_equal 406, @response.status
|
||||
|
||||
@request.accept = "application/json"
|
||||
get :using_defaults_with_type_list
|
||||
assert_equal 406, @response.status
|
||||
|
||||
@request.accept = "text/javascript"
|
||||
get :default_overwritten
|
||||
assert_equal 406, @response.status
|
||||
def test_error_is_raised_if_no_respond_to_is_declared_and_respond_with_is_called
|
||||
@controller = EmptyRespondWithController.new
|
||||
@request.accept = "*/*"
|
||||
assert_raise RuntimeError do
|
||||
get :index
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -30,16 +30,6 @@ module Backoffice
|
|||
end
|
||||
|
||||
class ResourcesTest < ActionController::TestCase
|
||||
# The assertions in these tests are incompatible with the hash method
|
||||
# optimisation. This could indicate user level problems
|
||||
def setup
|
||||
ActionController::Base.optimise_named_routes = false
|
||||
end
|
||||
|
||||
def teardown
|
||||
ActionController::Base.optimise_named_routes = true
|
||||
end
|
||||
|
||||
def test_should_arrange_actions
|
||||
resource = ActionDispatch::Routing::DeprecatedMapper::Resource.new(:messages,
|
||||
:collection => { :rss => :get, :reorder => :post, :csv => :post },
|
||||
|
@ -304,27 +294,27 @@ class ResourcesTest < ActionController::TestCase
|
|||
end
|
||||
end
|
||||
|
||||
def test_member_when_changed_default_restful_actions_and_path_names_not_specified
|
||||
default_path_names = ActionController::Base.resources_path_names
|
||||
ActionController::Base.resources_path_names = {:new => 'nuevo', :edit => 'editar'}
|
||||
|
||||
with_restful_routing :messages do
|
||||
new_options = { :action => 'new', :controller => 'messages' }
|
||||
new_path = "/messages/nuevo"
|
||||
edit_options = { :action => 'edit', :id => '1', :controller => 'messages' }
|
||||
edit_path = "/messages/1/editar"
|
||||
|
||||
assert_restful_routes_for :messages do |options|
|
||||
assert_recognizes(options.merge(new_options), :path => new_path, :method => :get)
|
||||
end
|
||||
|
||||
assert_restful_routes_for :messages do |options|
|
||||
assert_recognizes(options.merge(edit_options), :path => edit_path, :method => :get)
|
||||
end
|
||||
end
|
||||
ensure
|
||||
ActionController::Base.resources_path_names = default_path_names
|
||||
end
|
||||
# def test_member_when_changed_default_restful_actions_and_path_names_not_specified
|
||||
# default_path_names = ActionController::Base.resources_path_names
|
||||
# ActionController::Base.resources_path_names = {:new => 'nuevo', :edit => 'editar'}
|
||||
#
|
||||
# with_restful_routing :messages do
|
||||
# new_options = { :action => 'new', :controller => 'messages' }
|
||||
# new_path = "/messages/nuevo"
|
||||
# edit_options = { :action => 'edit', :id => '1', :controller => 'messages' }
|
||||
# edit_path = "/messages/1/editar"
|
||||
#
|
||||
# assert_restful_routes_for :messages do |options|
|
||||
# assert_recognizes(options.merge(new_options), :path => new_path, :method => :get)
|
||||
# end
|
||||
#
|
||||
# assert_restful_routes_for :messages do |options|
|
||||
# assert_recognizes(options.merge(edit_options), :path => edit_path, :method => :get)
|
||||
# end
|
||||
# end
|
||||
# ensure
|
||||
# ActionController::Base.resources_path_names = default_path_names
|
||||
# end
|
||||
|
||||
def test_with_two_member_actions_with_same_method
|
||||
[:put, :post].each do |method|
|
||||
|
|
|
@ -82,9 +82,6 @@ class LegacyRouteSetTests < Test::Unit::TestCase
|
|||
attr_reader :rs
|
||||
|
||||
def setup
|
||||
# These tests assume optimisation is on, so re-enable it.
|
||||
ActionController::Base.optimise_named_routes = true
|
||||
|
||||
@rs = ::ActionController::Routing::RouteSet.new
|
||||
end
|
||||
|
||||
|
@ -632,7 +629,6 @@ class LegacyRouteSetTests < Test::Unit::TestCase
|
|||
end
|
||||
|
||||
def test_routes_changed_correctly_after_clear
|
||||
ActionController::Base.optimise_named_routes = true
|
||||
rs = ::ActionController::Routing::RouteSet.new
|
||||
rs.draw do |r|
|
||||
r.connect 'ca', :controller => 'ca', :action => "aa"
|
||||
|
|
|
@ -0,0 +1,192 @@
|
|||
require "abstract_unit"
|
||||
require "rails/subscriber/test_helper"
|
||||
require "action_controller/railties/subscriber"
|
||||
|
||||
module Another
|
||||
class SubscribersController < ActionController::Base
|
||||
def show
|
||||
render :nothing => true
|
||||
end
|
||||
|
||||
def redirector
|
||||
redirect_to "http://foo.bar/"
|
||||
end
|
||||
|
||||
def data_sender
|
||||
send_data "cool data", :filename => "omg.txt"
|
||||
end
|
||||
|
||||
def xfile_sender
|
||||
send_file File.expand_path("company.rb", FIXTURE_LOAD_PATH), :x_sendfile => true
|
||||
end
|
||||
|
||||
def file_sender
|
||||
send_file File.expand_path("company.rb", FIXTURE_LOAD_PATH)
|
||||
end
|
||||
|
||||
def with_fragment_cache
|
||||
render :inline => "<%= cache('foo'){ 'bar' } %>"
|
||||
end
|
||||
|
||||
def with_page_cache
|
||||
cache_page("Super soaker", "/index.html")
|
||||
render :nothing => true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module ActionControllerSubscriberTest
|
||||
|
||||
def self.included(base)
|
||||
base.tests Another::SubscribersController
|
||||
end
|
||||
|
||||
def setup
|
||||
@old_logger = ActionController::Base.logger
|
||||
|
||||
@cache_path = File.expand_path('../temp/test_cache', File.dirname(__FILE__))
|
||||
ActionController::Base.page_cache_directory = @cache_path
|
||||
ActionController::Base.cache_store = :file_store, @cache_path
|
||||
|
||||
Rails::Subscriber.add(:action_controller, ActionController::Railties::Subscriber.new)
|
||||
super
|
||||
end
|
||||
|
||||
def teardown
|
||||
super
|
||||
Rails::Subscriber.subscribers.clear
|
||||
FileUtils.rm_rf(@cache_path)
|
||||
ActionController::Base.logger = @old_logger
|
||||
end
|
||||
|
||||
def set_logger(logger)
|
||||
ActionController::Base.logger = logger
|
||||
end
|
||||
|
||||
def test_process_action
|
||||
get :show
|
||||
wait
|
||||
assert_equal 2, logs.size
|
||||
assert_match /Processed\sAnother::SubscribersController#show/, logs[0]
|
||||
end
|
||||
|
||||
def test_process_action_formats
|
||||
get :show
|
||||
wait
|
||||
assert_equal 2, logs.size
|
||||
assert_match /text\/html/, logs[0]
|
||||
end
|
||||
|
||||
def test_process_action_without_parameters
|
||||
get :show
|
||||
wait
|
||||
assert_nil logs.detect {|l| l =~ /Parameters/ }
|
||||
end
|
||||
|
||||
def test_process_action_with_parameters
|
||||
get :show, :id => '10'
|
||||
wait
|
||||
|
||||
assert_equal 3, logs.size
|
||||
assert_equal 'Parameters: {"id"=>"10"}', logs[1]
|
||||
end
|
||||
|
||||
def test_process_action_with_view_runtime
|
||||
get :show
|
||||
wait
|
||||
assert_match /\(Views: [\d\.]+ms\)/, logs[1]
|
||||
end
|
||||
|
||||
def test_process_action_with_status_and_request_uri
|
||||
get :show
|
||||
wait
|
||||
last = logs.last
|
||||
assert_match /Completed/, last
|
||||
assert_match /200/, last
|
||||
assert_match /another\/subscribers\/show/, last
|
||||
end
|
||||
|
||||
def test_process_action_with_filter_parameters
|
||||
Another::SubscribersController.filter_parameter_logging(:lifo, :amount)
|
||||
|
||||
get :show, :lifo => 'Pratik', :amount => '420', :step => '1'
|
||||
wait
|
||||
|
||||
params = logs[1]
|
||||
assert_match /"amount"=>"\[FILTERED\]"/, params
|
||||
assert_match /"lifo"=>"\[FILTERED\]"/, params
|
||||
assert_match /"step"=>"1"/, params
|
||||
end
|
||||
|
||||
def test_redirect_to
|
||||
get :redirector
|
||||
wait
|
||||
|
||||
assert_equal 3, logs.size
|
||||
assert_equal "Redirected to http://foo.bar/", logs[0]
|
||||
end
|
||||
|
||||
def test_send_data
|
||||
get :data_sender
|
||||
wait
|
||||
|
||||
assert_equal 3, logs.size
|
||||
assert_match /Sent data omg\.txt/, logs[0]
|
||||
end
|
||||
|
||||
def test_send_file
|
||||
get :file_sender
|
||||
wait
|
||||
|
||||
assert_equal 3, logs.size
|
||||
assert_match /Sent file/, logs[0]
|
||||
assert_match /test\/fixtures\/company\.rb/, logs[0]
|
||||
end
|
||||
|
||||
def test_send_xfile
|
||||
get :xfile_sender
|
||||
wait
|
||||
|
||||
assert_equal 3, logs.size
|
||||
assert_match /Sent X\-Sendfile header/, logs[0]
|
||||
assert_match /test\/fixtures\/company\.rb/, logs[0]
|
||||
end
|
||||
|
||||
def test_with_fragment_cache
|
||||
ActionController::Base.perform_caching = true
|
||||
get :with_fragment_cache
|
||||
wait
|
||||
|
||||
assert_equal 4, logs.size
|
||||
assert_match /Exist fragment\? views\/foo/, logs[0]
|
||||
assert_match /Write fragment views\/foo/, logs[1]
|
||||
ensure
|
||||
ActionController::Base.perform_caching = true
|
||||
end
|
||||
|
||||
def test_with_page_cache
|
||||
ActionController::Base.perform_caching = true
|
||||
get :with_page_cache
|
||||
wait
|
||||
|
||||
assert_equal 3, logs.size
|
||||
assert_match /Write page/, logs[0]
|
||||
assert_match /\/index\.html/, logs[0]
|
||||
ensure
|
||||
ActionController::Base.perform_caching = true
|
||||
end
|
||||
|
||||
def logs
|
||||
@logs ||= @logger.logged(:info)
|
||||
end
|
||||
|
||||
class SyncSubscriberTest < ActionController::TestCase
|
||||
include Rails::Subscriber::SyncTestHelper
|
||||
include ActionControllerSubscriberTest
|
||||
end
|
||||
|
||||
class AsyncSubscriberTest < ActionController::TestCase
|
||||
include Rails::Subscriber::AsyncTestHelper
|
||||
include ActionControllerSubscriberTest
|
||||
end
|
||||
end
|
|
@ -100,286 +100,3 @@ class UrlRewriterTests < ActionController::TestCase
|
|||
end
|
||||
end
|
||||
|
||||
class UrlWriterTests < ActionController::TestCase
|
||||
class W
|
||||
include ActionController::UrlWriter
|
||||
end
|
||||
|
||||
def teardown
|
||||
W.default_url_options.clear
|
||||
end
|
||||
|
||||
def add_host!
|
||||
W.default_url_options[:host] = 'www.basecamphq.com'
|
||||
end
|
||||
|
||||
def test_exception_is_thrown_without_host
|
||||
assert_raise RuntimeError do
|
||||
W.new.url_for :controller => 'c', :action => 'a', :id => 'i'
|
||||
end
|
||||
end
|
||||
|
||||
def test_anchor
|
||||
assert_equal('/c/a#anchor',
|
||||
W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => 'anchor')
|
||||
)
|
||||
end
|
||||
|
||||
def test_anchor_should_call_to_param
|
||||
assert_equal('/c/a#anchor',
|
||||
W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => Struct.new(:to_param).new('anchor'))
|
||||
)
|
||||
end
|
||||
|
||||
def test_anchor_should_be_cgi_escaped
|
||||
assert_equal('/c/a#anc%2Fhor',
|
||||
W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => Struct.new(:to_param).new('anc/hor'))
|
||||
)
|
||||
end
|
||||
|
||||
def test_default_host
|
||||
add_host!
|
||||
assert_equal('http://www.basecamphq.com/c/a/i',
|
||||
W.new.url_for(:controller => 'c', :action => 'a', :id => 'i')
|
||||
)
|
||||
end
|
||||
|
||||
def test_host_may_be_overridden
|
||||
add_host!
|
||||
assert_equal('http://37signals.basecamphq.com/c/a/i',
|
||||
W.new.url_for(:host => '37signals.basecamphq.com', :controller => 'c', :action => 'a', :id => 'i')
|
||||
)
|
||||
end
|
||||
|
||||
def test_port
|
||||
add_host!
|
||||
assert_equal('http://www.basecamphq.com:3000/c/a/i',
|
||||
W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :port => 3000)
|
||||
)
|
||||
end
|
||||
|
||||
def test_protocol
|
||||
add_host!
|
||||
assert_equal('https://www.basecamphq.com/c/a/i',
|
||||
W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https')
|
||||
)
|
||||
end
|
||||
|
||||
def test_protocol_with_and_without_separator
|
||||
add_host!
|
||||
assert_equal('https://www.basecamphq.com/c/a/i',
|
||||
W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https')
|
||||
)
|
||||
assert_equal('https://www.basecamphq.com/c/a/i',
|
||||
W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https://')
|
||||
)
|
||||
end
|
||||
|
||||
def test_trailing_slash
|
||||
add_host!
|
||||
options = {:controller => 'foo', :trailing_slash => true, :action => 'bar', :id => '33'}
|
||||
assert_equal('http://www.basecamphq.com/foo/bar/33/', W.new.url_for(options) )
|
||||
end
|
||||
|
||||
def test_trailing_slash_with_protocol
|
||||
add_host!
|
||||
options = { :trailing_slash => true,:protocol => 'https', :controller => 'foo', :action => 'bar', :id => '33'}
|
||||
assert_equal('https://www.basecamphq.com/foo/bar/33/', W.new.url_for(options) )
|
||||
assert_equal 'https://www.basecamphq.com/foo/bar/33/?query=string', W.new.url_for(options.merge({:query => 'string'}))
|
||||
end
|
||||
|
||||
def test_trailing_slash_with_only_path
|
||||
options = {:controller => 'foo', :trailing_slash => true}
|
||||
assert_equal '/foo/', W.new.url_for(options.merge({:only_path => true}))
|
||||
options.update({:action => 'bar', :id => '33'})
|
||||
assert_equal '/foo/bar/33/', W.new.url_for(options.merge({:only_path => true}))
|
||||
assert_equal '/foo/bar/33/?query=string', W.new.url_for(options.merge({:query => 'string',:only_path => true}))
|
||||
end
|
||||
|
||||
def test_trailing_slash_with_anchor
|
||||
options = {:trailing_slash => true, :controller => 'foo', :action => 'bar', :id => '33', :only_path => true, :anchor=> 'chapter7'}
|
||||
assert_equal '/foo/bar/33/#chapter7', W.new.url_for(options)
|
||||
assert_equal '/foo/bar/33/?query=string#chapter7', W.new.url_for(options.merge({:query => 'string'}))
|
||||
end
|
||||
|
||||
def test_trailing_slash_with_params
|
||||
url = W.new.url_for(:trailing_slash => true, :only_path => true, :controller => 'cont', :action => 'act', :p1 => 'cafe', :p2 => 'link')
|
||||
params = extract_params(url)
|
||||
assert_equal params[0], { :p1 => 'cafe' }.to_query
|
||||
assert_equal params[1], { :p2 => 'link' }.to_query
|
||||
end
|
||||
|
||||
def test_relative_url_root_is_respected
|
||||
orig_relative_url_root = ActionController::Base.relative_url_root
|
||||
ActionController::Base.relative_url_root = '/subdir'
|
||||
|
||||
add_host!
|
||||
assert_equal('https://www.basecamphq.com/subdir/c/a/i',
|
||||
W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https')
|
||||
)
|
||||
ensure
|
||||
ActionController::Base.relative_url_root = orig_relative_url_root
|
||||
end
|
||||
|
||||
def test_named_routes
|
||||
with_routing do |set|
|
||||
set.draw do |map|
|
||||
match 'this/is/verbose', :to => 'home#index', :as => :no_args
|
||||
match 'home/sweet/home/:user', :to => 'home#index', :as => :home
|
||||
end
|
||||
|
||||
# We need to create a new class in order to install the new named route.
|
||||
kls = Class.new { include ActionController::UrlWriter }
|
||||
controller = kls.new
|
||||
assert controller.respond_to?(:home_url)
|
||||
assert_equal 'http://www.basecamphq.com/home/sweet/home/again',
|
||||
controller.send(:home_url, :host => 'www.basecamphq.com', :user => 'again')
|
||||
|
||||
assert_equal("/home/sweet/home/alabama", controller.send(:home_path, :user => 'alabama', :host => 'unused'))
|
||||
assert_equal("http://www.basecamphq.com/home/sweet/home/alabama", controller.send(:home_url, :user => 'alabama', :host => 'www.basecamphq.com'))
|
||||
assert_equal("http://www.basecamphq.com/this/is/verbose", controller.send(:no_args_url, :host=>'www.basecamphq.com'))
|
||||
end
|
||||
end
|
||||
|
||||
def test_relative_url_root_is_respected_for_named_routes
|
||||
orig_relative_url_root = ActionController::Base.relative_url_root
|
||||
ActionController::Base.relative_url_root = '/subdir'
|
||||
|
||||
with_routing do |set|
|
||||
set.draw do |map|
|
||||
match '/home/sweet/home/:user', :to => 'home#index', :as => :home
|
||||
end
|
||||
|
||||
kls = Class.new { include ActionController::UrlWriter }
|
||||
controller = kls.new
|
||||
|
||||
assert_equal 'http://www.basecamphq.com/subdir/home/sweet/home/again',
|
||||
controller.send(:home_url, :host => 'www.basecamphq.com', :user => 'again')
|
||||
end
|
||||
ensure
|
||||
ActionController::Base.relative_url_root = orig_relative_url_root
|
||||
end
|
||||
|
||||
def test_only_path
|
||||
with_routing do |set|
|
||||
set.draw do |map|
|
||||
match 'home/sweet/home/:user', :to => 'home#index', :as => :home
|
||||
match ':controller/:action/:id'
|
||||
end
|
||||
|
||||
# We need to create a new class in order to install the new named route.
|
||||
kls = Class.new { include ActionController::UrlWriter }
|
||||
controller = kls.new
|
||||
assert controller.respond_to?(:home_url)
|
||||
assert_equal '/brave/new/world',
|
||||
controller.send(:url_for, :controller => 'brave', :action => 'new', :id => 'world', :only_path => true)
|
||||
|
||||
assert_equal("/home/sweet/home/alabama", controller.send(:home_url, :user => 'alabama', :host => 'unused', :only_path => true))
|
||||
assert_equal("/home/sweet/home/alabama", controller.send(:home_path, 'alabama'))
|
||||
end
|
||||
end
|
||||
|
||||
def test_one_parameter
|
||||
assert_equal('/c/a?param=val',
|
||||
W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :param => 'val')
|
||||
)
|
||||
end
|
||||
|
||||
def test_two_parameters
|
||||
url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :p1 => 'X1', :p2 => 'Y2')
|
||||
params = extract_params(url)
|
||||
assert_equal params[0], { :p1 => 'X1' }.to_query
|
||||
assert_equal params[1], { :p2 => 'Y2' }.to_query
|
||||
end
|
||||
|
||||
def test_hash_parameter
|
||||
url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :query => {:name => 'Bob', :category => 'prof'})
|
||||
params = extract_params(url)
|
||||
assert_equal params[0], { 'query[category]' => 'prof' }.to_query
|
||||
assert_equal params[1], { 'query[name]' => 'Bob' }.to_query
|
||||
end
|
||||
|
||||
def test_array_parameter
|
||||
url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :query => ['Bob', 'prof'])
|
||||
params = extract_params(url)
|
||||
assert_equal params[0], { 'query[]' => 'Bob' }.to_query
|
||||
assert_equal params[1], { 'query[]' => 'prof' }.to_query
|
||||
end
|
||||
|
||||
def test_hash_recursive_parameters
|
||||
url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :query => {:person => {:name => 'Bob', :position => 'prof'}, :hobby => 'piercing'})
|
||||
params = extract_params(url)
|
||||
assert_equal params[0], { 'query[hobby]' => 'piercing' }.to_query
|
||||
assert_equal params[1], { 'query[person][name]' => 'Bob' }.to_query
|
||||
assert_equal params[2], { 'query[person][position]' => 'prof' }.to_query
|
||||
end
|
||||
|
||||
def test_hash_recursive_and_array_parameters
|
||||
url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :id => 101, :query => {:person => {:name => 'Bob', :position => ['prof', 'art director']}, :hobby => 'piercing'})
|
||||
assert_match %r(^/c/a/101), url
|
||||
params = extract_params(url)
|
||||
assert_equal params[0], { 'query[hobby]' => 'piercing' }.to_query
|
||||
assert_equal params[1], { 'query[person][name]' => 'Bob' }.to_query
|
||||
assert_equal params[2], { 'query[person][position][]' => 'art director' }.to_query
|
||||
assert_equal params[3], { 'query[person][position][]' => 'prof' }.to_query
|
||||
end
|
||||
|
||||
def test_path_generation_for_symbol_parameter_keys
|
||||
assert_generates("/image", :controller=> :image)
|
||||
end
|
||||
|
||||
def test_named_routes_with_nil_keys
|
||||
with_routing do |set|
|
||||
set.draw do |map|
|
||||
match 'posts.:format', :to => 'posts#index', :as => :posts
|
||||
match '/', :to => 'posts#index', :as => :main
|
||||
end
|
||||
|
||||
# We need to create a new class in order to install the new named route.
|
||||
kls = Class.new { include ActionController::UrlWriter }
|
||||
kls.default_url_options[:host] = 'www.basecamphq.com'
|
||||
|
||||
controller = kls.new
|
||||
params = {:action => :index, :controller => :posts, :format => :xml}
|
||||
assert_equal("http://www.basecamphq.com/posts.xml", controller.send(:url_for, params))
|
||||
params[:format] = nil
|
||||
assert_equal("http://www.basecamphq.com/", controller.send(:url_for, params))
|
||||
end
|
||||
end
|
||||
|
||||
def test_formatted_url_methods_are_deprecated
|
||||
with_routing do |set|
|
||||
set.draw do |map|
|
||||
resources :posts
|
||||
end
|
||||
# We need to create a new class in order to install the new named route.
|
||||
kls = Class.new { include ActionController::UrlWriter }
|
||||
controller = kls.new
|
||||
params = {:id => 1, :format => :xml}
|
||||
assert_deprecated do
|
||||
assert_equal("/posts/1.xml", controller.send(:formatted_post_path, params))
|
||||
end
|
||||
assert_deprecated do
|
||||
assert_equal("/posts/1.xml", controller.send(:formatted_post_path, 1, :xml))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_multiple_includes_maintain_distinct_options
|
||||
first_class = Class.new { include ActionController::UrlWriter }
|
||||
second_class = Class.new { include ActionController::UrlWriter }
|
||||
|
||||
first_host, second_host = 'firsthost.com', 'secondhost.com'
|
||||
|
||||
first_class.default_url_options[:host] = first_host
|
||||
second_class.default_url_options[:host] = second_host
|
||||
|
||||
assert_equal first_class.default_url_options[:host], first_host
|
||||
assert_equal second_class.default_url_options[:host], second_host
|
||||
end
|
||||
|
||||
private
|
||||
def extract_params(url)
|
||||
url.split('?', 2).last.split('&').sort
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
require 'abstract_unit'
|
||||
|
||||
class DispatcherTest < Test::Unit::TestCase
|
||||
class Foo
|
||||
cattr_accessor :a, :b
|
||||
end
|
||||
|
||||
class DummyApp
|
||||
def call(env)
|
||||
[200, {}, 'response']
|
||||
end
|
||||
end
|
||||
|
||||
def setup
|
||||
Foo.a, Foo.b = 0, 0
|
||||
ActionDispatch::Callbacks.reset_callbacks(:prepare)
|
||||
ActionDispatch::Callbacks.reset_callbacks(:call)
|
||||
end
|
||||
|
||||
def test_prepare_callbacks_with_cache_classes
|
||||
a = b = c = nil
|
||||
ActionDispatch::Callbacks.to_prepare { |*args| a = b = c = 1 }
|
||||
ActionDispatch::Callbacks.to_prepare { |*args| b = c = 2 }
|
||||
ActionDispatch::Callbacks.to_prepare { |*args| c = 3 }
|
||||
|
||||
# Ensure to_prepare callbacks are not run when defined
|
||||
assert_nil a || b || c
|
||||
|
||||
# Run callbacks
|
||||
dispatch
|
||||
|
||||
assert_equal 1, a
|
||||
assert_equal 2, b
|
||||
assert_equal 3, c
|
||||
|
||||
# Make sure they are only run once
|
||||
a = b = c = nil
|
||||
dispatch
|
||||
assert_nil a || b || c
|
||||
end
|
||||
|
||||
def test_prepare_callbacks_without_cache_classes
|
||||
a = b = c = nil
|
||||
ActionDispatch::Callbacks.to_prepare { |*args| a = b = c = 1 }
|
||||
ActionDispatch::Callbacks.to_prepare { |*args| b = c = 2 }
|
||||
ActionDispatch::Callbacks.to_prepare { |*args| c = 3 }
|
||||
|
||||
# Ensure to_prepare callbacks are not run when defined
|
||||
assert_nil a || b || c
|
||||
|
||||
# Run callbacks
|
||||
dispatch(false)
|
||||
|
||||
assert_equal 1, a
|
||||
assert_equal 2, b
|
||||
assert_equal 3, c
|
||||
|
||||
# Make sure they are run again
|
||||
a = b = c = nil
|
||||
dispatch(false)
|
||||
assert_equal 1, a
|
||||
assert_equal 2, b
|
||||
assert_equal 3, c
|
||||
end
|
||||
|
||||
def test_to_prepare_with_identifier_replaces
|
||||
ActionDispatch::Callbacks.to_prepare(:unique_id) { |*args| Foo.a, Foo.b = 1, 1 }
|
||||
ActionDispatch::Callbacks.to_prepare(:unique_id) { |*args| Foo.a = 2 }
|
||||
|
||||
dispatch
|
||||
assert_equal 2, Foo.a
|
||||
assert_equal 0, Foo.b
|
||||
end
|
||||
|
||||
def test_before_and_after_callbacks
|
||||
ActionDispatch::Callbacks.before { |*args| Foo.a += 1; Foo.b += 1 }
|
||||
ActionDispatch::Callbacks.after { |*args| Foo.a += 1; Foo.b += 1 }
|
||||
|
||||
dispatch
|
||||
assert_equal 2, Foo.a
|
||||
assert_equal 2, Foo.b
|
||||
|
||||
dispatch
|
||||
assert_equal 4, Foo.a
|
||||
assert_equal 4, Foo.b
|
||||
end
|
||||
|
||||
def test_should_send_an_instrumentation_callback_for_async_processing
|
||||
ActiveSupport::Notifications.expects(:instrument).with("action_dispatch.callback")
|
||||
dispatch
|
||||
end
|
||||
|
||||
def test_should_send_an_instrumentation_callback_for_async_processing_even_on_failure
|
||||
ActiveSupport::Notifications.notifier.expects(:publish)
|
||||
assert_raise RuntimeError do
|
||||
dispatch { |env| raise "OMG" }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def dispatch(cache_classes = true, &block)
|
||||
@dispatcher ||= ActionDispatch::Callbacks.new(block || DummyApp.new, !cache_classes)
|
||||
@dispatcher.call({'rack.input' => StringIO.new('')})
|
||||
end
|
||||
|
||||
end
|
|
@ -319,7 +319,7 @@ class RequestTest < ActiveSupport::TestCase
|
|||
end
|
||||
|
||||
test "allow method hacking on post" do
|
||||
[:get, :head, :options, :put, :post, :delete].each do |method|
|
||||
[:get, :options, :put, :post, :delete].each do |method|
|
||||
request = stub_request "REQUEST_METHOD" => method.to_s.upcase
|
||||
assert_equal(method == :head ? :get : method, request.method)
|
||||
end
|
||||
|
@ -341,7 +341,7 @@ class RequestTest < ActiveSupport::TestCase
|
|||
end
|
||||
|
||||
test "head masquerading as get" do
|
||||
request = stub_request 'REQUEST_METHOD' => 'HEAD'
|
||||
request = stub_request 'REQUEST_METHOD' => 'GET', "rack.methodoverride.original_method" => "HEAD"
|
||||
assert_equal :get, request.method
|
||||
assert request.get?
|
||||
assert request.head?
|
||||
|
|
|
@ -22,6 +22,10 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
|
|||
delete 'logout' => :destroy, :as => :logout
|
||||
end
|
||||
|
||||
resource :session do
|
||||
get :create
|
||||
end
|
||||
|
||||
match 'account/logout' => redirect("/logout"), :as => :logout_redirect
|
||||
match 'account/login', :to => redirect("/login")
|
||||
|
||||
|
@ -29,6 +33,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
|
|||
|
||||
match 'account/modulo/:name', :to => redirect("/%{name}s")
|
||||
match 'account/proc/:name', :to => redirect {|params| "/#{params[:name].pluralize}" }
|
||||
match 'account/google' => redirect('http://www.google.com/')
|
||||
|
||||
match 'openid/login', :via => [:get, :post], :to => "openid#login"
|
||||
|
||||
|
@ -47,6 +52,10 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
|
|||
get 'admin/accounts' => "queenbee#accounts"
|
||||
end
|
||||
|
||||
scope 'es' do
|
||||
resources :projects, :path_names => { :edit => 'cambiar' }, :as => 'projeto'
|
||||
end
|
||||
|
||||
resources :projects, :controller => :project do
|
||||
resources :involvements, :attachments
|
||||
|
||||
|
@ -56,7 +65,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
|
|||
|
||||
resources :companies do
|
||||
resources :people
|
||||
resource :avatar
|
||||
resource :avatar, :controller => :avatar
|
||||
end
|
||||
|
||||
resources :images do
|
||||
|
@ -65,7 +74,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
|
|||
|
||||
resources :people do
|
||||
nested do
|
||||
namespace ":access_token" do
|
||||
scope "/:access_token" do
|
||||
resource :avatar
|
||||
end
|
||||
end
|
||||
|
@ -88,6 +97,15 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
|
|||
end
|
||||
end
|
||||
|
||||
resources :replies do
|
||||
member do
|
||||
put :answer, :to => :mark_as_answer
|
||||
delete :answer, :to => :unmark_as_answer
|
||||
end
|
||||
end
|
||||
|
||||
resources :posts, :only => [:index, :show]
|
||||
|
||||
match 'sprockets.js' => ::TestRoutingMapper::SprocketsApp
|
||||
|
||||
match 'people/:id/update', :to => 'people#update', :as => :update_person
|
||||
|
@ -97,7 +115,18 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
|
|||
match 'articles/:year/:month/:day/:title', :to => "articles#show", :as => :article
|
||||
|
||||
namespace :account do
|
||||
match 'description', :to => "account#description", :as => "description"
|
||||
resource :subscription, :credit, :credit_card
|
||||
|
||||
namespace :admin do
|
||||
resource :subscription
|
||||
end
|
||||
end
|
||||
|
||||
namespace :forum do
|
||||
resources :products, :as => '' do
|
||||
resources :questions
|
||||
end
|
||||
end
|
||||
|
||||
controller :articles do
|
||||
|
@ -112,6 +141,16 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
|
|||
resources :rooms
|
||||
end
|
||||
|
||||
scope '(:locale)', :locale => /en|pl/ do
|
||||
resources :descriptions
|
||||
end
|
||||
|
||||
namespace :admin do
|
||||
scope '(/:locale)', :locale => /en|pl/ do
|
||||
resources :descriptions
|
||||
end
|
||||
end
|
||||
|
||||
match '/info' => 'projects#info', :as => 'info'
|
||||
|
||||
root :to => 'projects#index'
|
||||
|
@ -165,6 +204,31 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
|
|||
end
|
||||
end
|
||||
|
||||
def test_session_singleton_resource
|
||||
with_test_routes do
|
||||
get '/session'
|
||||
assert_equal 'sessions#create', @response.body
|
||||
assert_equal '/session', session_path
|
||||
|
||||
post '/session'
|
||||
assert_equal 'sessions#create', @response.body
|
||||
|
||||
put '/session'
|
||||
assert_equal 'sessions#update', @response.body
|
||||
|
||||
delete '/session'
|
||||
assert_equal 'sessions#destroy', @response.body
|
||||
|
||||
get '/session/new'
|
||||
assert_equal 'sessions#new', @response.body
|
||||
assert_equal '/session/new', new_session_path
|
||||
|
||||
get '/session/edit'
|
||||
assert_equal 'sessions#edit', @response.body
|
||||
assert_equal '/session/edit', edit_session_path
|
||||
end
|
||||
end
|
||||
|
||||
def test_redirect_modulo
|
||||
with_test_routes do
|
||||
get '/account/modulo/name'
|
||||
|
@ -193,20 +257,19 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
|
|||
end
|
||||
end
|
||||
|
||||
# TODO: rackmount is broken
|
||||
# def test_admin
|
||||
# with_test_routes do
|
||||
# get '/admin', {}, {'REMOTE_ADDR' => '192.168.1.100'}
|
||||
# assert_equal 'queenbee#index', @response.body
|
||||
#
|
||||
# assert_raise(ActionController::RoutingError) { get '/admin', {}, {'REMOTE_ADDR' => '10.0.0.100'} }
|
||||
#
|
||||
# get '/admin/accounts', {}, {'REMOTE_ADDR' => '192.168.1.100'}
|
||||
# assert_equal 'queenbee#accounts', @response.body
|
||||
#
|
||||
# assert_raise(ActionController::RoutingError) { get '/admin/accounts', {}, {'REMOTE_ADDR' => '10.0.0.100'} }
|
||||
# end
|
||||
# end
|
||||
def test_admin
|
||||
with_test_routes do
|
||||
get '/admin', {}, {'REMOTE_ADDR' => '192.168.1.100'}
|
||||
assert_equal 'queenbee#index', @response.body
|
||||
|
||||
assert_raise(ActionController::RoutingError) { get '/admin', {}, {'REMOTE_ADDR' => '10.0.0.100'} }
|
||||
|
||||
get '/admin/accounts', {}, {'REMOTE_ADDR' => '192.168.1.100'}
|
||||
assert_equal 'queenbee#accounts', @response.body
|
||||
|
||||
assert_raise(ActionController::RoutingError) { get '/admin/accounts', {}, {'REMOTE_ADDR' => '10.0.0.100'} }
|
||||
end
|
||||
end
|
||||
|
||||
def test_global
|
||||
with_test_routes do
|
||||
|
@ -231,31 +294,34 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
|
|||
def test_projects
|
||||
with_test_routes do
|
||||
get '/projects'
|
||||
assert_equal 'projects#index', @response.body
|
||||
assert_equal 'project#index', @response.body
|
||||
assert_equal '/projects', projects_path
|
||||
|
||||
post '/projects'
|
||||
assert_equal 'project#create', @response.body
|
||||
|
||||
get '/projects.xml'
|
||||
assert_equal 'projects#index', @response.body
|
||||
assert_equal 'project#index', @response.body
|
||||
assert_equal '/projects.xml', projects_path(:format => 'xml')
|
||||
|
||||
get '/projects/new'
|
||||
assert_equal 'projects#new', @response.body
|
||||
assert_equal 'project#new', @response.body
|
||||
assert_equal '/projects/new', new_project_path
|
||||
|
||||
get '/projects/new.xml'
|
||||
assert_equal 'projects#new', @response.body
|
||||
assert_equal 'project#new', @response.body
|
||||
assert_equal '/projects/new.xml', new_project_path(:format => 'xml')
|
||||
|
||||
get '/projects/1'
|
||||
assert_equal 'projects#show', @response.body
|
||||
assert_equal 'project#show', @response.body
|
||||
assert_equal '/projects/1', project_path(:id => '1')
|
||||
|
||||
get '/projects/1.xml'
|
||||
assert_equal 'projects#show', @response.body
|
||||
assert_equal 'project#show', @response.body
|
||||
assert_equal '/projects/1.xml', project_path(:id => '1', :format => 'xml')
|
||||
|
||||
get '/projects/1/edit'
|
||||
assert_equal 'projects#edit', @response.body
|
||||
assert_equal 'project#edit', @response.body
|
||||
assert_equal '/projects/1/edit', edit_project_path(:id => '1')
|
||||
end
|
||||
end
|
||||
|
@ -317,7 +383,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
|
|||
assert_equal '/projects/1/companies/1/people', project_company_people_path(:project_id => '1', :company_id => '1')
|
||||
|
||||
get '/projects/1/companies/1/avatar'
|
||||
assert_equal 'avatars#show', @response.body
|
||||
assert_equal 'avatar#show', @response.body
|
||||
assert_equal '/projects/1/companies/1/avatar', project_company_avatar_path(:project_id => '1', :company_id => '1')
|
||||
end
|
||||
end
|
||||
|
@ -394,6 +460,42 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
|
|||
end
|
||||
end
|
||||
|
||||
def test_replies
|
||||
with_test_routes do
|
||||
put '/replies/1/answer'
|
||||
assert_equal 'replies#mark_as_answer', @response.body
|
||||
|
||||
delete '/replies/1/answer'
|
||||
assert_equal 'replies#unmark_as_answer', @response.body
|
||||
end
|
||||
end
|
||||
|
||||
def test_posts
|
||||
with_test_routes do
|
||||
get '/posts'
|
||||
assert_equal 'posts#index', @response.body
|
||||
assert_equal '/posts', posts_path
|
||||
|
||||
get '/posts/1'
|
||||
assert_equal 'posts#show', @response.body
|
||||
assert_equal '/posts/1', post_path(:id => 1)
|
||||
|
||||
assert_raise(ActionController::RoutingError) { post '/posts' }
|
||||
assert_raise(ActionController::RoutingError) { put '/posts/1' }
|
||||
assert_raise(ActionController::RoutingError) { delete '/posts/1' }
|
||||
end
|
||||
end
|
||||
|
||||
def test_path_names
|
||||
with_test_routes do
|
||||
get '/es/projeto'
|
||||
assert_equal 'projects#index', @response.body
|
||||
|
||||
get '/es/projeto/1/cambiar'
|
||||
assert_equal 'projects#edit', @response.body
|
||||
end
|
||||
end
|
||||
|
||||
def test_sprockets
|
||||
with_test_routes do
|
||||
get '/sprockets.js'
|
||||
|
@ -419,6 +521,26 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
|
|||
end
|
||||
end
|
||||
|
||||
def test_forum_products
|
||||
with_test_routes do
|
||||
get '/forum'
|
||||
assert_equal 'forum/products#index', @response.body
|
||||
assert_equal '/forum', forum_products_path
|
||||
|
||||
get '/forum/basecamp'
|
||||
assert_equal 'forum/products#show', @response.body
|
||||
assert_equal '/forum/basecamp', forum_product_path(:id => 'basecamp')
|
||||
|
||||
get '/forum/basecamp/questions'
|
||||
assert_equal 'forum/questions#index', @response.body
|
||||
assert_equal '/forum/basecamp/questions', forum_product_questions_path(:product_id => 'basecamp')
|
||||
|
||||
get '/forum/basecamp/questions/1'
|
||||
assert_equal 'forum/questions#show', @response.body
|
||||
assert_equal '/forum/basecamp/questions/1', forum_product_question_path(:product_id => 'basecamp', :id => 1)
|
||||
end
|
||||
end
|
||||
|
||||
def test_articles_perma
|
||||
with_test_routes do
|
||||
get '/articles/2009/08/18/rails-3'
|
||||
|
@ -431,13 +553,24 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
|
|||
def test_account_namespace
|
||||
with_test_routes do
|
||||
get '/account/subscription'
|
||||
assert_equal 'subscriptions#show', @response.body
|
||||
assert_equal 'account/subscriptions#show', @response.body
|
||||
assert_equal '/account/subscription', account_subscription_path
|
||||
|
||||
get '/account/credit'
|
||||
assert_equal 'credits#show', @response.body
|
||||
assert_equal 'account/credits#show', @response.body
|
||||
assert_equal '/account/credit', account_credit_path
|
||||
|
||||
get '/account/credit_card'
|
||||
assert_equal 'credit_cards#show', @response.body
|
||||
assert_equal 'account/credit_cards#show', @response.body
|
||||
assert_equal '/account/credit_card', account_credit_card_path
|
||||
end
|
||||
end
|
||||
|
||||
def test_nested_namespace
|
||||
with_test_routes do
|
||||
get '/account/admin/subscription'
|
||||
assert_equal 'account/admin/subscriptions#show', @response.body
|
||||
assert_equal '/account/admin/subscription', account_admin_subscription_path
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -472,7 +605,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
|
|||
assert_equal 'projects#index', @response.body
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def test_index
|
||||
with_test_routes do
|
||||
assert_equal '/info', info_path
|
||||
|
@ -488,7 +621,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
|
|||
assert_equal 'projects#info', @response.body
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def test_convention_match_with_no_scope
|
||||
with_test_routes do
|
||||
assert_equal '/account/overview', account_overview_path
|
||||
|
@ -497,6 +630,78 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
|
|||
end
|
||||
end
|
||||
|
||||
def test_redirect_with_complete_url
|
||||
with_test_routes do
|
||||
get '/account/google'
|
||||
assert_equal 301, @response.status
|
||||
assert_equal 'http://www.google.com/', @response.headers['Location']
|
||||
assert_equal 'Moved Permanently', @response.body
|
||||
end
|
||||
end
|
||||
|
||||
def test_redirect_with_port
|
||||
previous_host, self.host = self.host, 'www.example.com:3000'
|
||||
with_test_routes do
|
||||
get '/account/login'
|
||||
assert_equal 301, @response.status
|
||||
assert_equal 'http://www.example.com:3000/login', @response.headers['Location']
|
||||
assert_equal 'Moved Permanently', @response.body
|
||||
end
|
||||
ensure
|
||||
self.host = previous_host
|
||||
end
|
||||
|
||||
def test_normalize_namespaced_matches
|
||||
with_test_routes do
|
||||
assert_equal '/account/description', account_description_path
|
||||
|
||||
get '/account/description'
|
||||
assert_equal 'account#description', @response.body
|
||||
end
|
||||
end
|
||||
|
||||
def test_optional_scoped_path
|
||||
with_test_routes do
|
||||
assert_equal '/en/descriptions', descriptions_path("en")
|
||||
assert_equal '/descriptions', descriptions_path(nil)
|
||||
assert_equal '/en/descriptions/1', description_path("en", 1)
|
||||
assert_equal '/descriptions/1', description_path(nil, 1)
|
||||
|
||||
get '/en/descriptions'
|
||||
assert_equal 'descriptions#index', @response.body
|
||||
|
||||
get '/descriptions'
|
||||
assert_equal 'descriptions#index', @response.body
|
||||
|
||||
get '/en/descriptions/1'
|
||||
assert_equal 'descriptions#show', @response.body
|
||||
|
||||
get '/descriptions/1'
|
||||
assert_equal 'descriptions#show', @response.body
|
||||
end
|
||||
end
|
||||
|
||||
def test_nested_optional_scoped_path
|
||||
with_test_routes do
|
||||
assert_equal '/admin/en/descriptions', admin_descriptions_path("en")
|
||||
assert_equal '/admin/descriptions', admin_descriptions_path(nil)
|
||||
assert_equal '/admin/en/descriptions/1', admin_description_path("en", 1)
|
||||
assert_equal '/admin/descriptions/1', admin_description_path(nil, 1)
|
||||
|
||||
get '/admin/en/descriptions'
|
||||
assert_equal 'admin/descriptions#index', @response.body
|
||||
|
||||
get '/admin/descriptions'
|
||||
assert_equal 'admin/descriptions#index', @response.body
|
||||
|
||||
get '/admin/en/descriptions/1'
|
||||
assert_equal 'admin/descriptions#show', @response.body
|
||||
|
||||
get '/admin/descriptions/1'
|
||||
assert_equal 'admin/descriptions#show', @response.body
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def with_test_routes
|
||||
real_routes, temp_routes = ActionController::Routing::Routes, Routes
|
||||
|
|
|
@ -104,4 +104,27 @@ class ShowExceptionsTest < ActionController::IntegrationTest
|
|||
assert_response 405
|
||||
assert_match /ActionController::MethodNotAllowed/, body
|
||||
end
|
||||
|
||||
test "publishes notifications" do
|
||||
# Wait pending notifications to be published
|
||||
ActiveSupport::Notifications.notifier.wait
|
||||
|
||||
@app, event = ProductionApp, nil
|
||||
self.remote_addr = '127.0.0.1'
|
||||
|
||||
ActiveSupport::Notifications.subscribe('action_dispatch.show_exception') do |*args|
|
||||
event = args
|
||||
end
|
||||
|
||||
get "/"
|
||||
assert_response 500
|
||||
assert_match /puke/, body
|
||||
|
||||
ActiveSupport::Notifications.notifier.wait
|
||||
|
||||
assert_equal 'action_dispatch.show_exception', event.first
|
||||
assert_kind_of Hash, event.last[:env]
|
||||
assert_equal 'GET', event.last[:env]["REQUEST_METHOD"]
|
||||
assert_kind_of RuntimeError, event.last[:exception]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
require 'abstract_unit'
|
||||
|
||||
class StringCoercionTest < ActiveSupport::TestCase
|
||||
test "body responds to each" do
|
||||
original_body = []
|
||||
body = ActionDispatch::StringCoercion::UglyBody.new(original_body)
|
||||
|
||||
assert original_body.respond_to?(:each)
|
||||
assert body.respond_to?(:each)
|
||||
end
|
||||
|
||||
test "body responds to to_path" do
|
||||
original_body = []
|
||||
def original_body.to_path; end
|
||||
body = ActionDispatch::StringCoercion::UglyBody.new(original_body)
|
||||
|
||||
assert original_body.respond_to?(:to_path)
|
||||
assert body.respond_to?(:to_path)
|
||||
end
|
||||
|
||||
test "body does not responds to to_path" do
|
||||
original_body = []
|
||||
body = ActionDispatch::StringCoercion::UglyBody.new(original_body)
|
||||
|
||||
assert !original_body.respond_to?(:to_path)
|
||||
assert !body.respond_to?(:to_path)
|
||||
end
|
||||
|
||||
test "calls to_s on body parts" do
|
||||
app = lambda { |env|
|
||||
[200, {'Content-Type' => 'html'}, [1, 2, 3]]
|
||||
}
|
||||
app = ActionDispatch::StringCoercion.new(app)
|
||||
parts = []
|
||||
status, headers, body = app.call({})
|
||||
body.each { |part| parts << part }
|
||||
|
||||
assert_equal %w( 1 2 3 ), parts
|
||||
end
|
||||
end
|
|
@ -1 +0,0 @@
|
|||
page[:body].visual_effect :highlight
|
|
@ -1 +0,0 @@
|
|||
page[:body].visual_effect :highlight
|
|
@ -1 +0,0 @@
|
|||
xml.p "Hello world!"
|
|
@ -69,7 +69,7 @@ class Post < Struct.new(:title, :author_name, :body, :secret, :written_on, :cost
|
|||
attr_accessor :author
|
||||
def author_attributes=(attributes); end
|
||||
|
||||
attr_accessor :comments
|
||||
attr_accessor :comments, :comment_ids
|
||||
def comments_attributes=(attributes); end
|
||||
|
||||
attr_accessor :tags
|
||||
|
|
|
@ -16,27 +16,27 @@ class ActiveModelHelperI18nTest < Test::Unit::TestCase
|
|||
|
||||
stubs(:content_tag).returns 'content_tag'
|
||||
|
||||
I18n.stubs(:t).with(:'header', :locale => 'en', :scope => [:activemodel, :errors, :template], :count => 1, :model => '').returns "1 error prohibited this from being saved"
|
||||
I18n.stubs(:t).with(:'body', :locale => 'en', :scope => [:activemodel, :errors, :template]).returns 'There were problems with the following fields:'
|
||||
I18n.stubs(:t).with(:'header', :locale => 'en', :scope => [:errors, :template], :count => 1, :model => '').returns "1 error prohibited this from being saved"
|
||||
I18n.stubs(:t).with(:'body', :locale => 'en', :scope => [:errors, :template]).returns 'There were problems with the following fields:'
|
||||
end
|
||||
|
||||
def test_error_messages_for_given_a_header_option_it_does_not_translate_header_message
|
||||
I18n.expects(:t).with(:'header', :locale => 'en', :scope => [:activemodel, :errors, :template], :count => 1, :model => '').never
|
||||
I18n.expects(:t).with(:'header', :locale => 'en', :scope => [:errors, :template], :count => 1, :model => '').never
|
||||
error_messages_for(:object => @object, :header_message => 'header message', :locale => 'en')
|
||||
end
|
||||
|
||||
def test_error_messages_for_given_no_header_option_it_translates_header_message
|
||||
I18n.expects(:t).with(:'header', :locale => 'en', :scope => [:activemodel, :errors, :template], :count => 1, :model => '').returns 'header message'
|
||||
I18n.expects(:t).with(:'header', :locale => 'en', :scope => [:errors, :template], :count => 1, :model => '').returns 'header message'
|
||||
error_messages_for(:object => @object, :locale => 'en')
|
||||
end
|
||||
|
||||
def test_error_messages_for_given_a_message_option_it_does_not_translate_message
|
||||
I18n.expects(:t).with(:'body', :locale => 'en', :scope => [:activemodel, :errors, :template]).never
|
||||
I18n.expects(:t).with(:'body', :locale => 'en', :scope => [:errors, :template]).never
|
||||
error_messages_for(:object => @object, :message => 'message', :locale => 'en')
|
||||
end
|
||||
|
||||
def test_error_messages_for_given_no_message_option_it_translates_message
|
||||
I18n.expects(:t).with(:'body', :locale => 'en', :scope => [:activemodel, :errors, :template]).returns 'There were problems with the following fields:'
|
||||
I18n.expects(:t).with(:'body', :locale => 'en', :scope => [:errors, :template]).returns 'There were problems with the following fields:'
|
||||
error_messages_for(:object => @object, :locale => 'en')
|
||||
end
|
||||
end
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue