mirror of
https://github.com/github/rails.git
synced 2026-04-26 03:00:59 -04:00
Merge branch 'master' of git://github.com/lifo/docrails
This commit is contained in:
6
Gemfile
6
Gemfile
@@ -6,6 +6,7 @@ else
|
||||
gem "arel", :git => "git://github.com/rails/arel.git"
|
||||
end
|
||||
|
||||
gem "rack", :git => "git://github.com/rack/rack.git"
|
||||
gem "rails", :path => File.dirname(__FILE__)
|
||||
|
||||
gem "rake", ">= 0.8.7"
|
||||
@@ -54,6 +55,11 @@ platforms :jruby do
|
||||
|
||||
gem "activerecord-jdbcsqlite3-adapter"
|
||||
|
||||
# This is needed by now to let tests work on JRuby
|
||||
# TODO: When the JRuby guys merge jruby-openssl in
|
||||
# jruby this will be removed
|
||||
gem "jruby-openssl"
|
||||
|
||||
group :db do
|
||||
gem "activerecord-jdbcmysql-adapter"
|
||||
gem "activerecord-jdbcpostgresql-adapter"
|
||||
|
||||
@@ -26,6 +26,7 @@ $:.unshift(actionpack_path) if File.directory?(actionpack_path) && !$:.include?(
|
||||
|
||||
require 'abstract_controller'
|
||||
require 'action_view'
|
||||
require 'action_mailer/version'
|
||||
|
||||
# Common Active Support usage in Action Mailer
|
||||
require 'active_support/core_ext/class'
|
||||
|
||||
@@ -409,7 +409,7 @@ module ActionMailer #:nodoc:
|
||||
protected
|
||||
|
||||
def set_payload_for_mail(payload, mail) #:nodoc:
|
||||
payload[:mailer] = self.name
|
||||
payload[:mailer] = name
|
||||
payload[:message_id] = mail.message_id
|
||||
payload[:subject] = mail.subject
|
||||
payload[:to] = mail.to
|
||||
@@ -421,11 +421,8 @@ module ActionMailer #:nodoc:
|
||||
end
|
||||
|
||||
def method_missing(method, *args) #:nodoc:
|
||||
if action_methods.include?(method.to_s)
|
||||
new(method, *args).message
|
||||
else
|
||||
super
|
||||
end
|
||||
return super unless respond_to?(method)
|
||||
new(method, *args).message
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -247,8 +247,8 @@ module ActionMailer
|
||||
[ nil, {} ]
|
||||
else
|
||||
ctype, *attrs = @content_type.split(/;\s*/)
|
||||
attrs = attrs.inject({}) { |h,s| k,v = s.split(/\=/, 2); h[k] = v; h }
|
||||
[ctype, {"charset" => @charset}.merge(attrs)]
|
||||
attrs = Hash[attrs.map { |attr| attr.split(/\=/, 2) }]
|
||||
[ctype, {"charset" => @charset}.merge!(attrs)]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -14,9 +14,9 @@ module ActionMailer
|
||||
paths = app.config.paths
|
||||
options = app.config.action_mailer
|
||||
|
||||
options.assets_dir ||= paths.public.to_a.first
|
||||
options.javascripts_dir ||= paths.public.javascripts.to_a.first
|
||||
options.stylesheets_dir ||= paths.public.stylesheets.to_a.first
|
||||
options.assets_dir ||= paths["public"].first
|
||||
options.javascripts_dir ||= paths["public/javascripts"].first
|
||||
options.stylesheets_dir ||= paths["public/stylesheets"].first
|
||||
|
||||
# make sure readers methods get compiled
|
||||
options.asset_path ||= nil
|
||||
|
||||
@@ -6,7 +6,7 @@ class <%= class_name %> < ActionMailer::Base
|
||||
# Subject can be set in your I18n file at config/locales/en.yml
|
||||
# with the following lookup:
|
||||
#
|
||||
# en.<%= file_name %>.<%= action %>.subject
|
||||
# en.<%= file_path.gsub("/",".") %>.<%= action %>.subject
|
||||
#
|
||||
def <%= action %>
|
||||
@greeting = "Hi"
|
||||
|
||||
@@ -328,7 +328,6 @@ class ActionMailerTest < Test::Unit::TestCase
|
||||
mail
|
||||
end
|
||||
|
||||
# Replacing logger work around for mocha bug. Should be fixed in mocha 0.3.3
|
||||
def setup
|
||||
set_delivery_method :test
|
||||
ActionMailer::Base.perform_deliveries = true
|
||||
@@ -336,14 +335,12 @@ class ActionMailerTest < Test::Unit::TestCase
|
||||
ActionMailer::Base.deliveries.clear
|
||||
ActiveSupport::Deprecation.silenced = true
|
||||
|
||||
@original_logger = TestMailer.logger
|
||||
@recipient = 'test@localhost'
|
||||
|
||||
TestMailer.delivery_method = :test
|
||||
end
|
||||
|
||||
def teardown
|
||||
TestMailer.logger = @original_logger
|
||||
ActiveSupport::Deprecation.silenced = false
|
||||
restore_delivery_method
|
||||
end
|
||||
@@ -1097,4 +1094,4 @@ EOF
|
||||
ensure
|
||||
TestMailer.smtp_settings.merge!(:enable_starttls_auto => true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,12 +1,20 @@
|
||||
*Rails 3.1.0 (unreleased)*
|
||||
|
||||
* Added render :once. You can pass either a string or an array of strings and Rails will ensure they each of them are rendered just once. [José Valim]
|
||||
|
||||
* Deprecate old template handler API. The new API simply requires a template handler to respond to call. [José Valim]
|
||||
|
||||
* :rhtml and :rxml were finally removed as template handlers. [José Valim]
|
||||
|
||||
* Moved etag responsibility from ActionDispatch::Response to the middleware stack. [José Valim]
|
||||
|
||||
* Rely on Rack::Session stores API for more compatibility across the Ruby world. This is backwards incompatible since Rack::Session expects #get_session to accept 4 arguments and requires #destroy_session instead of simply #destroy. [José Valim]
|
||||
|
||||
* file_field automatically adds :multipart => true to the enclosing form. [Santiago Pastorino]
|
||||
|
||||
* Renames csrf_meta_tag -> csrf_meta_tags, and aliases csrf_meta_tag for backwards compatibility. [fxn]
|
||||
|
||||
* Add Rack::Cache to the default stack. Create a Rails store that delegates to the Rails cache, so by default, whatever caching layer you are using will be used
|
||||
for HTTP caching. Note that Rack::Cache will be used if you use #expires_in, #fresh_when or #stale with :public => true. Otherwise, the caching rules will apply
|
||||
to the browser only.
|
||||
* Add Rack::Cache to the default stack. Create a Rails store that delegates to the Rails cache, so by default, whatever caching layer you are using will be used for HTTP caching. Note that Rack::Cache will be used if you use #expires_in, #fresh_when or #stale with :public => true. Otherwise, the caching rules will apply to the browser only. [Yehuda Katz, Carl Lerche]
|
||||
|
||||
*Rails 3.0.0 (August 29, 2010)*
|
||||
|
||||
|
||||
@@ -8,15 +8,18 @@ Rake can be found at http://rake.rubyforge.org
|
||||
|
||||
== Running by hand
|
||||
|
||||
If you only want to run a single test suite, or don't want to bother with Rake,
|
||||
you can do so with something like:
|
||||
To run a single test suite
|
||||
|
||||
ruby -Itest test/controller/base_tests.rb
|
||||
rake test TEST=path/to/test.rb
|
||||
|
||||
== Dependency on ActiveRecord and database setup
|
||||
which can be further narrowed down to one test:
|
||||
|
||||
rake test TEST=path/to/test.rb TESTOPTS="--name=test_something"
|
||||
|
||||
== Dependency on Active Record and database setup
|
||||
|
||||
Test cases in the test/controller/active_record/ directory depend on having
|
||||
activerecord and sqlite installed. If ActiveRecord is not in
|
||||
activerecord and sqlite installed. If Active Record is not in
|
||||
actionpack/../activerecord directory, or the sqlite rubygem is not installed,
|
||||
these tests are skipped.
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ module AbstractController
|
||||
class Base
|
||||
attr_internal :response_body
|
||||
attr_internal :action_name
|
||||
attr_internal :formats
|
||||
|
||||
include ActiveSupport::Configurable
|
||||
extend ActiveSupport::DescendantsTracker
|
||||
@@ -61,13 +62,13 @@ module AbstractController
|
||||
def action_methods
|
||||
@action_methods ||= begin
|
||||
# All public instance methods of this class, including ancestors
|
||||
methods = public_instance_methods(true).map { |m| m.to_s }.to_set -
|
||||
methods = (public_instance_methods(true) -
|
||||
# Except for public instance methods of Base and its ancestors
|
||||
internal_methods.map { |m| m.to_s } +
|
||||
internal_methods +
|
||||
# Be sure to include shadowed public instance methods of this class
|
||||
public_instance_methods(false).map { |m| m.to_s } -
|
||||
public_instance_methods(false)).uniq.map { |x| x.to_s } -
|
||||
# And always exclude explicitly hidden actions
|
||||
hidden_actions
|
||||
hidden_actions.to_a
|
||||
|
||||
# Clear out AS callback method pollution
|
||||
methods.reject { |method| method =~ /_one_time_conditions/ }
|
||||
|
||||
@@ -12,7 +12,6 @@ module AbstractController
|
||||
|
||||
# This is a class to fix I18n global state. Whenever you provide I18n.locale during a request,
|
||||
# it will trigger the lookup_context and consequently expire the cache.
|
||||
# TODO Add some deprecation warnings to remove I18n.locale from controllers
|
||||
class I18nProxy < ::I18n::Config #:nodoc:
|
||||
attr_reader :i18n_config, :lookup_context
|
||||
|
||||
@@ -50,7 +49,7 @@ module AbstractController
|
||||
if controller.respond_to?(:_helpers)
|
||||
include controller._helpers
|
||||
|
||||
if controller.respond_to?(:_routes)
|
||||
if controller.respond_to?(:_routes) && controller._routes
|
||||
include controller._routes.url_helpers
|
||||
include controller._routes.mounted_helpers
|
||||
end
|
||||
@@ -156,7 +155,7 @@ module AbstractController
|
||||
options[:partial] = action_name
|
||||
end
|
||||
|
||||
if (options.keys & [:partial, :file, :template]).empty?
|
||||
if (options.keys & [:partial, :file, :template, :once]).empty?
|
||||
options[:prefix] ||= _prefix
|
||||
end
|
||||
|
||||
|
||||
@@ -119,6 +119,11 @@ module ActionController
|
||||
headers["Location"] = url
|
||||
end
|
||||
|
||||
# basic url_for that can be overridden for more robust functionality
|
||||
def url_for(string)
|
||||
string
|
||||
end
|
||||
|
||||
def status
|
||||
@_status
|
||||
end
|
||||
|
||||
@@ -66,7 +66,7 @@ module ActionController
|
||||
# Examples:
|
||||
# expires_in 20.minutes
|
||||
# expires_in 3.hours, :public => true
|
||||
# expires in 3.hours, 'max-stale' => 5.hours, :public => true
|
||||
# expires_in 3.hours, 'max-stale' => 5.hours, :public => true
|
||||
#
|
||||
# This method will overwrite an existing Cache-Control header.
|
||||
# See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more possibilities.
|
||||
|
||||
@@ -2,8 +2,6 @@ module ActionController
|
||||
module Head
|
||||
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.
|
||||
# This allows you to easily return a response that consists only of
|
||||
@@ -27,8 +25,8 @@ module ActionController
|
||||
|
||||
self.status = status
|
||||
self.location = url_for(location) if location
|
||||
self.content_type = Mime[formats.first]
|
||||
self.content_type = Mime[formats.first] if formats
|
||||
self.response_body = " "
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -214,7 +214,7 @@ module ActionController
|
||||
|
||||
def encode_credentials(http_method, credentials, password, password_is_ha1)
|
||||
credentials[:response] = expected_response(http_method, credentials[:uri], credentials, password, password_is_ha1)
|
||||
"Digest " + credentials.sort_by {|x| x[0].to_s }.inject([]) {|a, v| a << "#{v[0]}='#{v[1]}'" }.join(', ')
|
||||
"Digest " + credentials.sort_by {|x| x[0].to_s }.map {|v| "#{v[0]}='#{v[1]}'" }.join(', ')
|
||||
end
|
||||
|
||||
def decode_credentials_header(request)
|
||||
@@ -423,14 +423,13 @@ module ActionController
|
||||
# Returns nil if no token is found.
|
||||
def token_and_options(request)
|
||||
if header = request.authorization.to_s[/^Token (.*)/]
|
||||
values = $1.split(',').
|
||||
inject({}) do |memo, value|
|
||||
value.strip! # remove any spaces between commas and values
|
||||
key, value = value.split(/\=\"?/) # split key=value pairs
|
||||
value.chomp!('"') # chomp trailing " in value
|
||||
value.gsub!(/\\\"/, '"') # unescape remaining quotes
|
||||
memo.update(key => value)
|
||||
end
|
||||
values = Hash[$1.split(',').map do |value|
|
||||
value.strip! # remove any spaces between commas and values
|
||||
key, value = value.split(/\=\"?/) # split key=value pairs
|
||||
value.chomp!('"') # chomp trailing " in value
|
||||
value.gsub!(/\\\"/, '"') # unescape remaining quotes
|
||||
[key, value]
|
||||
end]
|
||||
[values.delete("token"), values.with_indifferent_access]
|
||||
end
|
||||
end
|
||||
@@ -442,9 +441,8 @@ module ActionController
|
||||
#
|
||||
# Returns String.
|
||||
def encode_credentials(token, options = {})
|
||||
values = ["token=#{token.to_s.inspect}"]
|
||||
options.each do |key, value|
|
||||
values << "#{key}=#{value.to_s.inspect}"
|
||||
values = ["token=#{token.to_s.inspect}"] + options.map do |key, value|
|
||||
"#{key}=#{value.to_s.inspect}"
|
||||
end
|
||||
"Token #{values * ", "}"
|
||||
end
|
||||
|
||||
@@ -2,7 +2,6 @@ module ActionController
|
||||
module Rendering
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
include ActionController::RackDelegation
|
||||
include AbstractController::Rendering
|
||||
|
||||
# Before processing, set the request formats in current controller formats.
|
||||
|
||||
@@ -101,10 +101,6 @@ module ActionController #:nodoc:
|
||||
# send_data image.data, :type => image.content_type, :disposition => 'inline'
|
||||
#
|
||||
# See +send_file+ for more information on HTTP Content-* headers and caching.
|
||||
#
|
||||
# <b>Tip:</b> if you want to stream large amounts of on-the-fly generated
|
||||
# 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:
|
||||
send_file_headers! options.dup
|
||||
render options.slice(:status, :content_type).merge(:text => data)
|
||||
|
||||
@@ -21,10 +21,10 @@ module ActionController
|
||||
paths = app.config.paths
|
||||
options = app.config.action_controller
|
||||
|
||||
options.assets_dir ||= paths.public.to_a.first
|
||||
options.javascripts_dir ||= paths.public.javascripts.to_a.first
|
||||
options.stylesheets_dir ||= paths.public.stylesheets.to_a.first
|
||||
options.page_cache_directory ||= paths.public.to_a.first
|
||||
options.assets_dir ||= paths["public"].first
|
||||
options.javascripts_dir ||= paths["public/javascripts"].first
|
||||
options.stylesheets_dir ||= paths["public/stylesheets"].first
|
||||
options.page_cache_directory ||= paths["public"].first
|
||||
|
||||
# make sure readers methods get compiled
|
||||
options.asset_path ||= nil
|
||||
|
||||
@@ -5,12 +5,14 @@ module ActionController
|
||||
Module.new do
|
||||
define_method(:inherited) do |klass|
|
||||
super(klass)
|
||||
|
||||
if namespace = klass.parents.detect {|m| m.respond_to?(:_railtie) }
|
||||
klass.helpers_path = namespace._railtie.config.paths.app.helpers.to_a
|
||||
paths = namespace._railtie.paths["app/helpers"].existent
|
||||
else
|
||||
klass.helpers_path = app.config.helpers_paths
|
||||
paths = app.config.helpers_paths
|
||||
end
|
||||
|
||||
klass.helpers_path = paths
|
||||
klass.helper :all if klass.superclass == ActionController::Base
|
||||
end
|
||||
end
|
||||
|
||||
@@ -187,15 +187,18 @@ module ActionController
|
||||
end
|
||||
end
|
||||
|
||||
class TestSession < ActionDispatch::Session::AbstractStore::SessionHash #:nodoc:
|
||||
DEFAULT_OPTIONS = ActionDispatch::Session::AbstractStore::DEFAULT_OPTIONS
|
||||
class TestSession < Rack::Session::Abstract::SessionHash #:nodoc:
|
||||
DEFAULT_OPTIONS = Rack::Session::Abstract::ID::DEFAULT_OPTIONS
|
||||
|
||||
def initialize(session = {})
|
||||
@env, @by = nil, nil
|
||||
replace(session.stringify_keys)
|
||||
@loaded = true
|
||||
end
|
||||
|
||||
def exists?; true; end
|
||||
def exists?
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
# Superclass for ActionController functional tests. Functional tests allow you to
|
||||
@@ -394,7 +397,7 @@ module ActionController
|
||||
parameters ||= {}
|
||||
@request.assign_parameters(@routes, @controller.class.name.underscore.sub(/_controller$/, ''), action.to_s, parameters)
|
||||
|
||||
@request.session = ActionController::TestSession.new(session) unless session.nil?
|
||||
@request.session = ActionController::TestSession.new(session) if session
|
||||
@request.session["flash"] = @request.flash.update(flash || {})
|
||||
@request.session["flash"].sweep
|
||||
|
||||
|
||||
@@ -50,8 +50,7 @@ module ActionDispatch
|
||||
if cache_control = self["Cache-Control"]
|
||||
cache_control.split(/,\s*/).each do |segment|
|
||||
first, last = segment.split("=")
|
||||
last ||= true
|
||||
@cache_control[first.to_sym] = last
|
||||
@cache_control[first.to_sym] = last || true
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -88,28 +87,9 @@ module ActionDispatch
|
||||
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!
|
||||
|
||||
@@ -54,11 +54,7 @@ module ActionDispatch
|
||||
# the application should use), this \method returns the overridden
|
||||
# value, not the original.
|
||||
def request_method
|
||||
@request_method ||= begin
|
||||
method = env["REQUEST_METHOD"]
|
||||
HTTP_METHOD_LOOKUP[method] || raise(ActionController::UnknownHttpMethod, "#{method}, accepted HTTP methods are #{HTTP_METHODS.to_sentence(:locale => :en)}")
|
||||
method
|
||||
end
|
||||
@request_method ||= check_method(env["REQUEST_METHOD"])
|
||||
end
|
||||
|
||||
# Returns a symbol form of the #request_method
|
||||
@@ -70,11 +66,7 @@ module ActionDispatch
|
||||
# even if it was overridden by middleware. See #request_method for
|
||||
# more information.
|
||||
def method
|
||||
@method ||= begin
|
||||
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)}")
|
||||
method
|
||||
end
|
||||
@method ||= check_method(env["rack.methodoverride.original_method"] || env['REQUEST_METHOD'])
|
||||
end
|
||||
|
||||
# Returns a symbol form of the #method
|
||||
@@ -207,7 +199,7 @@ module ActionDispatch
|
||||
# TODO This should be broken apart into AD::Request::Session and probably
|
||||
# be included by the session middleware.
|
||||
def reset_session
|
||||
session.destroy if session
|
||||
session.destroy if session && session.respond_to?(:destroy)
|
||||
self.session = {}
|
||||
@env['action_dispatch.request.flash_hash'] = nil
|
||||
end
|
||||
@@ -246,5 +238,12 @@ module ActionDispatch
|
||||
def local?
|
||||
LOCALHOST.any? { |local_ip| local_ip === remote_addr && local_ip === remote_ip }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_method(name)
|
||||
HTTP_METHOD_LOOKUP[name] || raise(ActionController::UnknownHttpMethod, "#{name}, accepted HTTP methods are #{HTTP_METHODS.to_sentence(:locale => :en)}")
|
||||
name
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -132,7 +132,7 @@ module ActionDispatch # :nodoc:
|
||||
# information.
|
||||
attr_accessor :charset, :content_type
|
||||
|
||||
CONTENT_TYPE = "Content-Type"
|
||||
CONTENT_TYPE = "Content-Type"
|
||||
|
||||
cattr_accessor(:default_charset) { "utf-8" }
|
||||
|
||||
|
||||
@@ -2,27 +2,28 @@ require 'active_support/core_ext/object/blank'
|
||||
|
||||
module ActionDispatch
|
||||
module Http
|
||||
class UploadedFile < Tempfile
|
||||
class UploadedFile
|
||||
attr_accessor :original_filename, :content_type, :tempfile, :headers
|
||||
|
||||
def initialize(hash)
|
||||
@original_filename = hash[:filename]
|
||||
@content_type = hash[:type]
|
||||
@headers = hash[:head]
|
||||
|
||||
# To the untrained eye, this may appear as insanity. Given the alternatives,
|
||||
# such as busting the method cache on every request or breaking backwards
|
||||
# compatibility with is_a?(Tempfile), this solution is the best available
|
||||
# option.
|
||||
#
|
||||
# TODO: Deprecate is_a?(Tempfile) and define a real API for this parameter
|
||||
tempfile = hash[:tempfile]
|
||||
tempfile.instance_variables.each do |ivar|
|
||||
instance_variable_set(ivar, tempfile.instance_variable_get(ivar))
|
||||
end
|
||||
@tempfile = hash[:tempfile]
|
||||
raise(ArgumentError, ':tempfile is required') unless @tempfile
|
||||
end
|
||||
|
||||
alias local_path path
|
||||
def read(*args)
|
||||
@tempfile.read(*args)
|
||||
end
|
||||
|
||||
def rewind
|
||||
@tempfile.rewind
|
||||
end
|
||||
|
||||
def size
|
||||
@tempfile.size
|
||||
end
|
||||
end
|
||||
|
||||
module Upload
|
||||
|
||||
@@ -18,11 +18,6 @@ module ActionDispatch
|
||||
@protocol ||= ssl? ? 'https://' : 'http://'
|
||||
end
|
||||
|
||||
# Is this an SSL request?
|
||||
def ssl?
|
||||
@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"]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
require 'rack/utils'
|
||||
require 'rack/request'
|
||||
require 'rack/session/abstract/id'
|
||||
require 'action_dispatch/middleware/cookies'
|
||||
require 'active_support/core_ext/object/blank'
|
||||
|
||||
@@ -8,252 +9,76 @@ module ActionDispatch
|
||||
class SessionRestoreError < StandardError #:nodoc:
|
||||
end
|
||||
|
||||
class AbstractStore
|
||||
ENV_SESSION_KEY = 'rack.session'.freeze
|
||||
ENV_SESSION_OPTIONS_KEY = 'rack.session.options'.freeze
|
||||
|
||||
# thin wrapper around Hash that allows us to lazily
|
||||
# load session id into session_options
|
||||
class OptionsHash < Hash
|
||||
def initialize(by, env, default_options)
|
||||
@by = by
|
||||
@env = env
|
||||
@session_id_loaded = false
|
||||
merge!(default_options)
|
||||
end
|
||||
|
||||
def [](key)
|
||||
if key == :id
|
||||
load_session_id! unless key?(:id) || has_session_id?
|
||||
end
|
||||
super
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def has_session_id?
|
||||
@session_id_loaded
|
||||
end
|
||||
|
||||
def load_session_id!
|
||||
self[:id] = @by.send(:extract_session_id, @env)
|
||||
@session_id_loaded = true
|
||||
end
|
||||
module DestroyableSession
|
||||
def destroy
|
||||
clear
|
||||
options = @env[Rack::Session::Abstract::ENV_SESSION_OPTIONS_KEY] if @env
|
||||
options ||= {}
|
||||
@by.send(:destroy_session, @env, options[:id], options) if @by
|
||||
options[:id] = nil
|
||||
@loaded = false
|
||||
end
|
||||
end
|
||||
|
||||
class SessionHash < Hash
|
||||
def initialize(by, env)
|
||||
super()
|
||||
@by = by
|
||||
@env = env
|
||||
@loaded = false
|
||||
end
|
||||
|
||||
def [](key)
|
||||
load_for_read!
|
||||
super(key.to_s)
|
||||
end
|
||||
|
||||
def has_key?(key)
|
||||
load_for_read!
|
||||
super(key.to_s)
|
||||
end
|
||||
|
||||
def []=(key, value)
|
||||
load_for_write!
|
||||
super(key.to_s, value)
|
||||
end
|
||||
|
||||
def clear
|
||||
load_for_write!
|
||||
super
|
||||
end
|
||||
|
||||
def to_hash
|
||||
load_for_read!
|
||||
h = {}.replace(self)
|
||||
h.delete_if { |k,v| v.nil? }
|
||||
h
|
||||
end
|
||||
|
||||
def update(hash)
|
||||
load_for_write!
|
||||
super(hash.stringify_keys)
|
||||
end
|
||||
|
||||
def delete(key)
|
||||
load_for_write!
|
||||
super(key.to_s)
|
||||
end
|
||||
|
||||
def inspect
|
||||
load_for_read!
|
||||
super
|
||||
end
|
||||
|
||||
def exists?
|
||||
return @exists if instance_variable_defined?(:@exists)
|
||||
@exists = @by.send(:exists?, @env)
|
||||
end
|
||||
|
||||
def loaded?
|
||||
@loaded
|
||||
end
|
||||
|
||||
def destroy
|
||||
clear
|
||||
@by.send(:destroy, @env) if defined?(@by) && @by
|
||||
@env[ENV_SESSION_OPTIONS_KEY][:id] = nil if defined?(@env) && @env && @env[ENV_SESSION_OPTIONS_KEY]
|
||||
@loaded = false
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_for_read!
|
||||
load! if !loaded? && exists?
|
||||
end
|
||||
|
||||
def load_for_write!
|
||||
load! unless loaded?
|
||||
end
|
||||
|
||||
def load!
|
||||
id, session = @by.send(:load_session, @env)
|
||||
@env[ENV_SESSION_OPTIONS_KEY][:id] = id
|
||||
replace(session.stringify_keys)
|
||||
@loaded = true
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
DEFAULT_OPTIONS = {
|
||||
:key => '_session_id',
|
||||
:path => '/',
|
||||
:domain => nil,
|
||||
:expire_after => nil,
|
||||
:secure => false,
|
||||
:httponly => true,
|
||||
:cookie_only => true
|
||||
}
|
||||
::Rack::Session::Abstract::SessionHash.send :include, DestroyableSession
|
||||
|
||||
module Compatibility
|
||||
def initialize(app, options = {})
|
||||
@app = app
|
||||
@default_options = DEFAULT_OPTIONS.merge(options)
|
||||
@key = @default_options.delete(:key).freeze
|
||||
@cookie_only = @default_options.delete(:cookie_only)
|
||||
ensure_session_key!
|
||||
options[:key] ||= '_session_id'
|
||||
super
|
||||
end
|
||||
|
||||
def call(env)
|
||||
prepare!(env)
|
||||
response = @app.call(env)
|
||||
|
||||
session_data = env[ENV_SESSION_KEY]
|
||||
options = env[ENV_SESSION_OPTIONS_KEY]
|
||||
|
||||
if !session_data.is_a?(AbstractStore::SessionHash) || session_data.loaded? || options[:expire_after]
|
||||
request = ActionDispatch::Request.new(env)
|
||||
|
||||
return response if (options[:secure] && !request.ssl?)
|
||||
|
||||
session_data.send(:load!) if session_data.is_a?(AbstractStore::SessionHash) && !session_data.loaded?
|
||||
|
||||
sid = options[:id] || generate_sid
|
||||
session_data = session_data.to_hash
|
||||
|
||||
value = set_session(env, sid, session_data)
|
||||
return response unless value
|
||||
|
||||
cookie = { :value => value }
|
||||
unless options[:expire_after].nil?
|
||||
cookie[:expires] = Time.now + options.delete(:expire_after)
|
||||
end
|
||||
|
||||
set_cookie(request, cookie.merge!(options))
|
||||
end
|
||||
|
||||
response
|
||||
def generate_sid
|
||||
ActiveSupport::SecureRandom.hex(16)
|
||||
end
|
||||
|
||||
private
|
||||
protected
|
||||
|
||||
def prepare!(env)
|
||||
env[ENV_SESSION_KEY] = SessionHash.new(self, env)
|
||||
env[ENV_SESSION_OPTIONS_KEY] = OptionsHash.new(self, env, @default_options)
|
||||
end
|
||||
def initialize_sid
|
||||
@default_options.delete(:sidbits)
|
||||
@default_options.delete(:secure_random)
|
||||
end
|
||||
end
|
||||
|
||||
def generate_sid
|
||||
ActiveSupport::SecureRandom.hex(16)
|
||||
end
|
||||
module StaleSessionCheck
|
||||
def load_session(env)
|
||||
stale_session_check! { super }
|
||||
end
|
||||
|
||||
def set_cookie(request, options)
|
||||
if request.cookie_jar[@key] != options[:value] || !options[:expires].nil?
|
||||
request.cookie_jar[@key] = options
|
||||
def extract_session_id(env)
|
||||
stale_session_check! { super }
|
||||
end
|
||||
|
||||
def stale_session_check!
|
||||
yield
|
||||
rescue ArgumentError => argument_error
|
||||
if argument_error.message =~ %r{undefined class/module ([\w:]*\w)}
|
||||
begin
|
||||
# Note that the regexp does not allow $1 to end with a ':'
|
||||
$1.constantize
|
||||
rescue LoadError, NameError => const_error
|
||||
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
|
||||
else
|
||||
raise
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def load_session(env)
|
||||
stale_session_check! do
|
||||
sid = current_session_id(env)
|
||||
sid, session = get_session(env, sid)
|
||||
[sid, session]
|
||||
end
|
||||
end
|
||||
class AbstractStore < Rack::Session::Abstract::ID
|
||||
include Compatibility
|
||||
include StaleSessionCheck
|
||||
|
||||
def extract_session_id(env)
|
||||
stale_session_check! do
|
||||
request = ActionDispatch::Request.new(env)
|
||||
sid = request.cookies[@key]
|
||||
sid ||= request.params[@key] unless @cookie_only
|
||||
sid
|
||||
end
|
||||
end
|
||||
def destroy_session(env, sid, options)
|
||||
ActiveSupport::Deprecation.warn "Implementing #destroy in session stores is deprecated. " <<
|
||||
"Please implement destroy_session(env, session_id, options) instead."
|
||||
destroy(env)
|
||||
end
|
||||
|
||||
def current_session_id(env)
|
||||
env[ENV_SESSION_OPTIONS_KEY][:id]
|
||||
end
|
||||
|
||||
def ensure_session_key!
|
||||
if @key.blank?
|
||||
raise ArgumentError, 'A key is required to write a ' +
|
||||
'cookie containing the session data. Use ' +
|
||||
'config.session_store SESSION_STORE, { :key => ' +
|
||||
'"_myapp_session" } in config/application.rb'
|
||||
end
|
||||
end
|
||||
|
||||
def stale_session_check!
|
||||
yield
|
||||
rescue ArgumentError => argument_error
|
||||
if argument_error.message =~ %r{undefined class/module ([\w:]*\w)}
|
||||
begin
|
||||
# Note that the regexp does not allow $1 to end with a ':'
|
||||
$1.constantize
|
||||
rescue LoadError, NameError => const_error
|
||||
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
|
||||
else
|
||||
raise
|
||||
end
|
||||
end
|
||||
|
||||
def exists?(env)
|
||||
current_session_id(env).present?
|
||||
end
|
||||
|
||||
def get_session(env, sid)
|
||||
raise '#get_session needs to be implemented.'
|
||||
end
|
||||
|
||||
def set_session(env, sid, session_data)
|
||||
raise '#set_session needs to be implemented and should return ' <<
|
||||
'the value to be stored in the cookie (usually the sid)'
|
||||
end
|
||||
|
||||
def destroy(env)
|
||||
raise '#destroy needs to be implemented.'
|
||||
end
|
||||
def destroy(env)
|
||||
raise '#destroy needs to be implemented.'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
require 'active_support/core_ext/hash/keys'
|
||||
require 'active_support/core_ext/object/blank'
|
||||
require 'action_dispatch/middleware/session/abstract_store'
|
||||
require 'rack/session/cookie'
|
||||
|
||||
module ActionDispatch
|
||||
module Session
|
||||
@@ -38,58 +40,32 @@ module ActionDispatch
|
||||
# "rake secret" and set the key in config/initializers/secret_token.rb.
|
||||
#
|
||||
# Note that changing digest or secret invalidates all existing sessions!
|
||||
class CookieStore < AbstractStore
|
||||
|
||||
def initialize(app, options = {})
|
||||
super(app, options.merge!(:cookie_only => true))
|
||||
freeze
|
||||
end
|
||||
class CookieStore < Rack::Session::Cookie
|
||||
include Compatibility
|
||||
include StaleSessionCheck
|
||||
|
||||
private
|
||||
|
||||
def load_session(env)
|
||||
data = unpacked_cookie_data(env)
|
||||
data = persistent_session_id!(data)
|
||||
[data["session_id"], data]
|
||||
end
|
||||
|
||||
def extract_session_id(env)
|
||||
if data = unpacked_cookie_data(env)
|
||||
data["session_id"]
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def unpacked_cookie_data(env)
|
||||
env["action_dispatch.request.unsigned_session_cookie"] ||= begin
|
||||
stale_session_check! do
|
||||
request = ActionDispatch::Request.new(env)
|
||||
if data = request.cookie_jar.signed[@key]
|
||||
data.stringify_keys!
|
||||
end
|
||||
data || {}
|
||||
def unpacked_cookie_data(env)
|
||||
env["action_dispatch.request.unsigned_session_cookie"] ||= begin
|
||||
stale_session_check! do
|
||||
request = ActionDispatch::Request.new(env)
|
||||
if data = request.cookie_jar.signed[@key]
|
||||
data.stringify_keys!
|
||||
end
|
||||
data || {}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def set_cookie(request, options)
|
||||
request.cookie_jar.signed[@key] = options
|
||||
end
|
||||
def set_session(env, sid, session_data, options)
|
||||
persistent_session_id!(session_data, sid)
|
||||
end
|
||||
|
||||
def set_session(env, sid, session_data)
|
||||
persistent_session_id!(session_data, sid)
|
||||
end
|
||||
|
||||
def destroy(env)
|
||||
# session data is stored on client; nothing to do here
|
||||
end
|
||||
|
||||
def persistent_session_id!(data, sid=nil)
|
||||
data ||= {}
|
||||
data["session_id"] ||= sid || generate_sid
|
||||
data
|
||||
end
|
||||
def set_cookie(env, session_id, cookie)
|
||||
request = ActionDispatch::Request.new(env)
|
||||
request.cookie_jar.signed[@key] = cookie
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,56 +1,17 @@
|
||||
require 'action_dispatch/middleware/session/abstract_store'
|
||||
require 'rack/session/memcache'
|
||||
|
||||
module ActionDispatch
|
||||
module Session
|
||||
class MemCacheStore < AbstractStore
|
||||
class MemCacheStore < Rack::Session::Memcache
|
||||
include Compatibility
|
||||
include StaleSessionCheck
|
||||
|
||||
def initialize(app, options = {})
|
||||
require 'memcache'
|
||||
|
||||
# Support old :expires option
|
||||
options[:expire_after] ||= options[:expires]
|
||||
|
||||
super
|
||||
|
||||
@default_options = {
|
||||
:namespace => 'rack:session',
|
||||
:memcache_server => 'localhost:11211'
|
||||
}.merge(@default_options)
|
||||
|
||||
@pool = options[:cache] || MemCache.new(@default_options[:memcache_server], @default_options)
|
||||
unless @pool.servers.any? { |s| s.alive? }
|
||||
raise "#{self} unable to find server during initialization."
|
||||
end
|
||||
@mutex = Mutex.new
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
private
|
||||
def get_session(env, sid)
|
||||
sid ||= generate_sid
|
||||
begin
|
||||
session = @pool.get(sid) || {}
|
||||
rescue MemCache::MemCacheError, Errno::ECONNREFUSED
|
||||
session = {}
|
||||
end
|
||||
[sid, session]
|
||||
end
|
||||
|
||||
def set_session(env, sid, session_data)
|
||||
options = env['rack.session.options']
|
||||
expiry = options[:expire_after] || 0
|
||||
@pool.set(sid, session_data, expiry)
|
||||
sid
|
||||
rescue MemCache::MemCacheError, Errno::ECONNREFUSED
|
||||
false
|
||||
end
|
||||
|
||||
def destroy(env)
|
||||
if sid = current_session_id(env)
|
||||
@pool.delete(sid)
|
||||
end
|
||||
rescue MemCache::MemCacheError, Errno::ECONNREFUSED
|
||||
false
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,13 +6,13 @@ module ActionDispatch
|
||||
@at, @root = at.chomp('/'), root.chomp('/')
|
||||
@compiled_at = (Regexp.compile(/^#{Regexp.escape(at)}/) unless @at.blank?)
|
||||
@compiled_root = Regexp.compile(/^#{Regexp.escape(root)}/)
|
||||
@file_server = ::Rack::File.new(root)
|
||||
@file_server = ::Rack::File.new(@root)
|
||||
end
|
||||
|
||||
def match?(path)
|
||||
path = path.dup
|
||||
if @compiled_at.blank? || path.sub!(@compiled_at, '')
|
||||
full_path = File.join(@root, ::Rack::Utils.unescape(path))
|
||||
if !@compiled_at || path.sub!(@compiled_at, '')
|
||||
full_path = path.empty? ? @root : File.join(@root, ::Rack::Utils.unescape(path))
|
||||
paths = "#{full_path}#{ext}"
|
||||
|
||||
matches = Dir[paths]
|
||||
|
||||
@@ -171,13 +171,13 @@ module ActionDispatch
|
||||
end
|
||||
|
||||
def blocks
|
||||
block = @scope[:blocks] || []
|
||||
|
||||
if @options[:constraints].present? && !@options[:constraints].is_a?(Hash)
|
||||
block = @options[:constraints]
|
||||
else
|
||||
block = nil
|
||||
block << @options[:constraints]
|
||||
end
|
||||
|
||||
((@scope[:blocks] || []) + [ block ]).compact
|
||||
block
|
||||
end
|
||||
|
||||
def constraints
|
||||
@@ -345,11 +345,11 @@ module ActionDispatch
|
||||
# Redirect any path to another path:
|
||||
#
|
||||
# match "/stories" => redirect("/posts")
|
||||
def redirect(*args, &block)
|
||||
def redirect(*args)
|
||||
options = args.last.is_a?(Hash) ? args.pop : {}
|
||||
|
||||
path = args.shift || block
|
||||
path_proc = path.is_a?(Proc) ? path : proc { |params| params.empty? ? path : (path % params) }
|
||||
path = args.shift || Proc.new
|
||||
path_proc = path.is_a?(Proc) ? path : proc { |params| (params.empty? || !path.match(/%\{\w*\}/)) ? path : (path % params) }
|
||||
status = options[:status] || 301
|
||||
|
||||
lambda do |env|
|
||||
@@ -735,15 +735,15 @@ module ActionDispatch
|
||||
resource_scope(SingletonResource.new(resources.pop, options)) do
|
||||
yield if block_given?
|
||||
|
||||
collection_scope do
|
||||
collection do
|
||||
post :create
|
||||
end if parent_resource.actions.include?(:create)
|
||||
|
||||
new_scope do
|
||||
new do
|
||||
get :new
|
||||
end if parent_resource.actions.include?(:new)
|
||||
|
||||
member_scope do
|
||||
member do
|
||||
get :edit if parent_resource.actions.include?(:edit)
|
||||
get :show if parent_resource.actions.include?(:show)
|
||||
put :update if parent_resource.actions.include?(:update)
|
||||
@@ -780,16 +780,16 @@ module ActionDispatch
|
||||
resource_scope(Resource.new(resources.pop, options)) do
|
||||
yield if block_given?
|
||||
|
||||
collection_scope do
|
||||
collection do
|
||||
get :index if parent_resource.actions.include?(:index)
|
||||
post :create if parent_resource.actions.include?(:create)
|
||||
end
|
||||
|
||||
new_scope do
|
||||
new do
|
||||
get :new
|
||||
end if parent_resource.actions.include?(:new)
|
||||
|
||||
member_scope do
|
||||
member do
|
||||
get :edit if parent_resource.actions.include?(:edit)
|
||||
get :show if parent_resource.actions.include?(:show)
|
||||
put :update if parent_resource.actions.include?(:update)
|
||||
@@ -813,12 +813,14 @@ module ActionDispatch
|
||||
# create the <tt>search_photos_url</tt> and <tt>search_photos_path</tt>
|
||||
# route helpers.
|
||||
def collection
|
||||
unless @scope[:scope_level] == :resources
|
||||
raise ArgumentError, "can't use collection outside resources scope"
|
||||
unless resource_scope?
|
||||
raise ArgumentError, "can't use collection outside resource(s) scope"
|
||||
end
|
||||
|
||||
collection_scope do
|
||||
yield
|
||||
with_scope_level(:collection) do
|
||||
scope(parent_resource.collection_scope) do
|
||||
yield
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -838,8 +840,10 @@ module ActionDispatch
|
||||
raise ArgumentError, "can't use member outside resource(s) scope"
|
||||
end
|
||||
|
||||
member_scope do
|
||||
yield
|
||||
with_scope_level(:member) do
|
||||
scope(parent_resource.member_scope) do
|
||||
yield
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -848,8 +852,10 @@ module ActionDispatch
|
||||
raise ArgumentError, "can't use new outside resource(s) scope"
|
||||
end
|
||||
|
||||
new_scope do
|
||||
yield
|
||||
with_scope_level(:new) do
|
||||
scope(parent_resource.new_scope(action_path(:new))) do
|
||||
yield
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1034,30 +1040,6 @@ module ActionDispatch
|
||||
end
|
||||
end
|
||||
|
||||
def new_scope
|
||||
with_scope_level(:new) do
|
||||
scope(parent_resource.new_scope(action_path(:new))) do
|
||||
yield
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def collection_scope
|
||||
with_scope_level(:collection) do
|
||||
scope(parent_resource.collection_scope) do
|
||||
yield
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def member_scope
|
||||
with_scope_level(:member) do
|
||||
scope(parent_resource.member_scope) do
|
||||
yield
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def nested_options
|
||||
{}.tap do |options|
|
||||
options[:as] = parent_resource.member_name
|
||||
@@ -1130,7 +1112,7 @@ module ActionDispatch
|
||||
end
|
||||
|
||||
candidate = name.select(&:present?).join("_").presence
|
||||
candidate unless as.nil? && @set.routes.map(&:name).include?(candidate)
|
||||
candidate unless as.nil? && @set.routes.find { |r| r.name == candidate }
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
require 'rack/mount'
|
||||
require 'forwardable'
|
||||
require 'active_support/core_ext/object/to_query'
|
||||
require 'active_support/core_ext/hash/slice'
|
||||
|
||||
module ActionDispatch
|
||||
module Routing
|
||||
@@ -511,7 +512,7 @@ module ActionDispatch
|
||||
end
|
||||
|
||||
script_name = options.delete(:script_name)
|
||||
path = (script_name.blank? ? _generate_prefix(options) : script_name).to_s
|
||||
path = (script_name.blank? ? _generate_prefix(options) : script_name.chomp('/')).to_s
|
||||
|
||||
path_options = options.except(*RESERVED_OPTIONS)
|
||||
path_options = yield(path_options) if block_given?
|
||||
|
||||
@@ -81,14 +81,10 @@ module ActionDispatch
|
||||
|
||||
def normalize_argument_to_redirection(fragment)
|
||||
case fragment
|
||||
when %r{^\w[\w\d+.-]*:.*}
|
||||
when %r{^\w[A-Za-z\d+.-]*:.*}
|
||||
fragment
|
||||
when String
|
||||
if fragment =~ %r{^\w[\w\d+.-]*:.*}
|
||||
fragment
|
||||
else
|
||||
@request.protocol + @request.host_with_port + fragment
|
||||
end
|
||||
@request.protocol + @request.host_with_port + fragment
|
||||
when :back
|
||||
raise RedirectBackError unless refer = @request.headers["Referer"]
|
||||
refer
|
||||
|
||||
@@ -11,9 +11,8 @@ begin
|
||||
# formats are written, so you'll have two output files per test method.
|
||||
class PerformanceTest < ActionDispatch::IntegrationTest
|
||||
include ActiveSupport::Testing::Performance
|
||||
include ActiveSupport::Testing::Default
|
||||
end
|
||||
end
|
||||
rescue NameError
|
||||
$stderr.puts "Specify ruby-prof as application's dependency in Gemfile to run benchmarks."
|
||||
end
|
||||
end
|
||||
|
||||
@@ -38,11 +38,17 @@ module ActionView
|
||||
autoload :Helpers
|
||||
autoload :Base
|
||||
autoload :LookupContext
|
||||
autoload :Render
|
||||
autoload :PathSet, "action_view/paths"
|
||||
autoload :TestCase, "action_view/test_case"
|
||||
|
||||
autoload_under "renderer" do
|
||||
autoload :AbstractRenderer
|
||||
autoload :PartialRenderer
|
||||
autoload :TemplateRenderer
|
||||
end
|
||||
|
||||
autoload_under "render" do
|
||||
autoload :Layouts
|
||||
autoload :Partials
|
||||
autoload :Rendering
|
||||
end
|
||||
@@ -51,6 +57,7 @@ module ActionView
|
||||
autoload :Resolver
|
||||
autoload :PathResolver
|
||||
autoload :FileSystemResolver
|
||||
autoload :FallbackFileSystemResolver
|
||||
end
|
||||
|
||||
autoload_at "action_view/template/error" do
|
||||
|
||||
@@ -155,10 +155,7 @@ module ActionView #:nodoc:
|
||||
#
|
||||
# See the ActionView::Helpers::PrototypeHelper::JavaScriptGenerator::GeneratorMethods documentation for more details.
|
||||
class Base
|
||||
module Subclasses
|
||||
end
|
||||
|
||||
include Helpers, Rendering, Partials, Layouts, ::ERB::Util, Context
|
||||
include Helpers, Rendering, Partials, ::ERB::Util, Context
|
||||
|
||||
# Specify whether RJS responses should be wrapped in a try/catch block
|
||||
# that alert()s the caught exception (and then re-raises it).
|
||||
@@ -177,13 +174,12 @@ module ActionView #:nodoc:
|
||||
delegate :logger, :to => 'ActionController::Base', :allow_nil => true
|
||||
end
|
||||
|
||||
attr_accessor :base_path, :assigns, :template_extension, :lookup_context
|
||||
attr_internal :captures, :request, :controller, :template, :config
|
||||
attr_accessor :_template
|
||||
attr_internal :request, :controller, :config, :assigns, :lookup_context
|
||||
|
||||
delegate :find_template, :template_exists?, :formats, :formats=, :locale, :locale=,
|
||||
:view_paths, :view_paths=, :with_fallbacks, :update_details, :with_layout_format, :to => :lookup_context
|
||||
delegate :formats, :formats=, :locale, :locale=, :view_paths, :view_paths=, :to => :lookup_context
|
||||
|
||||
delegate :request_forgery_protection_token, :template, :params, :session, :cookies, :response, :headers,
|
||||
delegate :request_forgery_protection_token, :params, :session, :cookies, :response, :headers,
|
||||
:flash, :action_name, :controller_name, :to => :controller
|
||||
|
||||
delegate :logger, :to => :controller, :allow_nil => true
|
||||
@@ -198,26 +194,30 @@ module ActionView #:nodoc:
|
||||
end
|
||||
|
||||
def assign(new_assigns) # :nodoc:
|
||||
self.assigns = new_assigns.each { |key, value| instance_variable_set("@#{key}", value) }
|
||||
@_assigns = new_assigns.each { |key, value| instance_variable_set("@#{key}", value) }
|
||||
end
|
||||
|
||||
def initialize(lookup_context = nil, assigns_for_first_render = {}, controller = nil, formats = nil) #:nodoc:
|
||||
assign(assigns_for_first_render)
|
||||
self.helpers = self.class.helpers || Module.new
|
||||
|
||||
if @_controller = controller
|
||||
@_request = controller.request if controller.respond_to?(:request)
|
||||
end
|
||||
|
||||
@_config = controller && controller.respond_to?(:config) ? controller.config.inheritable_copy : {}
|
||||
self.helpers = Module.new unless self.class.helpers
|
||||
|
||||
@_config = {}
|
||||
@_content_for = Hash.new { |h,k| h[k] = ActiveSupport::SafeBuffer.new }
|
||||
@_virtual_path = nil
|
||||
@output_buffer = nil
|
||||
|
||||
@lookup_context = lookup_context.is_a?(ActionView::LookupContext) ?
|
||||
if @_controller = controller
|
||||
@_request = controller.request if controller.respond_to?(:request)
|
||||
@_config = controller.config.inheritable_copy if controller.respond_to?(:config)
|
||||
end
|
||||
|
||||
@_lookup_context = lookup_context.is_a?(ActionView::LookupContext) ?
|
||||
lookup_context : ActionView::LookupContext.new(lookup_context)
|
||||
@lookup_context.formats = formats if formats
|
||||
@_lookup_context.formats = formats if formats
|
||||
end
|
||||
|
||||
def store_content_for(key, value)
|
||||
@_content_for[key] = value
|
||||
end
|
||||
|
||||
def controller_path
|
||||
|
||||
@@ -152,7 +152,7 @@ module ActionView
|
||||
#
|
||||
# # Normally you'd calculate RELEASE_NUMBER at startup.
|
||||
# RELEASE_NUMBER = 12345
|
||||
# config.action_controller.asset_path_template = proc { |asset_path|
|
||||
# config.action_controller.asset_path = proc { |asset_path|
|
||||
# "/release-#{RELEASE_NUMBER}#{asset_path}"
|
||||
# }
|
||||
#
|
||||
@@ -292,9 +292,6 @@ module ActionView
|
||||
#
|
||||
# * = The application.js file is only referenced if it exists
|
||||
#
|
||||
# Though it's not really recommended practice, if you need to extend the default JavaScript set for any reason
|
||||
# (e.g., you're going to be using a certain .js file in every action), then take a look at the register_javascript_include_default method.
|
||||
#
|
||||
# You can also include all javascripts in the +javascripts+ directory using <tt>:all</tt> as the source:
|
||||
#
|
||||
# javascript_include_tag :all # =>
|
||||
|
||||
@@ -791,7 +791,7 @@ module ActionView
|
||||
options["incremental"] = true unless options.has_key?("incremental")
|
||||
end
|
||||
|
||||
InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("search", options)
|
||||
InstanceTag.new(object_name, method, self, options.delete("object")).to_input_field_tag("search", options)
|
||||
end
|
||||
|
||||
# Returns a text_field of type "tel".
|
||||
@@ -1015,19 +1015,14 @@ module ActionView
|
||||
|
||||
module ClassMethods
|
||||
def value(object, method_name)
|
||||
object.send method_name unless object.nil?
|
||||
object.send method_name if object
|
||||
end
|
||||
|
||||
def value_before_type_cast(object, method_name)
|
||||
unless object.nil?
|
||||
if object.respond_to?(method_name)
|
||||
object.send(method_name)
|
||||
# FIXME: this is AR dependent
|
||||
elsif object.respond_to?(method_name + "_before_type_cast")
|
||||
object.send(method_name + "_before_type_cast")
|
||||
else
|
||||
raise NoMethodError, "Model #{object.class} does not respond to #{method_name}"
|
||||
end
|
||||
object.respond_to?(method_name + "_before_type_cast") ?
|
||||
object.send(method_name + "_before_type_cast") :
|
||||
object.send(method_name)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -297,7 +297,6 @@ module ActionView
|
||||
def options_for_select(container, selected = nil)
|
||||
return container if String === container
|
||||
|
||||
container = container.to_a if Hash === container
|
||||
selected, disabled = extract_selected_and_disabled(selected).map do | r |
|
||||
Array.wrap(r).map(&:to_s)
|
||||
end
|
||||
|
||||
@@ -546,7 +546,7 @@ module ActionView
|
||||
end
|
||||
|
||||
def with_formats(*args)
|
||||
@context ? @context.update_details(:formats => args) { yield } : yield
|
||||
@context ? @context.lookup_context.update_details(:formats => args) { yield } : yield
|
||||
end
|
||||
|
||||
def javascript_object_for(object)
|
||||
|
||||
@@ -365,7 +365,7 @@ module ActionView
|
||||
# <% end %>
|
||||
def current_cycle(name = "default")
|
||||
cycle = get_cycle(name)
|
||||
cycle.current_value unless cycle.nil?
|
||||
cycle.current_value if cycle
|
||||
end
|
||||
|
||||
# Resets a cycle so that it starts from the first element the next time
|
||||
|
||||
@@ -45,8 +45,8 @@ module ActionView
|
||||
private
|
||||
def scope_key_by_partial(key)
|
||||
if key.to_s.first == "."
|
||||
if @_virtual_path
|
||||
@_virtual_path.gsub(%r{/_?}, ".") + key.to_s
|
||||
if (path = @_template && @_template.virtual_path)
|
||||
path.gsub(%r{/_?}, ".") + key.to_s
|
||||
else
|
||||
raise "Cannot use t(#{key.inspect}) shortcut because path is not available"
|
||||
end
|
||||
|
||||
@@ -476,39 +476,36 @@ module ActionView
|
||||
|
||||
html_options = html_options.stringify_keys
|
||||
encode = html_options.delete("encode").to_s
|
||||
cc, bcc, subject, body = html_options.delete("cc"), html_options.delete("bcc"), html_options.delete("subject"), html_options.delete("body")
|
||||
|
||||
extras = []
|
||||
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 = %w{ cc bcc body subject }.map { |item|
|
||||
option = html_options.delete(item) || next
|
||||
"#{item}=#{Rack::Utils.escape(option).gsub("+", "%20")}"
|
||||
}.compact
|
||||
extras = extras.empty? ? '' : '?' + html_escape(extras.join('&'))
|
||||
|
||||
email_address_obfuscated = email_address.dup
|
||||
email_address_obfuscated.gsub!(/@/, html_options.delete("replace_at")) if html_options.has_key?("replace_at")
|
||||
email_address_obfuscated.gsub!(/\./, html_options.delete("replace_dot")) if html_options.has_key?("replace_dot")
|
||||
email_address_obfuscated.gsub!(/@/, html_options.delete("replace_at")) if html_options.key?("replace_at")
|
||||
email_address_obfuscated.gsub!(/\./, html_options.delete("replace_dot")) if html_options.key?("replace_dot")
|
||||
|
||||
string = ''
|
||||
|
||||
if encode == "javascript"
|
||||
"document.write('#{content_tag("a", name || email_address_obfuscated.html_safe, html_options.merge("href" => "mailto:#{email_address}#{extras}".html_safe))}');".each_byte do |c|
|
||||
string << sprintf("%%%x", c)
|
||||
end
|
||||
case encode
|
||||
when "javascript"
|
||||
string =
|
||||
"document.write('#{content_tag("a", name || email_address_obfuscated.html_safe, html_options.merge("href" => "mailto:#{email_address}#{extras}".html_safe))}');".unpack('C*').map { |c|
|
||||
sprintf("%%%x", c)
|
||||
}.join
|
||||
"<script type=\"#{Mime::JS}\">eval(decodeURIComponent('#{string}'))</script>".html_safe
|
||||
elsif encode == "hex"
|
||||
email_address_encoded = ''
|
||||
email_address_obfuscated.each_byte do |c|
|
||||
email_address_encoded << sprintf("&#%d;", c)
|
||||
end
|
||||
when "hex"
|
||||
email_address_encoded = email_address_obfuscated.unpack('C*').map {|c|
|
||||
sprintf("&#%d;", c)
|
||||
}.join
|
||||
|
||||
protocol = 'mailto:'
|
||||
protocol.each_byte { |c| string << sprintf("&#%d;", c) }
|
||||
|
||||
email_address.each_byte do |c|
|
||||
string = 'mailto:'.unpack('C*').map { |c|
|
||||
sprintf("&#%d;", c)
|
||||
}.join + email_address.unpack('C*').map { |c|
|
||||
char = c.chr
|
||||
string << (char =~ /\w/ ? sprintf("%%%x", c) : char)
|
||||
end
|
||||
char =~ /\w/ ? sprintf("%%%x", c) : char
|
||||
}.join
|
||||
|
||||
content_tag "a", name || email_address_encoded.html_safe, html_options.merge("href" => "#{string}#{extras}".html_safe)
|
||||
else
|
||||
content_tag "a", name || email_address_obfuscated.html_safe, html_options.merge("href" => "mailto:#{email_address}#{extras}".html_safe)
|
||||
|
||||
@@ -10,7 +10,7 @@ module ActionView
|
||||
# this key is generated just once during the request, it speeds up all cache accesses.
|
||||
class LookupContext #:nodoc:
|
||||
mattr_accessor :fallbacks
|
||||
@@fallbacks = [FileSystemResolver.new(""), FileSystemResolver.new("/")]
|
||||
@@fallbacks = FallbackFileSystemResolver.instances
|
||||
|
||||
mattr_accessor :registered_details
|
||||
self.registered_details = []
|
||||
@@ -61,6 +61,7 @@ module ActionView
|
||||
def initialize(view_paths, details = {})
|
||||
@details, @details_key = { :handlers => default_handlers }, nil
|
||||
@frozen_formats, @skip_default_locale = false, false
|
||||
@cache = details.key?(:cache) ? details.delete(:cache) : true
|
||||
|
||||
self.view_paths = view_paths
|
||||
self.registered_detail_setters.each do |key, setter|
|
||||
@@ -77,17 +78,17 @@ module ActionView
|
||||
@view_paths = ActionView::Base.process_view_paths(paths)
|
||||
end
|
||||
|
||||
def find(name, prefix = nil, partial = false)
|
||||
@view_paths.find(*args_for_lookup(name, prefix, partial))
|
||||
def find(name, prefix = nil, partial = false, keys = [])
|
||||
@view_paths.find(*args_for_lookup(name, prefix, partial, keys))
|
||||
end
|
||||
alias :find_template :find
|
||||
|
||||
def find_all(name, prefix = nil, partial = false)
|
||||
@view_paths.find_all(*args_for_lookup(name, prefix, partial))
|
||||
def find_all(name, prefix = nil, partial = false, keys = [])
|
||||
@view_paths.find_all(*args_for_lookup(name, prefix, partial, keys))
|
||||
end
|
||||
|
||||
def exists?(name, prefix = nil, partial = false)
|
||||
@view_paths.exists?(*args_for_lookup(name, prefix, partial))
|
||||
def exists?(name, prefix = nil, partial = false, keys = [])
|
||||
@view_paths.exists?(*args_for_lookup(name, prefix, partial, keys))
|
||||
end
|
||||
alias :template_exists? :exists?
|
||||
|
||||
@@ -106,9 +107,9 @@ module ActionView
|
||||
|
||||
protected
|
||||
|
||||
def args_for_lookup(name, prefix, partial) #:nodoc:
|
||||
def args_for_lookup(name, prefix, partial, keys) #:nodoc:
|
||||
name, prefix = normalize_name(name, prefix)
|
||||
[name, prefix, partial || false, @details, details_key]
|
||||
[name, prefix, partial || false, @details, keys, details_key]
|
||||
end
|
||||
|
||||
# Support legacy foo.erb names even though we now ignore .erb
|
||||
@@ -130,10 +131,20 @@ module ActionView
|
||||
end
|
||||
|
||||
module Details
|
||||
attr_accessor :cache
|
||||
|
||||
# Calculate the details key. Remove the handlers from calculation to improve performance
|
||||
# since the user cannot modify it explicitly.
|
||||
def details_key #:nodoc:
|
||||
@details_key ||= DetailsKey.get(@details)
|
||||
@details_key ||= DetailsKey.get(@details) if @cache
|
||||
end
|
||||
|
||||
# Temporary skip passing the details_key forward.
|
||||
def disable_cache
|
||||
old_value, @cache = @cache, false
|
||||
yield
|
||||
ensure
|
||||
@cache = old_value
|
||||
end
|
||||
|
||||
# Freeze the current formats in the lookup context. By freezing them, you are guaranteeing
|
||||
|
||||
@@ -10,8 +10,8 @@ module ActionView #:nodoc:
|
||||
METHOD
|
||||
end
|
||||
|
||||
def find(path, prefix = nil, partial = false, details = {}, key = nil)
|
||||
template = find_all(path, prefix, partial, details, key).first
|
||||
def find(path, prefix = nil, partial = false, details = {}, keys = [], key = nil)
|
||||
template = find_all(path, prefix, partial, details, keys, key).first
|
||||
raise MissingTemplate.new(self, "#{prefix}/#{path}", details, partial) unless template
|
||||
template
|
||||
end
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
module ActionView
|
||||
# = Action View Layouts
|
||||
module Layouts
|
||||
# Returns the contents that are yielded to a layout, given a name or a block.
|
||||
#
|
||||
# You can think of a layout as a method that is called with a block. If the user calls
|
||||
# <tt>yield :some_name</tt>, the block, by default, returns <tt>content_for(:some_name)</tt>.
|
||||
# If the user calls simply +yield+, the default block returns <tt>content_for(:layout)</tt>.
|
||||
#
|
||||
# The user can override this default by passing a block to the layout:
|
||||
#
|
||||
# # The template
|
||||
# <%= render :layout => "my_layout" do %>
|
||||
# Content
|
||||
# <% end %>
|
||||
#
|
||||
# # The layout
|
||||
# <html>
|
||||
# <%= yield %>
|
||||
# </html>
|
||||
#
|
||||
# In this case, instead of the default block, which would return <tt>content_for(:layout)</tt>,
|
||||
# this method returns the block that was passed in to <tt>render :layout</tt>, and the response
|
||||
# would be
|
||||
#
|
||||
# <html>
|
||||
# Content
|
||||
# </html>
|
||||
#
|
||||
# Finally, the block can take block arguments, which can be passed in by +yield+:
|
||||
#
|
||||
# # The template
|
||||
# <%= render :layout => "my_layout" do |customer| %>
|
||||
# Hello <%= customer.name %>
|
||||
# <% end %>
|
||||
#
|
||||
# # The layout
|
||||
# <html>
|
||||
# <%= yield Struct.new(:name).new("David") %>
|
||||
# </html>
|
||||
#
|
||||
# In this case, the layout would receive the block passed into <tt>render :layout</tt>,
|
||||
# and the struct specified would be passed into the block as an argument. The result
|
||||
# would be
|
||||
#
|
||||
# <html>
|
||||
# Hello David
|
||||
# </html>
|
||||
#
|
||||
def _layout_for(*args, &block) #:nodoc:
|
||||
name = args.first
|
||||
|
||||
if name.is_a?(Symbol)
|
||||
@_content_for[name].html_safe
|
||||
elsif block
|
||||
capture(*args, &block)
|
||||
else
|
||||
@_content_for[:layout].html_safe
|
||||
end
|
||||
end
|
||||
|
||||
# This is the method which actually finds the layout using details in the lookup
|
||||
# context object. If no layout is found, it checks if at least a layout with
|
||||
# the given name exists across all details before raising the error.
|
||||
def find_layout(layout)
|
||||
begin
|
||||
with_layout_format do
|
||||
layout =~ /^\// ?
|
||||
with_fallbacks { find_template(layout) } : find_template(layout)
|
||||
end
|
||||
rescue ActionView::MissingTemplate => e
|
||||
update_details(:formats => nil) do
|
||||
raise unless template_exists?(layout)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Contains the logic that actually renders the layout.
|
||||
def _render_layout(layout, locals, &block) #:nodoc:
|
||||
layout.render(self, locals){ |*name| _layout_for(*name, &block) }
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -210,165 +210,12 @@ module ActionView
|
||||
# <%- end -%>
|
||||
# <% end %>
|
||||
module Partials
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
class PartialRenderer
|
||||
PARTIAL_NAMES = Hash.new {|h,k| h[k] = {} }
|
||||
|
||||
def initialize(view_context, options, block)
|
||||
@view = view_context
|
||||
@partial_names = PARTIAL_NAMES[@view.controller.class.name]
|
||||
|
||||
setup(options, block)
|
||||
end
|
||||
|
||||
def setup(options, block)
|
||||
partial = options[:partial]
|
||||
|
||||
@options = options
|
||||
@locals = options[:locals] || {}
|
||||
@block = block
|
||||
|
||||
if String === partial
|
||||
@object = options[:object]
|
||||
@path = partial
|
||||
@collection = collection
|
||||
else
|
||||
@object = partial
|
||||
|
||||
if @collection = collection
|
||||
paths = @collection_paths = @collection.map { |o| partial_path(o) }
|
||||
@path = paths.uniq.size == 1 ? paths.first : nil
|
||||
else
|
||||
@path = partial_path
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def render
|
||||
identifier = ((@template = find_template) ? @template.identifier : @path)
|
||||
|
||||
if @collection
|
||||
ActiveSupport::Notifications.instrument("render_collection.action_view",
|
||||
:identifier => identifier || "collection", :count => @collection.size) do
|
||||
render_collection
|
||||
end
|
||||
else
|
||||
content = ActiveSupport::Notifications.instrument("render_partial.action_view",
|
||||
:identifier => identifier) do
|
||||
render_partial
|
||||
end
|
||||
|
||||
if !@block && (layout = @options[:layout])
|
||||
content = @view._render_layout(find_template(layout), @locals){ content }
|
||||
end
|
||||
|
||||
content
|
||||
end
|
||||
end
|
||||
|
||||
def render_collection
|
||||
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.join(spacer).html_safe
|
||||
end
|
||||
|
||||
def collection_with_template(template = @template)
|
||||
segments, locals, template = [], @locals, @template
|
||||
|
||||
if @options[:as]
|
||||
as = @options[:as]
|
||||
counter = "#{as}_counter".to_sym
|
||||
else
|
||||
as = template.variable_name
|
||||
counter = template.counter_name
|
||||
end
|
||||
|
||||
locals[counter] = -1
|
||||
|
||||
@collection.each do |object|
|
||||
locals[counter] += 1
|
||||
locals[as] = object
|
||||
segments << template.render(@view, locals)
|
||||
end
|
||||
|
||||
segments
|
||||
end
|
||||
|
||||
def collection_without_template(collection_paths = @collection_paths)
|
||||
segments, locals = [], @locals
|
||||
index, template = -1, nil
|
||||
|
||||
if @options[:as]
|
||||
as = @options[:as]
|
||||
counter = "#{as}_counter"
|
||||
end
|
||||
|
||||
@collection.each_with_index do |object, i|
|
||||
template = find_template(collection_paths[i])
|
||||
locals[as || template.variable_name] = object
|
||||
locals[counter || template.counter_name] = (index += 1)
|
||||
|
||||
segments << template.render(@view, locals)
|
||||
end
|
||||
|
||||
@template = template
|
||||
segments
|
||||
end
|
||||
|
||||
def render_partial(object = @object)
|
||||
locals, view, template = @locals, @view, @template
|
||||
|
||||
object ||= locals[template.variable_name]
|
||||
locals[@options[:as] || template.variable_name] = object
|
||||
|
||||
template.render(view, locals) do |*name|
|
||||
view._layout_for(*name, &@block)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def collection
|
||||
if @object.respond_to?(:to_ary)
|
||||
@object
|
||||
elsif @options.key?(:collection)
|
||||
@options[:collection] || []
|
||||
end
|
||||
end
|
||||
|
||||
def find_template(path=@path)
|
||||
return path unless path.is_a?(String)
|
||||
prefix = @view.controller_path unless path.include?(?/)
|
||||
@view.find_template(path, prefix, true)
|
||||
end
|
||||
|
||||
def partial_path(object = @object)
|
||||
@partial_names[object.class.name] ||= begin
|
||||
object = object.to_model if object.respond_to?(:to_model)
|
||||
|
||||
object.class.model_name.partial_path.dup.tap do |partial|
|
||||
path = @view.controller_path
|
||||
partial.insert(0, "#{File.dirname(path)}/") if partial.include?(?/) && path.include?(?/)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def _render_partial(options, &block) #:nodoc:
|
||||
if defined?(@renderer)
|
||||
@renderer.setup(options, block)
|
||||
else
|
||||
@renderer = PartialRenderer.new(self, options, block)
|
||||
end
|
||||
|
||||
@renderer.render
|
||||
_partial_renderer.setup(options, block).render
|
||||
end
|
||||
|
||||
def _partial_renderer #:nodoc:
|
||||
@_partial_renderer ||= PartialRenderer.new(self)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,6 +10,7 @@ module ActionView
|
||||
# * <tt>:file</tt> - Renders an explicit template file (this used to be the old default), add :locals to pass in those.
|
||||
# * <tt>:inline</tt> - Renders an inline template similar to how it's done in the controller.
|
||||
# * <tt>:text</tt> - Renders the text passed in out.
|
||||
# * <tt>:once</tt> - Accepts a string or an array of strings and Rails will ensure they each of them are rendered just once.
|
||||
#
|
||||
# If no options hash is passed or :update specified, the default is to render a partial and use the second parameter
|
||||
# as the locals hash.
|
||||
@@ -20,10 +21,10 @@ module ActionView
|
||||
_render_partial(options.merge(:partial => options[:layout]), &block)
|
||||
elsif options.key?(:partial)
|
||||
_render_partial(options)
|
||||
elsif options.key?(:once)
|
||||
_render_once(options)
|
||||
else
|
||||
template = _determine_template(options)
|
||||
lookup_context.freeze_formats(template.formats, true)
|
||||
_render_template(template, options[:layout], options)
|
||||
_render_template(options)
|
||||
end
|
||||
when :update
|
||||
update_page(&block)
|
||||
@@ -32,36 +33,74 @@ module ActionView
|
||||
end
|
||||
end
|
||||
|
||||
# Determine the template to be rendered using the given options.
|
||||
def _determine_template(options) #:nodoc:
|
||||
if options.key?(:inline)
|
||||
handler = Template.handler_class_for_extension(options[:type] || "erb")
|
||||
Template.new(options[:inline], "inline template", handler, {})
|
||||
elsif options.key?(:text)
|
||||
Template::Text.new(options[:text], formats.try(:first))
|
||||
elsif options.key?(:file)
|
||||
with_fallbacks { find_template(options[:file], options[:prefix]) }
|
||||
elsif options.key?(:template)
|
||||
options[:template].respond_to?(:render) ?
|
||||
options[:template] : find_template(options[:template], options[:prefix])
|
||||
# Returns the contents that are yielded to a layout, given a name or a block.
|
||||
#
|
||||
# You can think of a layout as a method that is called with a block. If the user calls
|
||||
# <tt>yield :some_name</tt>, the block, by default, returns <tt>content_for(:some_name)</tt>.
|
||||
# If the user calls simply +yield+, the default block returns <tt>content_for(:layout)</tt>.
|
||||
#
|
||||
# The user can override this default by passing a block to the layout:
|
||||
#
|
||||
# # The template
|
||||
# <%= render :layout => "my_layout" do %>
|
||||
# Content
|
||||
# <% end %>
|
||||
#
|
||||
# # The layout
|
||||
# <html>
|
||||
# <%= yield %>
|
||||
# </html>
|
||||
#
|
||||
# In this case, instead of the default block, which would return <tt>content_for(:layout)</tt>,
|
||||
# this method returns the block that was passed in to <tt>render :layout</tt>, and the response
|
||||
# would be
|
||||
#
|
||||
# <html>
|
||||
# Content
|
||||
# </html>
|
||||
#
|
||||
# Finally, the block can take block arguments, which can be passed in by +yield+:
|
||||
#
|
||||
# # The template
|
||||
# <%= render :layout => "my_layout" do |customer| %>
|
||||
# Hello <%= customer.name %>
|
||||
# <% end %>
|
||||
#
|
||||
# # The layout
|
||||
# <html>
|
||||
# <%= yield Struct.new(:name).new("David") %>
|
||||
# </html>
|
||||
#
|
||||
# In this case, the layout would receive the block passed into <tt>render :layout</tt>,
|
||||
# and the struct specified would be passed into the block as an argument. The result
|
||||
# would be
|
||||
#
|
||||
# <html>
|
||||
# Hello David
|
||||
# </html>
|
||||
#
|
||||
def _layout_for(*args, &block)
|
||||
name = args.first
|
||||
|
||||
if name.is_a?(Symbol)
|
||||
@_content_for[name].html_safe
|
||||
elsif block
|
||||
capture(*args, &block)
|
||||
else
|
||||
@_content_for[:layout].html_safe
|
||||
end
|
||||
end
|
||||
|
||||
# Renders the given template. An string representing the layout can be
|
||||
# supplied as well.
|
||||
def _render_template(template, layout = nil, options = {}) #:nodoc:
|
||||
locals = options[:locals] || {}
|
||||
layout = find_layout(layout) if layout
|
||||
def _render_once(options) #:nodoc:
|
||||
_template_renderer.render_once(options)
|
||||
end
|
||||
|
||||
ActiveSupport::Notifications.instrument("render_template.action_view",
|
||||
:identifier => template.identifier, :layout => layout.try(:virtual_path)) do
|
||||
def _render_template(options) #:nodoc:
|
||||
_template_renderer.render(options)
|
||||
end
|
||||
|
||||
content = template.render(self, locals) { |*name| _layout_for(*name) }
|
||||
@_content_for[:layout] = content if layout
|
||||
|
||||
content = _render_layout(layout, locals) if layout
|
||||
content
|
||||
end
|
||||
def _template_renderer #:nodoc:
|
||||
@_template_renderer ||= TemplateRenderer.new(self)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
36
actionpack/lib/action_view/renderer/abstract_renderer.rb
Normal file
36
actionpack/lib/action_view/renderer/abstract_renderer.rb
Normal file
@@ -0,0 +1,36 @@
|
||||
module ActionView
|
||||
class AbstractRenderer #:nodoc:
|
||||
attr_reader :vew, :lookup_context
|
||||
|
||||
delegate :find_template, :template_exists?, :with_fallbacks, :update_details,
|
||||
:with_layout_format, :formats, :to => :lookup_context
|
||||
|
||||
def initialize(view)
|
||||
@view = view
|
||||
@lookup_context = view.lookup_context
|
||||
end
|
||||
|
||||
def render
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
# Checks if the given path contains a format and if so, change
|
||||
# the lookup context to take this new format into account.
|
||||
def wrap_formats(value)
|
||||
return yield unless value.is_a?(String)
|
||||
@@formats_regexp ||= /\.(#{Mime::SET.symbols.join('|')})$/
|
||||
|
||||
if value.sub!(@@formats_regexp, "")
|
||||
update_details(:formats => [$1.to_sym]){ yield }
|
||||
else
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def instrument(name, options={})
|
||||
ActiveSupport::Notifications.instrument("render_#{name}.action_view", options){ yield }
|
||||
end
|
||||
end
|
||||
end
|
||||
166
actionpack/lib/action_view/renderer/partial_renderer.rb
Normal file
166
actionpack/lib/action_view/renderer/partial_renderer.rb
Normal file
@@ -0,0 +1,166 @@
|
||||
require 'action_view/renderer/abstract_renderer'
|
||||
|
||||
module ActionView
|
||||
class PartialRenderer < AbstractRenderer #:nodoc:
|
||||
PARTIAL_NAMES = Hash.new {|h,k| h[k] = {} }
|
||||
|
||||
def initialize(view)
|
||||
super
|
||||
@partial_names = PARTIAL_NAMES[@view.controller.class.name]
|
||||
end
|
||||
|
||||
def setup(options, block)
|
||||
partial = options[:partial]
|
||||
|
||||
@options = options
|
||||
@locals = options[:locals] || {}
|
||||
@block = block
|
||||
|
||||
if String === partial
|
||||
@object = options[:object]
|
||||
@path = partial
|
||||
@collection = collection
|
||||
else
|
||||
@object = partial
|
||||
|
||||
if @collection = collection_from_object || collection
|
||||
paths = @collection_data = @collection.map { |o| partial_path(o) }
|
||||
@path = paths.uniq.size == 1 ? paths.first : nil
|
||||
else
|
||||
@path = partial_path
|
||||
end
|
||||
end
|
||||
|
||||
if @path
|
||||
@variable, @variable_counter = retrieve_variable(@path)
|
||||
else
|
||||
paths.map! { |path| retrieve_variable(path).unshift(path) }
|
||||
end
|
||||
|
||||
self
|
||||
end
|
||||
|
||||
def render
|
||||
wrap_formats(@path) do
|
||||
identifier = ((@template = find_partial) ? @template.identifier : @path)
|
||||
|
||||
if @collection
|
||||
instrument(:collection, :identifier => identifier || "collection", :count => @collection.size) do
|
||||
render_collection
|
||||
end
|
||||
else
|
||||
instrument(:partial, :identifier => identifier) do
|
||||
render_partial
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def render_collection
|
||||
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.join(spacer).html_safe
|
||||
end
|
||||
|
||||
def render_partial
|
||||
locals, view, block = @locals, @view, @block
|
||||
object, as = @object, @variable
|
||||
|
||||
if !block && (layout = @options[:layout])
|
||||
layout = find_template(layout)
|
||||
end
|
||||
|
||||
object ||= locals[as]
|
||||
locals[as] = object
|
||||
|
||||
content = @template.render(view, locals) do |*name|
|
||||
view._layout_for(*name, &block)
|
||||
end
|
||||
|
||||
content = layout.render(view, locals){ content } if layout
|
||||
content
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def collection
|
||||
if @options.key?(:collection)
|
||||
@options[:collection] || []
|
||||
end
|
||||
end
|
||||
|
||||
def collection_from_object
|
||||
if @object.respond_to?(:to_ary)
|
||||
@object
|
||||
end
|
||||
end
|
||||
|
||||
def find_partial
|
||||
if path = @path
|
||||
locals = @locals.keys
|
||||
locals << @variable
|
||||
locals << @variable_counter if @collection
|
||||
find_template(path, locals)
|
||||
end
|
||||
end
|
||||
|
||||
def find_template(path=@path, locals=@locals.keys)
|
||||
prefix = @view.controller_path unless path.include?(?/)
|
||||
@lookup_context.find_template(path, prefix, true, locals)
|
||||
end
|
||||
|
||||
def collection_with_template
|
||||
segments, locals, template = [], @locals, @template
|
||||
as, counter = @variable, @variable_counter
|
||||
|
||||
locals[counter] = -1
|
||||
|
||||
@collection.each do |object|
|
||||
locals[counter] += 1
|
||||
locals[as] = object
|
||||
segments << template.render(@view, locals)
|
||||
end
|
||||
|
||||
segments
|
||||
end
|
||||
|
||||
def collection_without_template
|
||||
segments, locals, collection_data = [], @locals, @collection_data
|
||||
index, template, cache = -1, nil, {}
|
||||
keys = @locals.keys
|
||||
|
||||
@collection.each_with_index do |object, i|
|
||||
path, *data = collection_data[i]
|
||||
template = (cache[path] ||= find_template(path, keys + data))
|
||||
locals[data[0]] = object
|
||||
locals[data[1]] = (index += 1)
|
||||
segments << template.render(@view, locals)
|
||||
end
|
||||
|
||||
@template = template
|
||||
segments
|
||||
end
|
||||
|
||||
def partial_path(object = @object)
|
||||
@partial_names[object.class.name] ||= begin
|
||||
object = object.to_model if object.respond_to?(:to_model)
|
||||
|
||||
object.class.model_name.partial_path.dup.tap do |partial|
|
||||
path = @view.controller_path
|
||||
partial.insert(0, "#{File.dirname(path)}/") if partial.include?(?/) && path.include?(?/)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def retrieve_variable(path)
|
||||
variable = @options[:as] || path[%r'_?(\w+)(\.\w+)*$', 1].to_sym
|
||||
variable_counter = :"#{variable}_counter" if @collection
|
||||
[variable, variable_counter]
|
||||
end
|
||||
end
|
||||
end
|
||||
97
actionpack/lib/action_view/renderer/template_renderer.rb
Normal file
97
actionpack/lib/action_view/renderer/template_renderer.rb
Normal file
@@ -0,0 +1,97 @@
|
||||
require 'set'
|
||||
require 'active_support/core_ext/object/try'
|
||||
require 'active_support/core_ext/array/wrap'
|
||||
require 'action_view/renderer/abstract_renderer'
|
||||
|
||||
module ActionView
|
||||
class TemplateRenderer < AbstractRenderer #:nodoc:
|
||||
attr_reader :rendered
|
||||
|
||||
def initialize(view)
|
||||
super
|
||||
@rendered = Set.new
|
||||
end
|
||||
|
||||
def render(options)
|
||||
wrap_formats(options[:template] || options[:file]) do
|
||||
template = determine_template(options)
|
||||
render_template(template, options[:layout], options[:locals])
|
||||
end
|
||||
end
|
||||
|
||||
def render_once(options)
|
||||
paths, locals = options[:once], options[:locals] || {}
|
||||
layout, keys, prefix = options[:layout], locals.keys, options[:prefix]
|
||||
|
||||
raise "render :once expects a String or an Array to be given" unless paths
|
||||
|
||||
render_with_layout(layout, locals) do
|
||||
contents = []
|
||||
Array.wrap(paths).each do |path|
|
||||
template = find_template(path, prefix, false, keys)
|
||||
contents << render_template(template, nil, locals) if @rendered.add?(template)
|
||||
end
|
||||
contents.join("\n")
|
||||
end
|
||||
end
|
||||
|
||||
# Determine the template to be rendered using the given options.
|
||||
def determine_template(options) #:nodoc:
|
||||
keys = options[:locals].try(:keys) || []
|
||||
|
||||
if options.key?(:text)
|
||||
Template::Text.new(options[:text], formats.try(:first))
|
||||
elsif options.key?(:file)
|
||||
with_fallbacks { find_template(options[:file], options[:prefix], false, keys) }
|
||||
elsif options.key?(:inline)
|
||||
handler = Template.handler_class_for_extension(options[:type] || "erb")
|
||||
Template::Inline.new(options[:inline], handler, :locals => keys)
|
||||
elsif options.key?(:template)
|
||||
options[:template].respond_to?(:render) ?
|
||||
options[:template] : find_template(options[:template], options[:prefix], false, keys)
|
||||
end
|
||||
end
|
||||
|
||||
# Renders the given template. An string representing the layout can be
|
||||
# supplied as well.
|
||||
def render_template(template, layout_name = nil, locals = {}) #:nodoc:
|
||||
lookup_context.freeze_formats(template.formats, true)
|
||||
view, locals = @view, locals || {}
|
||||
|
||||
render_with_layout(layout_name, locals) do |layout|
|
||||
instrument(:template, :identifier => template.identifier, :layout => layout.try(:virtual_path)) do
|
||||
template.render(view, locals) { |*name| view._layout_for(*name) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def render_with_layout(path, locals) #:nodoc:
|
||||
layout = path && find_layout(path, locals.keys)
|
||||
content = yield(layout)
|
||||
|
||||
if layout
|
||||
view = @view
|
||||
view.store_content_for(:layout, content)
|
||||
layout.render(view, locals){ |*name| view._layout_for(*name) }
|
||||
else
|
||||
content
|
||||
end
|
||||
end
|
||||
|
||||
# This is the method which actually finds the layout using details in the lookup
|
||||
# context object. If no layout is found, it checks if at least a layout with
|
||||
# the given name exists across all details before raising the error.
|
||||
def find_layout(layout, keys)
|
||||
begin
|
||||
with_layout_format do
|
||||
layout =~ /^\// ?
|
||||
with_fallbacks { find_template(layout, nil, false, keys) } : find_template(layout, nil, false, keys)
|
||||
end
|
||||
rescue ActionView::MissingTemplate => e
|
||||
update_details(:formats => nil) do
|
||||
raise unless template_exists?(layout)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -93,14 +93,18 @@ module ActionView
|
||||
autoload :Error
|
||||
autoload :Handler
|
||||
autoload :Handlers
|
||||
autoload :Inline
|
||||
autoload :Text
|
||||
end
|
||||
|
||||
extend Template::Handlers
|
||||
|
||||
attr_reader :source, :identifier, :handler, :virtual_path, :formats,
|
||||
:original_encoding
|
||||
attr_accessor :locals, :formats, :virtual_path
|
||||
|
||||
attr_reader :source, :identifier, :handler, :original_encoding, :updated_at
|
||||
|
||||
# This finalizer is needed (and exactly with a proc inside another proc)
|
||||
# otherwise templates leak in development.
|
||||
Finalizer = proc do |method_name, mod|
|
||||
proc do
|
||||
mod.module_eval do
|
||||
@@ -110,49 +114,83 @@ module ActionView
|
||||
end
|
||||
|
||||
def initialize(source, identifier, handler, details)
|
||||
@source = source
|
||||
@identifier = identifier
|
||||
@handler = handler
|
||||
@original_encoding = nil
|
||||
@method_names = {}
|
||||
format = details[:format] || (handler.default_format if handler.respond_to?(:default_format))
|
||||
|
||||
format = details[:format] || :html
|
||||
@formats = Array.wrap(format).map(&:to_sym)
|
||||
@virtual_path = details[:virtual_path].try(:sub, ".#{format}", "")
|
||||
@source = source
|
||||
@identifier = identifier
|
||||
@handler = handler
|
||||
@compiled = false
|
||||
@original_encoding = nil
|
||||
@locals = details[:locals] || []
|
||||
@virtual_path = details[:virtual_path]
|
||||
@updated_at = details[:updated_at] || Time.now
|
||||
@formats = Array.wrap(format).map(&:to_sym)
|
||||
end
|
||||
|
||||
# Render a template. If the template was not compiled yet, it is done
|
||||
# exactly before rendering.
|
||||
#
|
||||
# This method is instrumented as "!render_template.action_view". Notice that
|
||||
# we use a bang in this instrumentation because you don't want to
|
||||
# consume this in production. This is only slow if it's being listened to.
|
||||
def render(view, locals, &block)
|
||||
# Notice that we use a bang in this instrumentation because you don't want to
|
||||
# consume this in production. This is only slow if it's being listened to.
|
||||
old_template, view._template = view._template, self
|
||||
ActiveSupport::Notifications.instrument("!render_template.action_view", :virtual_path => @virtual_path) do
|
||||
if view.is_a?(ActionView::CompiledTemplates)
|
||||
mod = ActionView::CompiledTemplates
|
||||
else
|
||||
mod = view.singleton_class
|
||||
end
|
||||
|
||||
method_name = compile(locals, view, mod)
|
||||
compile!(view)
|
||||
view.send(method_name, locals, &block)
|
||||
end
|
||||
rescue Exception => e
|
||||
if e.is_a?(Template::Error)
|
||||
e.sub_template_of(self)
|
||||
raise e
|
||||
else
|
||||
raise Template::Error.new(self, view.respond_to?(:assigns) ? view.assigns : {}, e)
|
||||
end
|
||||
handle_render_error(view, e)
|
||||
ensure
|
||||
view._template = old_template
|
||||
end
|
||||
|
||||
def mime_type
|
||||
@mime_type ||= Mime::Type.lookup_by_extension(@formats.first.to_s) if @formats.first
|
||||
end
|
||||
|
||||
def variable_name
|
||||
@variable_name ||= @virtual_path[%r'_?(\w+)(\.\w+)*$', 1].to_sym
|
||||
# Receives a view object and return a template similar to self by using @virtual_path.
|
||||
#
|
||||
# This method is useful if you have a template object but it does not contain its source
|
||||
# anymore since it was already compiled. In such cases, all you need to do is to call
|
||||
# refresh passing in the view object.
|
||||
#
|
||||
# Notice this method raises an error if the template to be refreshed does not have a
|
||||
# virtual path set (true just for inline templates).
|
||||
def refresh(view)
|
||||
raise "A template needs to have a virtual path in order to be refreshed" unless @virtual_path
|
||||
lookup = view.lookup_context
|
||||
pieces = @virtual_path.split("/")
|
||||
name = pieces.pop
|
||||
partial = !!name.sub!(/^_/, "")
|
||||
lookup.disable_cache do
|
||||
lookup.find_template(name, pieces.join, partial, @locals)
|
||||
end
|
||||
end
|
||||
|
||||
def counter_name
|
||||
@counter_name ||= "#{variable_name}_counter".to_sym
|
||||
# Expires this template by setting his updated_at date to Jan 1st, 1970.
|
||||
def expire!
|
||||
@updated_at = Time.utc(1970)
|
||||
end
|
||||
|
||||
# Receives a view context and renders a template exactly like self by using
|
||||
# the @virtual_path. It raises an error if no @virtual_path was given.
|
||||
def rerender(view)
|
||||
raise "A template needs to have a virtual path in order to be rerendered" unless @virtual_path
|
||||
name = @virtual_path.dup
|
||||
if name.sub!(/(^|\/)_([^\/]*)$/, '\1\2')
|
||||
view.render :partial => name
|
||||
else
|
||||
view.render :template => @virtual_path
|
||||
end
|
||||
end
|
||||
|
||||
def hash
|
||||
identifier.hash
|
||||
end
|
||||
|
||||
def eql?(other)
|
||||
other.is_a?(Template) && other.identifier == identifier
|
||||
end
|
||||
|
||||
def inspect
|
||||
@@ -164,7 +202,27 @@ module ActionView
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
protected
|
||||
|
||||
# Compile a template. This method ensures a template is compiled
|
||||
# just once and removes the source after it is compiled.
|
||||
def compile!(view) #:nodoc:
|
||||
return if @compiled
|
||||
|
||||
if view.is_a?(ActionView::CompiledTemplates)
|
||||
mod = ActionView::CompiledTemplates
|
||||
else
|
||||
mod = view.singleton_class
|
||||
end
|
||||
|
||||
compile(view, mod)
|
||||
|
||||
# Just discard the source if we have a virtual path. This
|
||||
# means we can get the template back.
|
||||
@source = nil if @virtual_path
|
||||
@compiled = true
|
||||
end
|
||||
|
||||
# Among other things, this method is responsible for properly setting
|
||||
# the encoding of the source. Until this point, we assume that the
|
||||
# source is BINARY data. If no additional information is supplied,
|
||||
@@ -185,11 +243,8 @@ module ActionView
|
||||
# encode the source into Encoding.default_internal. In general,
|
||||
# this means that templates will be UTF-8 inside of Rails,
|
||||
# regardless of the original source encoding.
|
||||
def compile(locals, view, mod)
|
||||
method_name = build_method_name(locals)
|
||||
return method_name if view.respond_to?(method_name)
|
||||
|
||||
locals_code = locals.keys.map! { |key| "#{key} = local_assigns[:#{key}];" }.join
|
||||
def compile(view, mod) #:nodoc:
|
||||
method_name = self.method_name
|
||||
|
||||
if source.encoding_aware?
|
||||
# Look for # encoding: *. If we find one, we'll encode the
|
||||
@@ -229,9 +284,9 @@ module ActionView
|
||||
# encoding of the code
|
||||
source = <<-end_src
|
||||
def #{method_name}(local_assigns)
|
||||
_old_virtual_path, @_virtual_path = @_virtual_path, #{@virtual_path.inspect};_old_output_buffer = @output_buffer;#{locals_code};#{code}
|
||||
_old_output_buffer = @output_buffer;#{locals_code};#{code}
|
||||
ensure
|
||||
@_virtual_path, @output_buffer = _old_virtual_path, _old_output_buffer
|
||||
@output_buffer = _old_output_buffer
|
||||
end
|
||||
end_src
|
||||
|
||||
@@ -254,8 +309,6 @@ module ActionView
|
||||
begin
|
||||
mod.module_eval(source, identifier, 0)
|
||||
ObjectSpace.define_finalizer(self, Finalizer[method_name, mod])
|
||||
|
||||
method_name
|
||||
rescue Exception => e # errors from template code
|
||||
if logger = (view && view.logger)
|
||||
logger.debug "ERROR: compiling #{method_name} RAISED #{e}"
|
||||
@@ -267,12 +320,27 @@ module ActionView
|
||||
end
|
||||
end
|
||||
|
||||
def build_method_name(locals)
|
||||
@method_names[locals.keys.hash] ||= "_#{identifier_method_name}__#{@identifier.hash}_#{__id__}_#{locals.keys.hash}".gsub('-', "_")
|
||||
def handle_render_error(view, e) #:nodoc:
|
||||
if e.is_a?(Template::Error)
|
||||
e.sub_template_of(self)
|
||||
raise e
|
||||
else
|
||||
assigns = view.respond_to?(:assigns) ? view.assigns : {}
|
||||
template = @virtual_path ? refresh(view) : self
|
||||
raise Template::Error.new(template, assigns, e)
|
||||
end
|
||||
end
|
||||
|
||||
def identifier_method_name
|
||||
@identifier_method_name ||= inspect.gsub(/[^a-z_]/, '_')
|
||||
def locals_code #:nodoc:
|
||||
@locals.map { |key| "#{key} = local_assigns[:#{key}];" }.join
|
||||
end
|
||||
|
||||
def method_name #:nodoc:
|
||||
@method_name ||= "_#{identifier_method_name}__#{@identifier.hash}_#{__id__}".gsub('-', "_")
|
||||
end
|
||||
|
||||
def identifier_method_name #:nodoc:
|
||||
inspect.gsub(/[^a-z_]/, '_')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -43,8 +43,9 @@ module ActionView
|
||||
end
|
||||
|
||||
class Template
|
||||
# The Template::Error exception is raised when the compilation of the template fails. This exception then gathers a
|
||||
# bunch of intimate details and uses it to report a very precise exception message.
|
||||
# The Template::Error exception is raised when the compilation or rendering of the template
|
||||
# fails. This exception then gathers a bunch of intimate details and uses it to report a
|
||||
# precise exception message.
|
||||
class Error < ActionViewError #:nodoc:
|
||||
SOURCE_CODE_RADIUS = 3
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require "action_dispatch/http/mime_type"
|
||||
require 'action_dispatch/http/mime_type'
|
||||
require 'active_support/core_ext/class/attribute'
|
||||
|
||||
# Legacy TemplateHandler stub
|
||||
@@ -7,6 +7,8 @@ module ActionView
|
||||
module Handlers #:nodoc:
|
||||
module Compilable
|
||||
def self.included(base)
|
||||
ActiveSupport::Deprecation.warn "Including Compilable in your template handler is deprecated. " <<
|
||||
"All the API your template handler needs to implement is to respond to #call."
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
|
||||
@@ -26,6 +28,12 @@ module ActionView
|
||||
class_attribute :default_format
|
||||
self.default_format = Mime::HTML
|
||||
|
||||
def self.inherited(base)
|
||||
ActiveSupport::Deprecation.warn "Inheriting from ActionView::Template::Handler is deprecated. " <<
|
||||
"All the API your template handler needs to implement is to respond to #call."
|
||||
super
|
||||
end
|
||||
|
||||
def self.call(template)
|
||||
raise "Need to implement #{self.class.name}#call(template)"
|
||||
end
|
||||
|
||||
@@ -7,13 +7,9 @@ module ActionView #:nodoc:
|
||||
autoload :Builder, 'action_view/template/handlers/builder'
|
||||
|
||||
def self.extended(base)
|
||||
base.register_default_template_handler :erb, ERB
|
||||
base.register_template_handler :rjs, RJS
|
||||
base.register_template_handler :builder, Builder
|
||||
|
||||
# TODO: Depreciate old template extensions
|
||||
base.register_template_handler :rhtml, ERB
|
||||
base.register_template_handler :rxml, Builder
|
||||
base.register_default_template_handler :erb, ERB.new
|
||||
base.register_template_handler :rjs, RJS.new
|
||||
base.register_template_handler :builder, Builder.new
|
||||
end
|
||||
|
||||
@@template_handlers = {}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
module ActionView
|
||||
module Template::Handlers
|
||||
class Builder < Template::Handler
|
||||
include Compilable
|
||||
|
||||
class Builder
|
||||
# Default format used by Builder.
|
||||
class_attribute :default_format
|
||||
self.default_format = Mime::XML
|
||||
|
||||
def compile(template)
|
||||
def call(template)
|
||||
require 'builder'
|
||||
"xml = ::Builder::XmlMarkup.new(:indent => 2);" +
|
||||
"self.output_buffer = xml.target!;" +
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
require 'active_support/core_ext/class/attribute_accessors'
|
||||
require 'active_support/core_ext/string/output_safety'
|
||||
require "action_view/template"
|
||||
require 'action_view/template'
|
||||
require 'action_view/template/handler'
|
||||
require 'erubis'
|
||||
|
||||
module ActionView
|
||||
@@ -47,28 +48,31 @@ module ActionView
|
||||
end
|
||||
end
|
||||
|
||||
class ERB < Handler
|
||||
include Compilable
|
||||
|
||||
##
|
||||
# :singleton-method:
|
||||
class ERB
|
||||
# Specify trim mode for the ERB compiler. Defaults to '-'.
|
||||
# See ERb documentation for suitable values.
|
||||
cattr_accessor :erb_trim_mode
|
||||
class_attribute :erb_trim_mode
|
||||
self.erb_trim_mode = '-'
|
||||
|
||||
# Default format used by ERB.
|
||||
class_attribute :default_format
|
||||
self.default_format = Mime::HTML
|
||||
|
||||
cattr_accessor :erb_implementation
|
||||
# Default implemenation used.
|
||||
class_attribute :erb_implementation
|
||||
self.erb_implementation = Erubis
|
||||
|
||||
ENCODING_TAG = Regexp.new("\\A(<%#{ENCODING_FLAG}-?%>)[ \\t]*")
|
||||
|
||||
def self.handles_encoding?
|
||||
def self.call(template)
|
||||
new.call(template)
|
||||
end
|
||||
|
||||
def handles_encoding?
|
||||
true
|
||||
end
|
||||
|
||||
def compile(template)
|
||||
def call(template)
|
||||
if template.source.encoding_aware?
|
||||
# First, convert to BINARY, so in case the encoding is
|
||||
# wrong, we can still find an encoding tag
|
||||
@@ -94,6 +98,7 @@ module ActionView
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def valid_encoding(string, encoding)
|
||||
# If a magic encoding comment was found, tag the
|
||||
# String with this encoding. This is for a case
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
module ActionView
|
||||
module Template::Handlers
|
||||
class RJS < Template::Handler
|
||||
include Compilable
|
||||
|
||||
class RJS
|
||||
# Default format used by RJS.
|
||||
class_attribute :default_format
|
||||
self.default_format = Mime::JS
|
||||
|
||||
def compile(template)
|
||||
def call(template)
|
||||
"update_page do |page|;#{template.source}\nend"
|
||||
end
|
||||
|
||||
def default_format
|
||||
Mime::JS
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
20
actionpack/lib/action_view/template/inline.rb
Normal file
20
actionpack/lib/action_view/template/inline.rb
Normal file
@@ -0,0 +1,20 @@
|
||||
require 'digest/md5'
|
||||
|
||||
module ActionView
|
||||
class Template
|
||||
class Inline < ::ActionView::Template
|
||||
def initialize(source, handler, options={})
|
||||
super(source, "inline template", handler, options)
|
||||
end
|
||||
|
||||
def md5_source
|
||||
@md5_source ||= Digest::MD5.hexdigest(source)
|
||||
end
|
||||
|
||||
def eql?(other)
|
||||
other.is_a?(Inline) && other.md5_source == md5_source
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,9 +6,8 @@ module ActionView
|
||||
# = Action View Resolver
|
||||
class Resolver
|
||||
def initialize
|
||||
@path = nil
|
||||
@cached = Hash.new { |h1,k1| h1[k1] =
|
||||
Hash.new { |h2,k2| h2[k2] = Hash.new { |h3, k3| h3[k3] = {} } } }
|
||||
@cached = Hash.new { |h1,k1| h1[k1] = Hash.new { |h2,k2|
|
||||
h2[k2] = Hash.new { |h3,k3| h3[k3] = Hash.new { |h4,k4| h4[k4] = {} } } } }
|
||||
end
|
||||
|
||||
def clear_cache
|
||||
@@ -16,8 +15,8 @@ module ActionView
|
||||
end
|
||||
|
||||
# Normalizes the arguments and passes it on to find_template.
|
||||
def find_all(name, prefix=nil, partial=false, details={}, key=nil)
|
||||
cached(key, prefix, name, partial) do
|
||||
def find_all(name, prefix=nil, partial=false, details={}, locals=[], key=nil)
|
||||
cached(key, [name, prefix, partial], details, locals) do
|
||||
find_templates(name, prefix, partial, details)
|
||||
end
|
||||
end
|
||||
@@ -35,34 +34,73 @@ module ActionView
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def cached(key, prefix, name, partial)
|
||||
return yield unless key && caching?
|
||||
@cached[key][prefix][name][partial] ||= yield
|
||||
# Helpers that builds a path. Useful for building virtual paths.
|
||||
def build_path(name, prefix, partial)
|
||||
path = ""
|
||||
path << "#{prefix}/" unless prefix.empty?
|
||||
path << (partial ? "_#{name}" : name)
|
||||
path
|
||||
end
|
||||
|
||||
# Hnadles templates caching. If a key is given and caching is on
|
||||
# always check the cache before hitting the resolver. Otherwise,
|
||||
# it always hits the resolver but check if the resolver is fresher
|
||||
# before returning it.
|
||||
def cached(key, path_info, details, locals) #:nodoc:
|
||||
name, prefix, partial = path_info
|
||||
locals = sort_locals(locals)
|
||||
|
||||
if key && caching?
|
||||
@cached[key][name][prefix][partial][locals] ||= decorate(yield, path_info, details, locals)
|
||||
else
|
||||
fresh = decorate(yield, path_info, details, locals)
|
||||
return fresh unless key
|
||||
|
||||
scope = @cached[key][name][prefix][partial]
|
||||
cache = scope[locals]
|
||||
mtime = cache && cache.map(&:updated_at).max
|
||||
|
||||
if !mtime || fresh.empty? || fresh.any? { |t| t.updated_at > mtime }
|
||||
scope[locals] = fresh
|
||||
else
|
||||
cache
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Ensures all the resolver information is set in the template.
|
||||
def decorate(templates, path_info, details, locals) #:nodoc:
|
||||
cached = nil
|
||||
templates.each do |t|
|
||||
t.locals = locals
|
||||
t.formats = details[:formats] || [:html] if t.formats.empty?
|
||||
t.virtual_path ||= (cached ||= build_path(*path_info))
|
||||
end
|
||||
end
|
||||
|
||||
if :symbol.respond_to?("<=>")
|
||||
def sort_locals(locals) #:nodoc:
|
||||
locals.sort.freeze
|
||||
end
|
||||
else
|
||||
def sort_locals(locals) #:nodoc:
|
||||
locals = locals.map{ |l| l.to_s }
|
||||
locals.sort!
|
||||
locals.freeze
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class PathResolver < Resolver
|
||||
EXTENSION_ORDER = [:locale, :formats, :handlers]
|
||||
|
||||
def to_s
|
||||
@path.to_s
|
||||
end
|
||||
alias :to_path :to_s
|
||||
|
||||
private
|
||||
private
|
||||
|
||||
def find_templates(name, prefix, partial, details)
|
||||
path = build_path(name, prefix, partial, details)
|
||||
path = build_path(name, prefix, partial)
|
||||
query(path, EXTENSION_ORDER.map { |ext| details[ext] }, details[:formats])
|
||||
end
|
||||
|
||||
def build_path(name, prefix, partial, details)
|
||||
path = ""
|
||||
path << "#{prefix}/" unless prefix.empty?
|
||||
path << (partial ? "_#{name}" : name)
|
||||
path
|
||||
end
|
||||
|
||||
def query(path, exts, formats)
|
||||
query = File.join(@path, path)
|
||||
|
||||
@@ -76,26 +114,28 @@ module ActionView
|
||||
contents = File.open(p, "rb") {|io| io.read }
|
||||
|
||||
Template.new(contents, File.expand_path(p), handler,
|
||||
:virtual_path => path, :format => format)
|
||||
:virtual_path => path, :format => format, :updated_at => mtime(p))
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the file mtime from the filesystem.
|
||||
def mtime(p)
|
||||
File.stat(p).mtime
|
||||
end
|
||||
|
||||
# Extract handler and formats from path. If a format cannot be a found neither
|
||||
# from the path, or the handler, we should return the array of formats given
|
||||
# to the resolver.
|
||||
def extract_handler_and_format(path, default_formats)
|
||||
pieces = File.basename(path).split(".")
|
||||
pieces.shift
|
||||
|
||||
handler = Template.handler_class_for_extension(pieces.pop)
|
||||
format = pieces.last && Mime[pieces.last] && pieces.pop.to_sym
|
||||
format ||= handler.default_format if handler.respond_to?(:default_format)
|
||||
format ||= default_formats
|
||||
|
||||
handler = Template.handler_class_for_extension(pieces.pop)
|
||||
format = pieces.last && Mime[pieces.last]
|
||||
[handler, format]
|
||||
end
|
||||
end
|
||||
|
||||
# A resolver that loads files from the filesystem.
|
||||
class FileSystemResolver < PathResolver
|
||||
def initialize(path)
|
||||
raise ArgumentError, "path already is a Resolver class" if path.is_a?(Resolver)
|
||||
@@ -103,9 +143,26 @@ module ActionView
|
||||
@path = File.expand_path(path)
|
||||
end
|
||||
|
||||
def to_s
|
||||
@path.to_s
|
||||
end
|
||||
alias :to_path :to_s
|
||||
|
||||
def eql?(resolver)
|
||||
self.class.equal?(resolver.class) && to_path == resolver.to_path
|
||||
end
|
||||
alias :== :eql?
|
||||
end
|
||||
|
||||
# The same as FileSystemResolver but does not allow templates to store
|
||||
# a virtual path since it is invalid for such resolvers.
|
||||
class FallbackFileSystemResolver < FileSystemResolver
|
||||
def self.instances
|
||||
[new(""), new("/")]
|
||||
end
|
||||
|
||||
def decorate(*)
|
||||
super.each { |t| t.virtual_path = nil }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -25,10 +25,6 @@ module ActionView #:nodoc:
|
||||
def formats
|
||||
[@mime_type.to_sym]
|
||||
end
|
||||
|
||||
def partial?
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -103,7 +103,7 @@ module ActionView
|
||||
end
|
||||
|
||||
def render(options = {}, local_assigns = {}, &block)
|
||||
view.assign(_assigns)
|
||||
view.assign(view_assigns)
|
||||
@rendered << output = view.render(options, local_assigns, &block)
|
||||
output
|
||||
end
|
||||
@@ -169,15 +169,19 @@ module ActionView
|
||||
|
||||
alias_method :_view, :view
|
||||
|
||||
EXCLUDE_IVARS = %w{
|
||||
INTERNAL_IVARS = %w{
|
||||
@__name__
|
||||
@_assertion_wrapped
|
||||
@_assertions
|
||||
@_result
|
||||
@_routes
|
||||
@controller
|
||||
@layouts
|
||||
@locals
|
||||
@method_name
|
||||
@output_buffer
|
||||
@partials
|
||||
@passed
|
||||
@rendered
|
||||
@request
|
||||
@routes
|
||||
@@ -187,12 +191,24 @@ module ActionView
|
||||
@view_context_class
|
||||
}
|
||||
|
||||
def _instance_variables
|
||||
instance_variables.map(&:to_s) - EXCLUDE_IVARS
|
||||
def _user_defined_ivars
|
||||
instance_variables.map(&:to_s) - INTERNAL_IVARS
|
||||
end
|
||||
|
||||
# Returns a Hash of instance variables and their values, as defined by
|
||||
# the user in the test case, which are then assigned to the view being
|
||||
# rendered. This is generally intended for internal use and extension
|
||||
# frameworks.
|
||||
def view_assigns
|
||||
Hash[_user_defined_ivars.map do |var|
|
||||
[var[1, var.length].to_sym, instance_variable_get(var)]
|
||||
end]
|
||||
end
|
||||
|
||||
def _assigns
|
||||
_instance_variables.map { |var| [var[1..-1].to_sym, instance_variable_get(var)] }
|
||||
ActiveSupport::Deprecation.warn "ActionView::TestCase#_assigns is deprecated and will be removed in future versions. " <<
|
||||
"Please use view_assigns instead."
|
||||
view_assigns
|
||||
end
|
||||
|
||||
def _routes
|
||||
|
||||
@@ -13,26 +13,29 @@ module ActionView #:nodoc:
|
||||
@hash = hash
|
||||
end
|
||||
|
||||
private
|
||||
private
|
||||
|
||||
def query(path, exts, formats)
|
||||
query = Regexp.escape(path)
|
||||
query = ""
|
||||
exts.each do |ext|
|
||||
query << '(' << ext.map {|e| e && Regexp.escape(".#{e}") }.join('|') << '|)'
|
||||
end
|
||||
query = /^(#{Regexp.escape(path)})#{query}$/
|
||||
|
||||
templates = []
|
||||
@hash.select { |k,v| k =~ /^#{query}$/ }.each do |_path, source|
|
||||
@hash.each do |_path, array|
|
||||
source, updated_at = array
|
||||
next unless _path =~ query
|
||||
handler, format = extract_handler_and_format(_path, formats)
|
||||
templates << Template.new(source, _path, handler,
|
||||
:virtual_path => _path, :format => format)
|
||||
:virtual_path => $1, :format => format, :updated_at => updated_at)
|
||||
end
|
||||
|
||||
templates.sort_by {|t| -t.identifier.match(/^#{query}$/).captures.reject(&:blank?).size }
|
||||
end
|
||||
end
|
||||
|
||||
class NullResolver < ActionView::PathResolver
|
||||
class NullResolver < PathResolver
|
||||
def query(path, exts, formats)
|
||||
handler, format = extract_handler_and_format(path, formats)
|
||||
[ActionView::Template.new("Template generated by Null Resolver", path, handler, :virtual_path => path, :format => format)]
|
||||
|
||||
@@ -308,3 +308,38 @@ module ActionView
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Workshop
|
||||
extend ActiveModel::Naming
|
||||
include ActiveModel::Conversion
|
||||
attr_accessor :id
|
||||
|
||||
def initialize(id)
|
||||
@id = id
|
||||
end
|
||||
|
||||
def persisted?
|
||||
id.present?
|
||||
end
|
||||
|
||||
def to_s
|
||||
id.to_s
|
||||
end
|
||||
end
|
||||
|
||||
module ActionDispatch
|
||||
class ShowExceptions
|
||||
private
|
||||
remove_method :public_path
|
||||
def public_path
|
||||
"#{FIXTURE_LOAD_PATH}/public"
|
||||
end
|
||||
|
||||
remove_method :logger
|
||||
# Silence logger
|
||||
def logger
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -32,6 +32,8 @@ class ActionPackAssertionsController < ActionController::Base
|
||||
|
||||
def redirect_to_path() redirect_to '/some/path' end
|
||||
|
||||
def redirect_invalid_external_route() redirect_to 'ht_tp://www.rubyonrails.org' end
|
||||
|
||||
def redirect_to_named_route() redirect_to route_one_url end
|
||||
|
||||
def redirect_external() redirect_to "http://www.rubyonrails.org"; end
|
||||
@@ -368,6 +370,11 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
def test_redirect_invalid_external_route
|
||||
process :redirect_invalid_external_route
|
||||
assert_redirected_to "http://test.hostht_tp://www.rubyonrails.org"
|
||||
end
|
||||
|
||||
def test_redirected_to_url_full_url
|
||||
process :redirect_to_path
|
||||
assert_redirected_to 'http://test.host/some/path'
|
||||
|
||||
@@ -25,6 +25,10 @@ class CaptureController < ActionController::Base
|
||||
render :layout => "talk_from_action"
|
||||
end
|
||||
|
||||
def proper_block_detection
|
||||
@todo = "some todo"
|
||||
end
|
||||
|
||||
def rescue_action(e) raise end
|
||||
end
|
||||
|
||||
@@ -66,8 +70,8 @@ class CaptureTest < ActionController::TestCase
|
||||
end
|
||||
|
||||
def test_proper_block_detection
|
||||
@todo = "some todo"
|
||||
get :proper_block_detection
|
||||
assert_equal "some todo", @response.body
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -29,18 +29,18 @@ class OldContentTypeController < ActionController::Base
|
||||
render :text => "hello world!"
|
||||
end
|
||||
|
||||
def render_default_for_rhtml
|
||||
def render_default_for_erb
|
||||
end
|
||||
|
||||
def render_default_for_rxml
|
||||
def render_default_for_builder
|
||||
end
|
||||
|
||||
def render_default_for_rjs
|
||||
end
|
||||
|
||||
def render_change_for_rxml
|
||||
def render_change_for_builder
|
||||
response.content_type = Mime::HTML
|
||||
render :action => "render_default_for_rxml"
|
||||
render :action => "render_default_for_builder"
|
||||
end
|
||||
|
||||
def render_default_content_types_for_respond_to
|
||||
@@ -108,23 +108,23 @@ class ContentTypeTest < ActionController::TestCase
|
||||
assert_equal "utf-8", @response.charset, @response.headers.inspect
|
||||
end
|
||||
|
||||
def test_nil_default_for_rhtml
|
||||
def test_nil_default_for_erb
|
||||
OldContentTypeController.default_charset = nil
|
||||
get :render_default_for_rhtml
|
||||
get :render_default_for_erb
|
||||
assert_equal Mime::HTML, @response.content_type
|
||||
assert_nil @response.charset, @response.headers.inspect
|
||||
ensure
|
||||
OldContentTypeController.default_charset = "utf-8"
|
||||
end
|
||||
|
||||
def test_default_for_rhtml
|
||||
get :render_default_for_rhtml
|
||||
def test_default_for_erb
|
||||
get :render_default_for_erb
|
||||
assert_equal Mime::HTML, @response.content_type
|
||||
assert_equal "utf-8", @response.charset
|
||||
end
|
||||
|
||||
def test_default_for_rxml
|
||||
get :render_default_for_rxml
|
||||
def test_default_for_builder
|
||||
get :render_default_for_builder
|
||||
assert_equal Mime::XML, @response.content_type
|
||||
assert_equal "utf-8", @response.charset
|
||||
end
|
||||
@@ -135,8 +135,8 @@ class ContentTypeTest < ActionController::TestCase
|
||||
assert_equal "utf-8", @response.charset
|
||||
end
|
||||
|
||||
def test_change_for_rxml
|
||||
get :render_change_for_rxml
|
||||
def test_change_for_builder
|
||||
get :render_change_for_builder
|
||||
assert_equal Mime::HTML, @response.content_type
|
||||
assert_equal "utf-8", @response.charset
|
||||
end
|
||||
|
||||
@@ -314,6 +314,7 @@ class FilterTest < ActionController::TestCase
|
||||
|
||||
def initialize
|
||||
@@execution_log = ""
|
||||
super()
|
||||
end
|
||||
|
||||
before_filter { |c| c.class.execution_log << " before procfilter " }
|
||||
@@ -757,12 +758,12 @@ class ControllerWithSymbolAsFilter < PostsController
|
||||
|
||||
def without_exception
|
||||
# Do stuff...
|
||||
1 + 1
|
||||
wtf = 1 + 1
|
||||
|
||||
yield
|
||||
|
||||
# Do stuff...
|
||||
1 + 1
|
||||
wtf += 1
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -167,7 +167,7 @@ end
|
||||
|
||||
class IntegrationTestTest < Test::Unit::TestCase
|
||||
def setup
|
||||
@test = ::ActionDispatch::IntegrationTest.new(:default_test)
|
||||
@test = ::ActionDispatch::IntegrationTest.new(:app)
|
||||
@test.class.stubs(:fixture_table_names).returns([])
|
||||
@session = @test.open_session
|
||||
end
|
||||
|
||||
@@ -46,13 +46,13 @@ class LayoutAutoDiscoveryTest < ActionController::TestCase
|
||||
def test_application_layout_is_default_when_no_controller_match
|
||||
@controller = ProductController.new
|
||||
get :hello
|
||||
assert_equal 'layout_test.rhtml hello.rhtml', @response.body
|
||||
assert_equal 'layout_test.erb hello.erb', @response.body
|
||||
end
|
||||
|
||||
def test_controller_name_layout_name_match
|
||||
@controller = ItemController.new
|
||||
get :hello
|
||||
assert_equal 'item.rhtml hello.rhtml', @response.body
|
||||
assert_equal 'item.erb hello.erb', @response.body
|
||||
end
|
||||
|
||||
def test_third_party_template_library_auto_discovers_layout
|
||||
@@ -65,13 +65,13 @@ class LayoutAutoDiscoveryTest < ActionController::TestCase
|
||||
def test_namespaced_controllers_auto_detect_layouts1
|
||||
@controller = ControllerNameSpace::NestedController.new
|
||||
get :hello
|
||||
assert_equal 'controller_name_space/nested.rhtml hello.rhtml', @response.body
|
||||
assert_equal 'controller_name_space/nested.erb hello.erb', @response.body
|
||||
end
|
||||
|
||||
def test_namespaced_controllers_auto_detect_layouts2
|
||||
@controller = MultipleExtensions.new
|
||||
get :hello
|
||||
assert_equal 'multiple_extensions.html.erb hello.rhtml', @response.body.strip
|
||||
assert_equal 'multiple_extensions.html.erb hello.erb', @response.body.strip
|
||||
end
|
||||
end
|
||||
|
||||
@@ -79,7 +79,7 @@ class DefaultLayoutController < LayoutTest
|
||||
end
|
||||
|
||||
class AbsolutePathLayoutController < LayoutTest
|
||||
layout File.expand_path(File.expand_path(__FILE__) + '/../../fixtures/layout_tests/layouts/layout_test.rhtml')
|
||||
layout File.expand_path(File.expand_path(__FILE__) + '/../../fixtures/layout_tests/layouts/layout_test.erb')
|
||||
end
|
||||
|
||||
class HasOwnLayoutController < LayoutTest
|
||||
@@ -137,7 +137,7 @@ class LayoutSetInResponseTest < ActionController::TestCase
|
||||
def test_layout_only_exception_when_excepted
|
||||
@controller = OnlyLayoutController.new
|
||||
get :goodbye
|
||||
assert !@response.body.include?("item.rhtml"), "#{@response.body.inspect} included 'item.rhtml'"
|
||||
assert !@response.body.include?("item.erb"), "#{@response.body.inspect} included 'item.erb'"
|
||||
end
|
||||
|
||||
def test_layout_except_exception_when_included
|
||||
@@ -149,7 +149,7 @@ class LayoutSetInResponseTest < ActionController::TestCase
|
||||
def test_layout_except_exception_when_excepted
|
||||
@controller = ExceptLayoutController.new
|
||||
get :goodbye
|
||||
assert !@response.body.include?("item.rhtml"), "#{@response.body.inspect} included 'item.rhtml'"
|
||||
assert !@response.body.include?("item.erb"), "#{@response.body.inspect} included 'item.erb'"
|
||||
end
|
||||
|
||||
def test_layout_set_when_using_render
|
||||
@@ -173,7 +173,7 @@ class LayoutSetInResponseTest < ActionController::TestCase
|
||||
def test_absolute_pathed_layout
|
||||
@controller = AbsolutePathLayoutController.new
|
||||
get :hello
|
||||
assert_equal "layout_test.rhtml hello.rhtml", @response.body.strip
|
||||
assert_equal "layout_test.erb hello.erb", @response.body.strip
|
||||
end
|
||||
end
|
||||
|
||||
@@ -184,7 +184,7 @@ class RenderWithTemplateOptionController < LayoutTest
|
||||
end
|
||||
|
||||
class SetsNonExistentLayoutFile < LayoutTest
|
||||
layout "nofile.rhtml"
|
||||
layout "nofile.erb"
|
||||
end
|
||||
|
||||
class LayoutExceptionRaised < ActionController::TestCase
|
||||
|
||||
@@ -23,7 +23,7 @@ module Another
|
||||
def with_fragment_cache
|
||||
render :inline => "<%= cache('foo'){ 'bar' } %>"
|
||||
end
|
||||
|
||||
|
||||
def with_fragment_cache_and_percent_in_key
|
||||
render :inline => "<%= cache('foo%bar'){ 'Contains % sign in key' } %>"
|
||||
end
|
||||
@@ -151,8 +151,8 @@ class ACLogSubscriberTest < ActionController::TestCase
|
||||
wait
|
||||
|
||||
assert_equal 4, logs.size
|
||||
assert_match /Exist fragment\? views\/foo%bar/, logs[1]
|
||||
assert_match /Write fragment views\/foo%bar/, logs[2]
|
||||
assert_match(/Exist fragment\? views\/foo%bar/, logs[1])
|
||||
assert_match(/Write fragment views\/foo%bar/, logs[2])
|
||||
ensure
|
||||
@controller.config.perform_caching = true
|
||||
end
|
||||
|
||||
@@ -24,4 +24,19 @@ module BareMetalTest
|
||||
assert_equal "Hello world", string
|
||||
end
|
||||
end
|
||||
|
||||
class HeadController < ActionController::Metal
|
||||
include ActionController::Head
|
||||
|
||||
def index
|
||||
head :not_found
|
||||
end
|
||||
end
|
||||
|
||||
class HeadTest < ActiveSupport::TestCase
|
||||
test "head works on its own" do
|
||||
status, headers, body = HeadController.action(:index).call(Rack::MockRequest.env_for("/"))
|
||||
assert_equal 404, status
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
require 'abstract_unit'
|
||||
|
||||
module Etags
|
||||
class BasicController < ActionController::Base
|
||||
self.view_paths = [ActionView::FixtureResolver.new(
|
||||
"etags/basic/base.html.erb" => "Hello from without_layout.html.erb",
|
||||
"layouts/etags.html.erb" => "teh <%= yield %> tagz"
|
||||
)]
|
||||
|
||||
def without_layout
|
||||
render :action => "base"
|
||||
end
|
||||
|
||||
def with_layout
|
||||
render :action => "base", :layout => "etags"
|
||||
end
|
||||
end
|
||||
|
||||
class EtagTest < Rack::TestCase
|
||||
describe "Rendering without any special etag options returns an etag that is an MD5 hash of its text"
|
||||
|
||||
test "an action without a layout" do
|
||||
get "/etags/basic/without_layout"
|
||||
|
||||
body = "Hello from without_layout.html.erb"
|
||||
assert_body body
|
||||
assert_header "Etag", etag_for(body)
|
||||
assert_status 200
|
||||
end
|
||||
|
||||
test "an action with a layout" do
|
||||
get "/etags/basic/with_layout"
|
||||
|
||||
body = "teh Hello from without_layout.html.erb tagz"
|
||||
assert_body body
|
||||
assert_header "Etag", etag_for(body)
|
||||
assert_status 200
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def etag_for(text)
|
||||
%("#{Digest::MD5.hexdigest(text)}")
|
||||
end
|
||||
end
|
||||
end
|
||||
72
actionpack/test/controller/new_base/render_once_test.rb
Normal file
72
actionpack/test/controller/new_base/render_once_test.rb
Normal file
@@ -0,0 +1,72 @@
|
||||
require 'abstract_unit'
|
||||
|
||||
module RenderTemplate
|
||||
class RenderOnceController < ActionController::Base
|
||||
layout false
|
||||
|
||||
RESOLVER = ActionView::FixtureResolver.new(
|
||||
"test/a.html.erb" => "a",
|
||||
"test/b.html.erb" => "<>",
|
||||
"test/c.html.erb" => "c",
|
||||
"test/one.html.erb" => "<%= render :once => 'test/result' %>",
|
||||
"test/two.html.erb" => "<%= render :once => 'test/result' %>",
|
||||
"test/three.html.erb" => "<%= render :once => 'test/result' %>",
|
||||
"test/result.html.erb" => "YES!",
|
||||
"layouts/test.html.erb" => "l<%= yield %>l"
|
||||
)
|
||||
|
||||
self.view_paths = [RESOLVER]
|
||||
|
||||
def multiple
|
||||
render :once => %w(test/a test/b test/c)
|
||||
end
|
||||
|
||||
def once
|
||||
render :once => %w(test/one test/two test/three)
|
||||
end
|
||||
|
||||
def duplicate
|
||||
render :once => %w(test/a test/a test/a)
|
||||
end
|
||||
|
||||
def with_layout
|
||||
render :once => %w(test/a test/b test/c), :layout => "test"
|
||||
end
|
||||
end
|
||||
|
||||
module Tests
|
||||
def test_mutliple_arguments_get_all_rendered
|
||||
get :multiple
|
||||
assert_response "a\n<>\nc"
|
||||
end
|
||||
|
||||
def test_referenced_templates_get_rendered_once
|
||||
get :once
|
||||
assert_response "YES!\n\n"
|
||||
end
|
||||
|
||||
def test_duplicated_templates_get_rendered_once
|
||||
get :duplicate
|
||||
assert_response "a"
|
||||
end
|
||||
|
||||
def test_layout_wraps_all_rendered_templates
|
||||
get :with_layout
|
||||
assert_response "la\n<>\ncl"
|
||||
end
|
||||
end
|
||||
|
||||
class TestWithResolverCache < Rack::TestCase
|
||||
testing RenderTemplate::RenderOnceController
|
||||
include Tests
|
||||
end
|
||||
|
||||
class TestWithoutResolverCache < Rack::TestCase
|
||||
testing RenderTemplate::RenderOnceController
|
||||
include Tests
|
||||
|
||||
def setup
|
||||
RenderTemplate::RenderOnceController::RESOLVER.stubs(:caching?).returns(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -5,10 +5,17 @@ module RenderPartial
|
||||
class BasicController < ActionController::Base
|
||||
|
||||
self.view_paths = [ActionView::FixtureResolver.new(
|
||||
"render_partial/basic/_basic.html.erb" => "BasicPartial!",
|
||||
"render_partial/basic/basic.html.erb" => "<%= @test_unchanged = 'goodbye' %><%= render :partial => 'basic' %><%= @test_unchanged %>"
|
||||
"render_partial/basic/_basic.html.erb" => "BasicPartial!",
|
||||
"render_partial/basic/basic.html.erb" => "<%= @test_unchanged = 'goodbye' %><%= render :partial => 'basic' %><%= @test_unchanged %>",
|
||||
"render_partial/basic/with_json.html.erb" => "<%= render 'with_json.json' %>",
|
||||
"render_partial/basic/_with_json.json.erb" => "<%= render 'final' %>",
|
||||
"render_partial/basic/_final.json.erb" => "{ final: json }"
|
||||
)]
|
||||
|
||||
def html_with_json_inside_json
|
||||
render :action => "with_json"
|
||||
end
|
||||
|
||||
def changing
|
||||
@test_unchanged = 'hello'
|
||||
render :action => "basic"
|
||||
@@ -22,6 +29,12 @@ module RenderPartial
|
||||
get :changing
|
||||
assert_response("goodbyeBasicPartial!goodbye")
|
||||
end
|
||||
|
||||
test "rendering a template with renders another partial with other format that renders other partial in the same format" do
|
||||
get :html_with_json_inside_json
|
||||
assert_content_type "text/html; charset=utf-8"
|
||||
assert_response "{ final: json }"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -8,13 +8,21 @@ module RenderTemplate
|
||||
"shared.html.erb" => "Elastica",
|
||||
"locals.html.erb" => "The secret is <%= secret %>",
|
||||
"xml_template.xml.builder" => "xml.html do\n xml.p 'Hello'\nend",
|
||||
"with_raw.html.erb" => "Hello <%=raw '<strong>this is raw</strong>' %>"
|
||||
"with_raw.html.erb" => "Hello <%=raw '<strong>this is raw</strong>' %>",
|
||||
"test/with_json.html.erb" => "<%= render :template => 'test/with_json.json' %>",
|
||||
"test/with_json.json.erb" => "<%= render :template => 'test/final' %>",
|
||||
"test/final.json.erb" => "{ final: json }",
|
||||
"test/with_error.html.erb" => "<%= idontexist %>"
|
||||
)]
|
||||
|
||||
def index
|
||||
render :template => "test/basic"
|
||||
end
|
||||
|
||||
def html_with_json_inside_json
|
||||
render :template => "test/with_json"
|
||||
end
|
||||
|
||||
def index_without_key
|
||||
render "test/basic"
|
||||
end
|
||||
@@ -42,6 +50,10 @@ module RenderTemplate
|
||||
def with_raw
|
||||
render :template => "with_raw"
|
||||
end
|
||||
|
||||
def with_error
|
||||
render :template => "test/with_error"
|
||||
end
|
||||
end
|
||||
|
||||
class TestWithoutLayout < Rack::TestCase
|
||||
@@ -88,6 +100,18 @@ module RenderTemplate
|
||||
assert_body "Hello <strong>this is raw</strong>"
|
||||
assert_status 200
|
||||
end
|
||||
|
||||
test "rendering a template with renders another template with other format that renders other template in the same format" do
|
||||
get :html_with_json_inside_json
|
||||
assert_content_type "text/html; charset=utf-8"
|
||||
assert_response "{ final: json }"
|
||||
end
|
||||
|
||||
test "rendering a template with error properly exceprts the code" do
|
||||
get :with_error
|
||||
assert_status 500
|
||||
assert_match "undefined local variable or method `idontexist'", response.body
|
||||
end
|
||||
end
|
||||
|
||||
class WithLayoutController < ::ApplicationController
|
||||
|
||||
@@ -1,30 +1,5 @@
|
||||
require 'abstract_unit'
|
||||
|
||||
class Comment
|
||||
extend ActiveModel::Naming
|
||||
include ActiveModel::Conversion
|
||||
|
||||
attr_reader :id
|
||||
def to_key; id ? [id] : nil end
|
||||
def save; @id = 1 end
|
||||
def new_record?; @id.nil? end
|
||||
def name
|
||||
@id.nil? ? 'new comment' : "comment ##{@id}"
|
||||
end
|
||||
end
|
||||
|
||||
class Sheep
|
||||
extend ActiveModel::Naming
|
||||
include ActiveModel::Conversion
|
||||
|
||||
attr_reader :id
|
||||
def to_key; id ? [id] : nil end
|
||||
def save; @id = 1 end
|
||||
def new_record?; @id.nil? end
|
||||
def name
|
||||
@id.nil? ? 'new sheep' : "sheep ##{@id}"
|
||||
end
|
||||
end
|
||||
require 'controller/fake_models'
|
||||
|
||||
class RecordIdentifierTest < Test::Unit::TestCase
|
||||
include ActionController::RecordIdentifier
|
||||
|
||||
@@ -3,24 +3,6 @@ require 'abstract_unit'
|
||||
class WorkshopsController < ActionController::Base
|
||||
end
|
||||
|
||||
class Workshop
|
||||
extend ActiveModel::Naming
|
||||
include ActiveModel::Conversion
|
||||
attr_accessor :id
|
||||
|
||||
def initialize(id)
|
||||
@id = id
|
||||
end
|
||||
|
||||
def persisted?
|
||||
id.present?
|
||||
end
|
||||
|
||||
def to_s
|
||||
id.to_s
|
||||
end
|
||||
end
|
||||
|
||||
class RedirectController < ActionController::Base
|
||||
def simple_redirect
|
||||
redirect_to :action => "hello_world"
|
||||
|
||||
@@ -99,11 +99,6 @@ class TestController < ActionController::Base
|
||||
render :template => "test/hello_world"
|
||||
end
|
||||
|
||||
def render_hello_world_with_etag_set
|
||||
response.etag = "hello_world"
|
||||
render :template => "test/hello_world"
|
||||
end
|
||||
|
||||
# :ported: compatibility
|
||||
def render_hello_world_with_forward_slash
|
||||
render :template => "/test/hello_world"
|
||||
@@ -1386,119 +1381,6 @@ class ExpiresInRenderTest < ActionController::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class EtagRenderTest < ActionController::TestCase
|
||||
tests TestController
|
||||
|
||||
def setup
|
||||
super
|
||||
@request.host = "www.nextangle.com"
|
||||
@expected_bang_etag = etag_for(expand_key([:foo, 123]))
|
||||
end
|
||||
|
||||
def test_render_blank_body_shouldnt_set_etag
|
||||
get :blank_response
|
||||
assert !@response.etag?
|
||||
end
|
||||
|
||||
def test_render_200_should_set_etag
|
||||
get :render_hello_world_from_variable
|
||||
assert_equal etag_for("hello david"), @response.headers['ETag']
|
||||
assert_equal "max-age=0, private, must-revalidate", @response.headers['Cache-Control']
|
||||
end
|
||||
|
||||
def test_render_against_etag_request_should_304_when_match
|
||||
@request.if_none_match = etag_for("hello david")
|
||||
get :render_hello_world_from_variable
|
||||
assert_equal 304, @response.status.to_i
|
||||
assert @response.body.empty?
|
||||
end
|
||||
|
||||
def test_render_against_etag_request_should_have_no_content_length_when_match
|
||||
@request.if_none_match = etag_for("hello david")
|
||||
get :render_hello_world_from_variable
|
||||
assert !@response.headers.has_key?("Content-Length")
|
||||
end
|
||||
|
||||
def test_render_against_etag_request_should_200_when_no_match
|
||||
@request.if_none_match = etag_for("hello somewhere else")
|
||||
get :render_hello_world_from_variable
|
||||
assert_equal 200, @response.status.to_i
|
||||
assert !@response.body.empty?
|
||||
end
|
||||
|
||||
def test_render_should_not_set_etag_when_last_modified_has_been_specified
|
||||
get :render_hello_world_with_last_modified_set
|
||||
assert_equal 200, @response.status.to_i
|
||||
assert_not_nil @response.last_modified
|
||||
assert_nil @response.etag
|
||||
assert_present @response.body
|
||||
end
|
||||
|
||||
def test_render_with_etag
|
||||
get :render_hello_world_from_variable
|
||||
expected_etag = etag_for('hello david')
|
||||
assert_equal expected_etag, @response.headers['ETag']
|
||||
@response = ActionController::TestResponse.new
|
||||
|
||||
@request.if_none_match = expected_etag
|
||||
get :render_hello_world_from_variable
|
||||
assert_equal 304, @response.status.to_i
|
||||
|
||||
@response = ActionController::TestResponse.new
|
||||
@request.if_none_match = "\"diftag\""
|
||||
get :render_hello_world_from_variable
|
||||
assert_equal 200, @response.status.to_i
|
||||
end
|
||||
|
||||
def render_with_404_shouldnt_have_etag
|
||||
get :render_custom_code
|
||||
assert_nil @response.headers['ETag']
|
||||
end
|
||||
|
||||
def test_etag_should_not_be_changed_when_already_set
|
||||
get :render_hello_world_with_etag_set
|
||||
assert_equal etag_for("hello_world"), @response.headers['ETag']
|
||||
end
|
||||
|
||||
def test_etag_should_govern_renders_with_layouts_too
|
||||
get :builder_layout_test
|
||||
assert_equal "<wrapper>\n<html>\n <p>Hello </p>\n<p>This is grand!</p>\n</html>\n</wrapper>\n", @response.body
|
||||
assert_equal etag_for("<wrapper>\n<html>\n <p>Hello </p>\n<p>This is grand!</p>\n</html>\n</wrapper>\n"), @response.headers['ETag']
|
||||
end
|
||||
|
||||
def test_etag_with_bang_should_set_etag
|
||||
get :conditional_hello_with_bangs
|
||||
assert_equal @expected_bang_etag, @response.headers["ETag"]
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
def test_etag_with_bang_should_obey_if_none_match
|
||||
@request.if_none_match = @expected_bang_etag
|
||||
get :conditional_hello_with_bangs
|
||||
assert_response :not_modified
|
||||
end
|
||||
|
||||
def test_etag_with_public_true_should_set_header
|
||||
get :conditional_hello_with_public_header
|
||||
assert_equal "public", @response.headers['Cache-Control']
|
||||
end
|
||||
|
||||
def test_etag_with_public_true_should_set_header_and_retain_other_headers
|
||||
get :conditional_hello_with_public_header_and_expires_at
|
||||
assert_equal "max-age=60, public", @response.headers['Cache-Control']
|
||||
end
|
||||
|
||||
protected
|
||||
def etag_for(text)
|
||||
%("#{Digest::MD5.hexdigest(text)}")
|
||||
end
|
||||
|
||||
def expand_key(args)
|
||||
ActiveSupport::Cache.expand_cache_key(args)
|
||||
end
|
||||
end
|
||||
|
||||
class LastModifiedRenderTest < ActionController::TestCase
|
||||
tests TestController
|
||||
|
||||
|
||||
@@ -1,21 +1,5 @@
|
||||
require 'abstract_unit'
|
||||
|
||||
module ActionDispatch
|
||||
class ShowExceptions
|
||||
private
|
||||
remove_method :public_path
|
||||
def public_path
|
||||
"#{FIXTURE_LOAD_PATH}/public"
|
||||
end
|
||||
|
||||
remove_method :logger
|
||||
# Silence logger
|
||||
def logger
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class RescueController < ActionController::Base
|
||||
class NotAuthorized < StandardError
|
||||
end
|
||||
|
||||
@@ -1,8 +1,23 @@
|
||||
require 'abstract_unit'
|
||||
require 'rack/test'
|
||||
|
||||
module TestGenerationPrefix
|
||||
class Post
|
||||
extend ActiveModel::Naming
|
||||
|
||||
def to_param
|
||||
"1"
|
||||
end
|
||||
|
||||
def self.model_name
|
||||
klass = "Post"
|
||||
def klass.name; self end
|
||||
|
||||
ActiveModel::Name.new(klass)
|
||||
end
|
||||
end
|
||||
|
||||
class WithMountedEngine < ActionDispatch::IntegrationTest
|
||||
require 'rack/test'
|
||||
include Rack::Test::Methods
|
||||
|
||||
class BlogEngine
|
||||
@@ -55,21 +70,6 @@ module TestGenerationPrefix
|
||||
# force draw
|
||||
RailsApplication.routes
|
||||
|
||||
class Post
|
||||
extend ActiveModel::Naming
|
||||
|
||||
def to_param
|
||||
"1"
|
||||
end
|
||||
|
||||
def self.model_name
|
||||
klass = "Post"
|
||||
def klass.name; self end
|
||||
|
||||
ActiveModel::Name.new(klass)
|
||||
end
|
||||
end
|
||||
|
||||
class ::InsideEngineGeneratingController < ActionController::Base
|
||||
include BlogEngine.routes.url_helpers
|
||||
include RailsApplication.routes.mounted_helpers
|
||||
@@ -253,4 +253,65 @@ module TestGenerationPrefix
|
||||
assert_equal "http://www.example.com/awesome/blog/posts/1", path
|
||||
end
|
||||
end
|
||||
|
||||
class EngineMountedAtRoot < ActionDispatch::IntegrationTest
|
||||
include Rack::Test::Methods
|
||||
|
||||
class BlogEngine
|
||||
def self.routes
|
||||
@routes ||= begin
|
||||
routes = ActionDispatch::Routing::RouteSet.new
|
||||
routes.draw do
|
||||
match "/posts/:id", :to => "posts#show", :as => :post
|
||||
end
|
||||
|
||||
routes
|
||||
end
|
||||
end
|
||||
|
||||
def self.call(env)
|
||||
env['action_dispatch.routes'] = routes
|
||||
routes.call(env)
|
||||
end
|
||||
end
|
||||
|
||||
class RailsApplication
|
||||
def self.routes
|
||||
@routes ||= begin
|
||||
routes = ActionDispatch::Routing::RouteSet.new
|
||||
routes.draw do
|
||||
mount BlogEngine => "/"
|
||||
end
|
||||
|
||||
routes
|
||||
end
|
||||
end
|
||||
|
||||
def self.call(env)
|
||||
env['action_dispatch.routes'] = routes
|
||||
routes.call(env)
|
||||
end
|
||||
end
|
||||
|
||||
# force draw
|
||||
RailsApplication.routes
|
||||
|
||||
class ::PostsController < ActionController::Base
|
||||
include BlogEngine.routes.url_helpers
|
||||
include RailsApplication.routes.mounted_helpers
|
||||
|
||||
def show
|
||||
render :text => post_path(:id => params[:id])
|
||||
end
|
||||
end
|
||||
|
||||
def app
|
||||
RailsApplication
|
||||
end
|
||||
|
||||
test "generating path inside engine" do
|
||||
get "/posts/1"
|
||||
assert_equal "/posts/1", last_response.body
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -36,7 +36,6 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest
|
||||
assert_equal 'bar', params['foo']
|
||||
|
||||
file = params['file']
|
||||
assert_kind_of Tempfile, file
|
||||
assert_equal 'file.txt', file.original_filename
|
||||
assert_equal "text/plain", file.content_type
|
||||
assert_equal 'contents', file.read
|
||||
@@ -49,8 +48,6 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest
|
||||
file = params['file']
|
||||
foo = params['foo']
|
||||
|
||||
assert_kind_of Tempfile, file
|
||||
|
||||
assert_equal 'file.txt', file.original_filename
|
||||
assert_equal "text/plain", file.content_type
|
||||
|
||||
@@ -64,8 +61,6 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest
|
||||
|
||||
file = params['file']
|
||||
|
||||
assert_kind_of Tempfile, file
|
||||
|
||||
assert_equal 'file.txt', file.original_filename
|
||||
assert_equal "text/plain", file.content_type
|
||||
assert_equal(('a' * 20480), file.read)
|
||||
@@ -77,13 +72,11 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest
|
||||
assert_equal 'bar', params['foo']
|
||||
|
||||
file = params['file']
|
||||
assert_kind_of Tempfile, file
|
||||
assert_equal 'file.csv', file.original_filename
|
||||
assert_nil file.content_type
|
||||
assert_equal 'contents', file.read
|
||||
|
||||
file = params['flowers']
|
||||
assert_kind_of Tempfile, file
|
||||
assert_equal 'flowers.jpg', file.original_filename
|
||||
assert_equal "image/jpeg", file.content_type
|
||||
assert_equal 19512, file.size
|
||||
|
||||
@@ -11,9 +11,7 @@ class ResponseTest < ActiveSupport::TestCase
|
||||
status, headers, body = @response.to_a
|
||||
assert_equal 200, status
|
||||
assert_equal({
|
||||
"Content-Type" => "text/html; charset=utf-8",
|
||||
"Cache-Control" => "max-age=0, private, must-revalidate",
|
||||
"ETag" => '"65a8e27d8879283831b664bd8b7f0ad4"'
|
||||
"Content-Type" => "text/html; charset=utf-8"
|
||||
}, headers)
|
||||
|
||||
parts = []
|
||||
@@ -27,9 +25,7 @@ class ResponseTest < ActiveSupport::TestCase
|
||||
status, headers, body = @response.to_a
|
||||
assert_equal 200, status
|
||||
assert_equal({
|
||||
"Content-Type" => "text/html; charset=utf-8",
|
||||
"Cache-Control" => "max-age=0, private, must-revalidate",
|
||||
"ETag" => '"ebb5e89e8a94e9dd22abf5d915d112b2"'
|
||||
"Content-Type" => "text/html; charset=utf-8"
|
||||
}, headers)
|
||||
end
|
||||
|
||||
@@ -41,8 +37,7 @@ class ResponseTest < ActiveSupport::TestCase
|
||||
status, headers, body = @response.to_a
|
||||
assert_equal 200, status
|
||||
assert_equal({
|
||||
"Content-Type" => "text/html; charset=utf-8",
|
||||
"Cache-Control" => "no-cache"
|
||||
"Content-Type" => "text/html; charset=utf-8"
|
||||
}, headers)
|
||||
|
||||
parts = []
|
||||
|
||||
@@ -53,18 +53,6 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
|
||||
def rescue_action(e) raise end
|
||||
end
|
||||
|
||||
def test_raises_argument_error_if_missing_session_key
|
||||
assert_raise(ArgumentError, nil.inspect) {
|
||||
ActionDispatch::Session::CookieStore.new(nil,
|
||||
:key => nil, :secret => SessionSecret)
|
||||
}
|
||||
|
||||
assert_raise(ArgumentError, ''.inspect) {
|
||||
ActionDispatch::Session::CookieStore.new(nil,
|
||||
:key => '', :secret => SessionSecret)
|
||||
}
|
||||
end
|
||||
|
||||
def test_setting_session_value
|
||||
with_test_route_set do
|
||||
get '/set_session_value'
|
||||
|
||||
@@ -1,19 +1,5 @@
|
||||
require 'abstract_unit'
|
||||
|
||||
module ActionDispatch
|
||||
class ShowExceptions
|
||||
private
|
||||
def public_path
|
||||
"#{FIXTURE_LOAD_PATH}/public"
|
||||
end
|
||||
|
||||
# Silence logger
|
||||
def logger
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class ShowExceptionsTest < ActionDispatch::IntegrationTest
|
||||
Boomer = lambda do |env|
|
||||
req = ActionDispatch::Request.new(env)
|
||||
|
||||
@@ -2,30 +2,37 @@ require 'abstract_unit'
|
||||
|
||||
module StaticTests
|
||||
def test_serves_dynamic_content
|
||||
assert_equal "Hello, World!", get("/nofile")
|
||||
assert_equal "Hello, World!", get("/nofile").body
|
||||
end
|
||||
|
||||
def test_serves_static_index_at_root
|
||||
assert_equal "/index.html", get("/index.html")
|
||||
assert_equal "/index.html", get("/index")
|
||||
assert_equal "/index.html", get("/")
|
||||
assert_html "/index.html", get("/index.html")
|
||||
assert_html "/index.html", get("/index")
|
||||
assert_html "/index.html", get("/")
|
||||
assert_html "/index.html", get("")
|
||||
end
|
||||
|
||||
def test_serves_static_file_in_directory
|
||||
assert_equal "/foo/bar.html", get("/foo/bar.html")
|
||||
assert_equal "/foo/bar.html", get("/foo/bar/")
|
||||
assert_equal "/foo/bar.html", get("/foo/bar")
|
||||
assert_html "/foo/bar.html", get("/foo/bar.html")
|
||||
assert_html "/foo/bar.html", get("/foo/bar/")
|
||||
assert_html "/foo/bar.html", get("/foo/bar")
|
||||
end
|
||||
|
||||
def test_serves_static_index_file_in_directory
|
||||
assert_equal "/foo/index.html", get("/foo/index.html")
|
||||
assert_equal "/foo/index.html", get("/foo/")
|
||||
assert_equal "/foo/index.html", get("/foo")
|
||||
assert_html "/foo/index.html", get("/foo/index.html")
|
||||
assert_html "/foo/index.html", get("/foo/")
|
||||
assert_html "/foo/index.html", get("/foo")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def assert_html(body, response)
|
||||
assert_equal body, response.body
|
||||
assert_equal "text/html", response.headers["Content-Type"]
|
||||
end
|
||||
|
||||
def get(path)
|
||||
Rack::MockRequest.new(@app).request("GET", path).body
|
||||
Rack::MockRequest.new(@app).request("GET", path)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -59,16 +66,16 @@ class MultipleDirectorisStaticTest < ActiveSupport::TestCase
|
||||
include StaticTests
|
||||
|
||||
test "serves files from other mounted directories" do
|
||||
assert_equal "/blog/index.html", get("/blog/index.html")
|
||||
assert_equal "/blog/index.html", get("/blog/index")
|
||||
assert_equal "/blog/index.html", get("/blog/")
|
||||
assert_html "/blog/index.html", get("/blog/index.html")
|
||||
assert_html "/blog/index.html", get("/blog/index")
|
||||
assert_html "/blog/index.html", get("/blog/")
|
||||
|
||||
assert_equal "/blog/blog.html", get("/blog/blog/")
|
||||
assert_equal "/blog/blog.html", get("/blog/blog.html")
|
||||
assert_equal "/blog/blog.html", get("/blog/blog")
|
||||
assert_html "/blog/blog.html", get("/blog/blog/")
|
||||
assert_html "/blog/blog.html", get("/blog/blog.html")
|
||||
assert_html "/blog/blog.html", get("/blog/blog")
|
||||
|
||||
assert_equal "/blog/subdir/index.html", get("/blog/subdir/index.html")
|
||||
assert_equal "/blog/subdir/index.html", get("/blog/subdir/")
|
||||
assert_equal "/blog/subdir/index.html", get("/blog/subdir")
|
||||
assert_html "/blog/subdir/index.html", get("/blog/subdir/index.html")
|
||||
assert_html "/blog/subdir/index.html", get("/blog/subdir/")
|
||||
assert_html "/blog/subdir/index.html", get("/blog/subdir")
|
||||
end
|
||||
end
|
||||
|
||||
58
actionpack/test/dispatch/uploaded_file_test.rb
Normal file
58
actionpack/test/dispatch/uploaded_file_test.rb
Normal file
@@ -0,0 +1,58 @@
|
||||
require 'abstract_unit'
|
||||
|
||||
module ActionDispatch
|
||||
class UploadedFileTest < ActiveSupport::TestCase
|
||||
def test_constructor_with_argument_error
|
||||
assert_raises(ArgumentError) do
|
||||
Http::UploadedFile.new({})
|
||||
end
|
||||
end
|
||||
|
||||
def test_original_filename
|
||||
uf = Http::UploadedFile.new(:filename => 'foo', :tempfile => Object.new)
|
||||
assert_equal 'foo', uf.original_filename
|
||||
end
|
||||
|
||||
def test_content_type
|
||||
uf = Http::UploadedFile.new(:type => 'foo', :tempfile => Object.new)
|
||||
assert_equal 'foo', uf.content_type
|
||||
end
|
||||
|
||||
def test_headers
|
||||
uf = Http::UploadedFile.new(:head => 'foo', :tempfile => Object.new)
|
||||
assert_equal 'foo', uf.headers
|
||||
end
|
||||
|
||||
def test_tempfile
|
||||
uf = Http::UploadedFile.new(:tempfile => 'foo')
|
||||
assert_equal 'foo', uf.tempfile
|
||||
end
|
||||
|
||||
def test_delegates_to_tempfile
|
||||
tf = Class.new { def read; 'thunderhorse' end }
|
||||
uf = Http::UploadedFile.new(:tempfile => tf.new)
|
||||
assert_equal 'thunderhorse', uf.read
|
||||
end
|
||||
|
||||
def test_delegates_to_tempfile_with_params
|
||||
tf = Class.new { def read *args; args end }
|
||||
uf = Http::UploadedFile.new(:tempfile => tf.new)
|
||||
assert_equal %w{ thunder horse }, uf.read(*%w{ thunder horse })
|
||||
end
|
||||
|
||||
def test_delegate_respects_respond_to?
|
||||
tf = Class.new { def read; yield end; private :read }
|
||||
uf = Http::UploadedFile.new(:tempfile => tf.new)
|
||||
assert_raises(NoMethodError) do
|
||||
uf.read
|
||||
end
|
||||
end
|
||||
|
||||
def test_respond_to?
|
||||
tf = Class.new { def read; yield end }
|
||||
uf = Http::UploadedFile.new(:tempfile => tf.new)
|
||||
assert uf.respond_to?(:headers), 'responds to headers'
|
||||
assert uf.respond_to?(:read), 'responds to read'
|
||||
end
|
||||
end
|
||||
end
|
||||
1
actionpack/test/fixtures/layout_tests/alt/hello.erb
vendored
Normal file
1
actionpack/test/fixtures/layout_tests/alt/hello.erb
vendored
Normal file
@@ -0,0 +1 @@
|
||||
alt/hello.erb
|
||||
@@ -1 +0,0 @@
|
||||
alt/hello.rhtml
|
||||
1
actionpack/test/fixtures/layout_tests/layouts/controller_name_space/nested.erb
vendored
Normal file
1
actionpack/test/fixtures/layout_tests/layouts/controller_name_space/nested.erb
vendored
Normal file
@@ -0,0 +1 @@
|
||||
controller_name_space/nested.erb <%= yield %>
|
||||
@@ -1 +0,0 @@
|
||||
controller_name_space/nested.rhtml <%= yield %>
|
||||
1
actionpack/test/fixtures/layout_tests/layouts/item.erb
vendored
Normal file
1
actionpack/test/fixtures/layout_tests/layouts/item.erb
vendored
Normal file
@@ -0,0 +1 @@
|
||||
item.erb <%= yield %>
|
||||
@@ -1 +0,0 @@
|
||||
item.rhtml <%= yield %>
|
||||
1
actionpack/test/fixtures/layout_tests/layouts/layout_test.erb
vendored
Normal file
1
actionpack/test/fixtures/layout_tests/layouts/layout_test.erb
vendored
Normal file
@@ -0,0 +1 @@
|
||||
layout_test.erb <%= yield %>
|
||||
@@ -1 +0,0 @@
|
||||
layout_test.rhtml <%= yield %>
|
||||
1
actionpack/test/fixtures/layout_tests/views/goodbye.erb
vendored
Normal file
1
actionpack/test/fixtures/layout_tests/views/goodbye.erb
vendored
Normal file
@@ -0,0 +1 @@
|
||||
hello.erb
|
||||
@@ -1 +0,0 @@
|
||||
hello.rhtml
|
||||
1
actionpack/test/fixtures/layout_tests/views/hello.erb
vendored
Normal file
1
actionpack/test/fixtures/layout_tests/views/hello.erb
vendored
Normal file
@@ -0,0 +1 @@
|
||||
hello.erb
|
||||
@@ -1 +0,0 @@
|
||||
hello.rhtml
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user