Resolved conflict

This commit is contained in:
David Heinemeier Hansson
2008-07-16 17:45:28 -05:00
217 changed files with 5080 additions and 1990 deletions

View File

@@ -426,7 +426,7 @@ module ActionMailer #:nodoc:
end
def template_root=(root)
write_inheritable_attribute(:template_root, ActionView::ViewLoadPaths.new(Array(root)))
write_inheritable_attribute(:template_root, ActionView::PathSet.new(Array(root)))
end
end

View File

@@ -38,7 +38,7 @@ module TMail
# = Class Address
#
# Provides a complete handling library for email addresses. Can parse a string of an
# address directly or take in preformatted addresses themseleves. Allows you to add
# address directly or take in preformatted addresses themselves. Allows you to add
# and remove phrases from the front of the address and provides a compare function for
# email addresses.
#
@@ -143,7 +143,7 @@ module TMail
# This is to catch an unquoted "@" symbol in the local part of the
# address. Handles addresses like <"@"@me.com> and makes sure they
# stay like <"@"@me.com> (previously were becomming <@@me.com>)
# stay like <"@"@me.com> (previously were becoming <@@me.com>)
if local && (local.join == '@' || local.join =~ /\A[^"].*?@.*?[^"]\Z/)
@local = "\"#{local.join}\""
else

View File

@@ -59,7 +59,7 @@ module TMail
#
# This is because a mailbox doesn't have the : after the From that designates the
# beginning of the envelope sender (which can be different to the from address of
# the emial)
# the email)
#
# Other fields can be passed as normal, "Reply-To", "Received" etc.
#

View File

@@ -42,7 +42,7 @@ module TMail
# Allows you to query the mail object with a string to get the contents
# of the field you want.
#
# Returns a string of the exact contnts of the field
# Returns a string of the exact contents of the field
#
# mail.from = "mikel <mikel@lindsaar.net>"
# mail.header_string("From") #=> "mikel <mikel@lindsaar.net>"

View File

@@ -255,7 +255,7 @@ module TMail
alias fetch []
# Allows you to set or delete TMail header objects at will.
# Eamples:
# Examples:
# @mail = TMail::Mail.new
# @mail['to'].to_s # => 'mikel@test.com.au'
# @mail['to'] = 'mikel@elsewhere.org'
@@ -265,7 +265,7 @@ module TMail
# @mail['to'].to_s # => nil
# @mail.encoded # => "\r\n"
#
# Note: setting mail[] = nil actualy deletes the header field in question from the object,
# Note: setting mail[] = nil actually deletes the header field in question from the object,
# it does not just set the value of the hash to nil
def []=( key, val )
dkey = key.downcase

View File

@@ -32,8 +32,7 @@ end
# Wrap tests that use Mocha and skip if unavailable.
def uses_mocha(test_name)
gem 'mocha', ">=0.5"
require 'stubba'
gem 'mocha', ">=0.9.0"
yield
rescue Gem::LoadError
$stderr.puts "Skipping #{test_name} tests (Mocha >= 0.5 is required). `gem install mocha` and try again."

View File

@@ -2,6 +2,43 @@
* Fixed that AssetTagHelper#compute_public_path shouldn't cache the asset_host along with the source or per-request proc's won't run [DHH]
* Removed config.action_view.cache_template_loading, use config.cache_classes instead [Josh Peek]
* Get buffer for fragment cache from template's @output_buffer [Josh Peek]
* Set config.action_view.warn_cache_misses = true to receive a warning if you perform an action that results in an expensive disk operation that could be cached [Josh Peek]
* Refactor template preloading. New abstractions include Renderable mixins and a refactored Template class [Josh Peek]
* Changed ActionView::TemplateHandler#render API method signature to render(template, local_assigns = {}) [Josh Peek]
* Changed PrototypeHelper#submit_to_remote to PrototypeHelper#button_to_remote to stay consistent with link_to_remote (submit_to_remote still works as an alias) #8994 [clemens]
* Add :recursive option to javascript_include_tag and stylesheet_link_tag to be used along with :all. #480 [Damian Janowski]
* Allow users to disable the use of the Accept header [Michael Koziarski]
The accept header is poorly implemented by browsers and causes strange
errors when used on public sites where crawlers make requests too. You
can use formatted urls (e.g. /people/1.xml) to support API clients in a
much simpler way.
To disable the header you need to set:
config.action_controller.use_accept_header = false
* Do not stat template files in production mode before rendering. You will no longer be able to modify templates in production mode without restarting the server [Josh Peek]
* Deprecated TemplateHandler line offset [Josh Peek]
* Allow caches_action to accept cache store options. #416. [José Valim]. Example:
caches_action :index, :redirected, :if => Proc.new { |c| !c.request.format.json? }, :expires_in => 1.hour
* Remove define_javascript_functions, javascript_include_tag and friends are far superior. [Michael Koziarski]
* Deprecate :use_full_path render option. The supplying the option no longer has an effect [Josh Peek]
* Add :as option to render a collection of partials with a custom local variable name. #509 [Simon Jefford, Pratik Naik]
render :partial => 'other_people', :collection => @people, :as => :person
@@ -12,6 +49,16 @@
* Made ActionView::Base#render_file private [Josh Peek]
* Refactor and simplify the implementation of assert_redirected_to. Arguments are now normalised relative to the controller being tested, not the root of the application. [Michael Koziarski]
This could cause some erroneous test failures if you were redirecting between controllers
in different namespaces and wrote your assertions relative to the root of the application.
* Remove follow_redirect from controller functional tests.
If you want to follow redirects you can use integration tests. The functional test
version was only useful if you were using redirect_to :id=>...
* Fix polymorphic_url with singleton resources. #461 [Tammer Saleh]
* Replaced TemplateFinder abstraction with ViewLoadPaths [Josh Peek]

View File

@@ -31,7 +31,7 @@ http://www.rubyonrails.org.
A short rundown of the major features:
* Actions grouped in controller as methods instead of separate command objects
and can therefore share helper methods.
and can therefore share helper methods
BlogController < ActionController::Base
def show
@@ -168,7 +168,7 @@ A short rundown of the major features:
{Learn more}[link:classes/ActionController/Base.html]
* Javascript and Ajax integration.
* Javascript and Ajax integration
link_to_function "Greeting", "alert('Hello world!')"
link_to_remote "Delete this post", :update => "posts",
@@ -177,7 +177,7 @@ A short rundown of the major features:
{Learn more}[link:classes/ActionView/Helpers/JavaScriptHelper.html]
* Pagination for navigating lists of results.
* Pagination for navigating lists of results
# controller
def list
@@ -192,15 +192,9 @@ A short rundown of the major features:
{Learn more}[link:classes/ActionController/Pagination.html]
* Easy testing of both controller and template result through TestRequest/Response
class LoginControllerTest < Test::Unit::TestCase
def setup
@controller = LoginController.new
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
end
* Easy testing of both controller and rendered template through ActionController::TestCase
class LoginControllerTest < ActionController::TestCase
def test_failing_authenticate
process :authenticate, :user_name => "nop", :password => ""
assert flash.has_key?(:alert)
@@ -208,7 +202,7 @@ A short rundown of the major features:
end
end
{Learn more}[link:classes/ActionController/TestRequest.html]
{Learn more}[link:classes/ActionController/TestCase.html]
* Automated benchmarking and integrated logging

View File

@@ -56,74 +56,24 @@ module ActionController
# # assert that the redirection was to the named route login_url
# assert_redirected_to login_url
#
# # assert that the redirection was to the url for @customer
# assert_redirected_to @customer
#
def assert_redirected_to(options = {}, message=nil)
clean_backtrace do
assert_response(:redirect, message)
return true if options == @response.redirected_to
ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty?
# Support partial arguments for hash redirections
if options.is_a?(Hash) && @response.redirected_to.is_a?(Hash)
return true if options.all? {|(key, value)| @response.redirected_to[key] == value}
end
redirected_to_after_normalisation = normalize_argument_to_redirection(@response.redirected_to)
options_after_normalisation = normalize_argument_to_redirection(options)
begin
url = {}
original = { :expected => options, :actual => @response.redirected_to.is_a?(Symbol) ? @response.redirected_to : @response.redirected_to.dup }
original.each do |key, value|
if value.is_a?(Symbol)
value = @controller.respond_to?(value, true) ? @controller.send(value) : @controller.send("hash_for_#{value}_url")
end
unless value.is_a?(Hash)
request = case value
when NilClass then nil
when /^\w+:\/\// then recognized_request_for(%r{^(\w+://.*?(/|$|\?))(.*)$} =~ value ? $3 : nil)
else recognized_request_for(value)
end
value = request.path_parameters if request
end
if value.is_a?(Hash) # stringify 2 levels of hash keys
if name = value.delete(:use_route)
route = ActionController::Routing::Routes.named_routes[name]
value.update(route.parameter_shell)
end
value.stringify_keys!
value.values.select { |v| v.is_a?(Hash) }.collect { |v| v.stringify_keys! }
if key == :expected && value['controller'] == @controller.controller_name && original[:actual].is_a?(Hash)
original[:actual].stringify_keys!
value.delete('controller') if original[:actual]['controller'].nil? || original[:actual]['controller'] == value['controller']
end
end
if value.respond_to?(:[]) && value['controller']
value['controller'] = value['controller'].to_s
if key == :actual && value['controller'].first != '/' && !value['controller'].include?('/')
new_controller_path = ActionController::Routing.controller_relative_to(value['controller'], @controller.class.controller_path)
value['controller'] = new_controller_path if value['controller'] != new_controller_path && ActionController::Routing.possible_controllers.include?(new_controller_path) && @response.redirected_to.is_a?(Hash)
end
value['controller'] = value['controller'][1..-1] if value['controller'].first == '/' # strip leading hash
end
url[key] = value
end
@response_diff = url[:actual].diff(url[:expected]) if url[:actual]
msg = build_message(message, "expected a redirect to <?>, found one to <?>, a difference of <?> ", url[:expected], url[:actual], @response_diff)
assert_block(msg) do
url[:expected].keys.all? do |k|
if k == :controller then url[:expected][k] == ActionController::Routing.controller_relative_to(url[:actual][k], @controller.class.controller_path)
else parameterize(url[:expected][k]) == parameterize(url[:actual][k])
end
end
end
rescue ActionController::RoutingError # routing failed us, so match the strings only.
msg = build_message(message, "expected a redirect to <?>, found one to <?>", options, @response.redirect_url)
url_regexp = %r{^(\w+://.*?(/|$|\?))(.*)$}
eurl, epath, url, path = [options, @response.redirect_url].collect do |url|
u, p = (url_regexp =~ url) ? [$1, $3] : [nil, url]
[u, (p.first == '/') ? p : '/' + p]
end.flatten
assert_equal(eurl, url, msg) if eurl && url
assert_equal(epath, path, msg) if epath && path
if redirected_to_after_normalisation != options_after_normalisation
flunk "Expected response to be a redirect to <#{options_after_normalisation}> but was a redirect to <#{redirected_to_after_normalisation}>"
end
end
end
@@ -137,36 +87,37 @@ module ActionController
#
def assert_template(expected = nil, message=nil)
clean_backtrace do
rendered = expected ? @response.rendered_file(!expected.include?('/')) : @response.rendered_file
rendered = @response.rendered_template
msg = build_message(message, "expecting <?> but rendering with <?>", expected, rendered)
assert_block(msg) do
if expected.nil?
!@response.rendered_with_file?
@response.rendered_template.nil?
else
expected == rendered
rendered.to_s.match(expected)
end
end
end
end
private
# Recognizes the route for a given path.
def recognized_request_for(path, request_method = nil)
path = "/#{path}" unless path.first == '/'
# Assume given controller
request = ActionController::TestRequest.new({}, {}, nil)
request.env["REQUEST_METHOD"] = request_method.to_s.upcase if request_method
request.path = path
ActionController::Routing::Routes.recognize(request)
request
end
# Proxy to to_param if the object will respond to it.
def parameterize(value)
value.respond_to?(:to_param) ? value.to_param : value
end
def normalize_argument_to_redirection(fragment)
after_routing = @controller.url_for(fragment)
if after_routing =~ %r{^\w+://.*}
after_routing
else
# FIXME - this should probably get removed.
if after_routing.first != '/'
after_routing = '/' + after_routing
end
@request.protocol + @request.host_with_port + after_routing
end
end
end
end
end

View File

@@ -21,10 +21,8 @@ module ActionController
# from the response HTML or elements selected by the enclosing assertion.
#
# In addition to HTML responses, you can make the following assertions:
# * +assert_select_rjs+ - Assertions on HTML content of RJS update and
# insertion operations.
# * +assert_select_encoded+ - Assertions on HTML encoded inside XML,
# for example for dealing with feed item descriptions.
# * +assert_select_rjs+ - Assertions on HTML content of RJS update and insertion operations.
# * +assert_select_encoded+ - Assertions on HTML encoded inside XML, for example for dealing with feed item descriptions.
# * +assert_select_email+ - Assertions on the HTML body of an e-mail.
#
# Also see HTML::Selector to learn how to use selectors.

View File

@@ -340,6 +340,16 @@ module ActionController #:nodoc:
cattr_accessor :optimise_named_routes
self.optimise_named_routes = true
# Indicates whether the response format should be determined by examining the Accept HTTP header,
# or by using the simpler params + ajax rules.
#
# If this is set to +true+ (the default) then +respond_to+ and +Request#format+ will take the Accept
# header into account. If it is set to false then the request format will be determined solely
# by examining params[:format]. If params format is missing, the format will be either HTML or
# Javascript depending on whether the request is an AJAX request.
cattr_accessor :use_accept_header
self.use_accept_header = true
# Controls whether request forgergy protection is turned on or not. Turned off by default only in test mode.
class_inheritable_accessor :allow_forgery_protection
self.allow_forgery_protection = true
@@ -402,7 +412,7 @@ module ActionController #:nodoc:
# More methods can be hidden using <tt>hide_actions</tt>.
def hidden_actions
unless read_inheritable_attribute(:hidden_actions)
write_inheritable_attribute(:hidden_actions, ActionController::Base.public_instance_methods.map(&:to_s))
write_inheritable_attribute(:hidden_actions, ActionController::Base.public_instance_methods.map { |m| m.to_s })
end
read_inheritable_attribute(:hidden_actions)
@@ -410,18 +420,18 @@ module ActionController #:nodoc:
# Hide each of the given methods from being callable as actions.
def hide_action(*names)
write_inheritable_attribute(:hidden_actions, hidden_actions | names.map(&:to_s))
write_inheritable_attribute(:hidden_actions, hidden_actions | names.map { |name| name.to_s })
end
## View load paths determine the bases from which template references can be made. So a call to
## render("test/template") will be looked up in the view load paths array and the closest match will be
## returned.
# View load paths determine the bases from which template references can be made. So a call to
# render("test/template") will be looked up in the view load paths array and the closest match will be
# returned.
def view_paths
@view_paths || superclass.view_paths
end
def view_paths=(value)
@view_paths = ActionView::ViewLoadPaths.new(Array(value)) if value
@view_paths = ActionView::Base.process_view_paths(value) if value
end
# Adds a view_path to the front of the view_paths array.
@@ -603,7 +613,8 @@ module ActionController #:nodoc:
#
# This takes the current URL as is and only exchanges the action. In contrast, <tt>url_for :action => 'print'</tt>
# would have slashed-off the path components after the changed action.
def url_for(options = {}) #:doc:
def url_for(options = {})
options ||= {}
case options
when String
options
@@ -641,7 +652,7 @@ module ActionController #:nodoc:
end
def view_paths=(value)
@template.view_paths = ViewLoadPaths.new(value)
@template.view_paths = ActionView::Base.process_view_paths(value)
end
# Adds a view_path to the front of the view_paths array.
@@ -858,7 +869,7 @@ module ActionController #:nodoc:
else
if file = options[:file]
render_for_file(file, options[:status], options[:use_full_path], options[:locals] || {})
render_for_file(file, options[:status], nil, options[:locals] || {})
elsif template = options[:template]
render_for_file(template, options[:status], true, options[:locals] || {})
@@ -870,9 +881,9 @@ module ActionController #:nodoc:
elsif action_name = options[:action]
template = default_template_name(action_name.to_s)
if options[:layout] && !template_exempt_from_layout?(template)
render_with_a_layout(:file => template, :status => options[:status], :use_full_path => true, :layout => true)
render_with_a_layout(:file => template, :status => options[:status], :layout => true)
else
render_with_no_layout(:file => template, :status => options[:status], :use_full_path => true)
render_with_no_layout(:file => template, :status => options[:status])
end
elsif xml = options[:xml]
@@ -897,7 +908,7 @@ module ActionController #:nodoc:
else
render_for_text(
@template.send!(:render_partial, partial,
ActionView::Base::ObjectWrapper.new(options[:object]), options[:locals]), options[:status]
options[:object], options[:locals]), options[:status]
)
end
@@ -1042,29 +1053,31 @@ module ActionController #:nodoc:
status = 302
end
response.redirected_to= options
logger.info("Redirected to #{options}") if logger && logger.info?
case options
when %r{^\w+://.*}
raise DoubleRenderError if performed?
logger.info("Redirected to #{options}") if logger && logger.info?
response.redirect(options, interpret_status(status))
response.redirected_to = options
@performed_redirect = true
redirect_to_full_url(options, status)
when String
redirect_to(request.protocol + request.host_with_port + options, :status=>status)
redirect_to_full_url(request.protocol + request.host_with_port + options, status)
when :back
request.env["HTTP_REFERER"] ? redirect_to(request.env["HTTP_REFERER"], :status=>status) : raise(RedirectBackError)
when Hash
redirect_to(url_for(options), :status=>status)
response.redirected_to = options
if referer = request.headers["Referer"]
redirect_to(referer, :status=>status)
else
raise RedirectBackError
end
else
redirect_to(url_for(options), :status=>status)
redirect_to_full_url(url_for(options), status)
end
end
def redirect_to_full_url(url, status)
raise DoubleRenderError if performed?
response.redirect(url, interpret_status(status))
@performed_redirect = true
end
# Sets a HTTP 1.1 Cache-Control header. Defaults to issuing a "private" instruction, so that
# intermediate caches shouldn't cache the response.
#
@@ -1097,10 +1110,10 @@ module ActionController #:nodoc:
private
def render_for_file(template_path, status = nil, use_full_path = false, locals = {}) #:nodoc:
def render_for_file(template_path, status = nil, use_full_path = nil, locals = {}) #:nodoc:
add_variables_to_assigns
logger.info("Rendering #{template_path}" + (status ? " (#{status})" : '')) if logger
render_for_text(@template.render(:file => template_path, :use_full_path => use_full_path, :locals => locals), status)
render_for_text(@template.render(:file => template_path, :locals => locals), status)
end
def render_for_text(text = nil, status = nil, append_response = false) #:nodoc:
@@ -1188,7 +1201,7 @@ module ActionController #:nodoc:
end
def self.action_methods
@action_methods ||= Set.new(public_instance_methods.map(&:to_s)) - hidden_actions
@action_methods ||= Set.new(public_instance_methods.map { |m| m.to_s }) - hidden_actions
end
def add_variables_to_assigns
@@ -1235,8 +1248,8 @@ module ActionController #:nodoc:
end
def template_exempt_from_layout?(template_name = default_template_name)
template_name = @template.send(:template_file_from_name, template_name) if @template
@@exempt_from_layout.any? { |ext| template_name.to_s =~ ext }
template_name = @template.pick_template(template_name).to_s if @template
@@exempt_from_layout.any? { |ext| template_name =~ ext }
end
def default_template_name(action_name = self.action_name)

View File

@@ -27,13 +27,15 @@ module ActionController #:nodoc:
# You can set modify the default action cache path by passing a :cache_path option. This will be passed directly to ActionCachePath.path_for. This is handy
# for actions with multiple possible routes that should be cached differently. If a block is given, it is called with the current controller instance.
#
# And you can also use :if to pass a Proc that specifies when the action should be cached.
# And you can also use :if (or :unless) to pass a Proc that specifies when the action should be cached.
#
# Finally, if you are using memcached, you can also pass :expires_in.
#
# class ListsController < ApplicationController
# before_filter :authenticate, :except => :public
# caches_page :public
# caches_action :index, :if => Proc.new { |c| !c.request.format.json? } # cache if is not a JSON request
# caches_action :show, :cache_path => { :project => 1 }
# caches_action :show, :cache_path => { :project => 1 }, :expires_in => 1.hour
# caches_action :feed, :cache_path => Proc.new { |controller|
# controller.params[:user_id] ?
# controller.send(:user_list_url, c.params[:user_id], c.params[:id]) :
@@ -56,8 +58,10 @@ module ActionController #:nodoc:
def caches_action(*actions)
return unless cache_configured?
options = actions.extract_options!
cache_filter = ActionCacheFilter.new(:layout => options.delete(:layout), :cache_path => options.delete(:cache_path))
around_filter(cache_filter, {:only => actions}.merge(options))
filter_options = { :only => actions, :if => options.delete(:if), :unless => options.delete(:unless) }
cache_filter = ActionCacheFilter.new(:layout => options.delete(:layout), :cache_path => options.delete(:cache_path), :store_options => options)
around_filter(cache_filter, filter_options)
end
end
@@ -80,8 +84,8 @@ module ActionController #:nodoc:
end
def before(controller)
cache_path = ActionCachePath.new(controller, path_options_for(controller, @options))
if cache = controller.read_fragment(cache_path.path)
cache_path = ActionCachePath.new(controller, path_options_for(controller, @options.slice(:cache_path)))
if cache = controller.read_fragment(cache_path.path, @options[:store_options])
controller.rendered_action_cache = true
set_content_type!(controller, cache_path.extension)
options = { :text => cache }
@@ -96,7 +100,7 @@ module ActionController #:nodoc:
def after(controller)
return if controller.rendered_action_cache || !caching_allowed(controller)
action_content = cache_layout? ? content_for_layout(controller) : controller.response.body
controller.write_fragment(controller.action_cache_path.path, action_content)
controller.write_fragment(controller.action_cache_path.path, action_content, @options[:store_options])
end
private
@@ -162,10 +166,7 @@ module ActionController #:nodoc:
# If there's no extension in the path, check request.format
if extension.nil?
extension = request.format.to_sym.to_s
if extension=='all'
extension = nil
end
extension = request.cache_format
end
extension
end

View File

@@ -2,7 +2,7 @@ module ActionController #:nodoc:
module Caching
# Fragment caching is used for caching various blocks within templates without caching the entire action as a whole. This is useful when
# certain elements of an action change frequently or depend on complicated state while other parts rarely change or can be shared amongst multiple
# parties. The caching is doing using the cache helper available in the Action View. A template with caching might look something like:
# parties. The caching is done using the cache helper available in the Action View. A template with caching might look something like:
#
# <b>Hello <%= @name %></b>
# <% cache do %>
@@ -60,10 +60,8 @@ module ActionController #:nodoc:
ActiveSupport::Cache.expand_cache_key(key.is_a?(Hash) ? url_for(key).split("://").last : key, :views)
end
def fragment_for(block, name = {}, options = nil) #:nodoc:
def fragment_for(buffer, name = {}, options = nil, &block) #:nodoc:
if perform_caching
buffer = yield
if cache = read_fragment(name, options)
buffer.concat(cache)
else

View File

@@ -22,6 +22,16 @@ module ActionController #:nodoc:
#
# cookies.delete :user_name
#
# Please note that if you specify a :domain when setting a cookie, you must also specify the domain when deleting the cookie:
#
# cookies[:key] = {
# :value => 'a yummy cookie',
# :expires => 1.year.from_now,
# :domain => 'domain.com'
# }
#
# cookies.delete(:key, :domain => 'domain.com')
#
# The option symbols for setting cookies are:
#
# * <tt>:value</tt> - The cookie's value or list of values (as an array).

View File

@@ -7,6 +7,225 @@ module ActionController #:nodoc:
end
end
class FilterChain < ActiveSupport::Callbacks::CallbackChain #:nodoc:
def append_filter_to_chain(filters, filter_type, &block)
pos = find_filter_append_position(filters, filter_type)
update_filter_chain(filters, filter_type, pos, &block)
end
def prepend_filter_to_chain(filters, filter_type, &block)
pos = find_filter_prepend_position(filters, filter_type)
update_filter_chain(filters, filter_type, pos, &block)
end
def create_filters(filters, filter_type, &block)
filters, conditions = extract_options(filters, &block)
filters.map! { |filter| find_or_create_filter(filter, filter_type, conditions) }
filters
end
def skip_filter_in_chain(*filters, &test)
filters, conditions = extract_options(filters)
filters.each do |filter|
if callback = find(filter) then delete(callback) end
end if conditions.empty?
update_filter_in_chain(filters, :skip => conditions, &test)
end
private
def update_filter_chain(filters, filter_type, pos, &block)
new_filters = create_filters(filters, filter_type, &block)
insert(pos, new_filters).flatten!
end
def find_filter_append_position(filters, filter_type)
# appending an after filter puts it at the end of the call chain
# before and around filters go before the first after filter in the chain
unless filter_type == :after
each_with_index do |f,i|
return i if f.after?
end
end
return -1
end
def find_filter_prepend_position(filters, filter_type)
# prepending a before or around filter puts it at the front of the call chain
# after filters go before the first after filter in the chain
if filter_type == :after
each_with_index do |f,i|
return i if f.after?
end
return -1
end
return 0
end
def find_or_create_filter(filter, filter_type, options = {})
update_filter_in_chain([filter], options)
if found_filter = find(filter) { |f| f.type == filter_type }
found_filter
else
filter_kind = case
when filter.respond_to?(:before) && filter_type == :before
:before
when filter.respond_to?(:after) && filter_type == :after
:after
else
:filter
end
case filter_type
when :before
BeforeFilter.new(filter_kind, filter, options)
when :after
AfterFilter.new(filter_kind, filter, options)
else
AroundFilter.new(filter_kind, filter, options)
end
end
end
def update_filter_in_chain(filters, options, &test)
filters.map! { |f| block_given? ? find(f, &test) : find(f) }
filters.compact!
map! do |filter|
if filters.include?(filter)
new_filter = filter.dup
new_filter.update_options!(options)
new_filter
else
filter
end
end
end
end
class Filter < ActiveSupport::Callbacks::Callback #:nodoc:
def initialize(kind, method, options = {})
super
update_options! options
end
def before?
self.class == BeforeFilter
end
def after?
self.class == AfterFilter
end
def around?
self.class == AroundFilter
end
# Make sets of strings from :only/:except options
def update_options!(other)
if other
convert_only_and_except_options_to_sets_of_strings(other)
if other[:skip]
convert_only_and_except_options_to_sets_of_strings(other[:skip])
end
end
options.update(other)
end
private
def should_not_skip?(controller)
if options[:skip]
!included_in_action?(controller, options[:skip])
else
true
end
end
def included_in_action?(controller, options)
if options[:only]
options[:only].include?(controller.action_name)
elsif options[:except]
!options[:except].include?(controller.action_name)
else
true
end
end
def should_run_callback?(controller)
should_not_skip?(controller) && included_in_action?(controller, options) && super
end
def convert_only_and_except_options_to_sets_of_strings(opts)
[:only, :except].each do |key|
if values = opts[key]
opts[key] = Array(values).map(&:to_s).to_set
end
end
end
end
class AroundFilter < Filter #:nodoc:
def type
:around
end
def call(controller, &block)
if should_run_callback?(controller)
method = filter_responds_to_before_and_after? ? around_proc : self.method
# For around_filter do |controller, action|
if method.is_a?(Proc) && method.arity == 2
evaluate_method(method, controller, block)
else
evaluate_method(method, controller, &block)
end
else
block.call
end
end
private
def filter_responds_to_before_and_after?
method.respond_to?(:before) && method.respond_to?(:after)
end
def around_proc
Proc.new do |controller, action|
method.before(controller)
if controller.send!(:performed?)
controller.send!(:halt_filter_chain, method, :rendered_or_redirected)
else
begin
action.call
ensure
method.after(controller)
end
end
end
end
end
class BeforeFilter < Filter #:nodoc:
def type
:before
end
def call(controller, &block)
super
if controller.send!(:performed?)
controller.send!(:halt_filter_chain, method, :rendered_or_redirected)
end
end
end
class AfterFilter < Filter #:nodoc:
def type
:after
end
end
# Filters enable controllers to run shared pre- and post-processing code for its actions. These filters can be used to do
# authentication, caching, or auditing before the intended action is performed. Or to do localization or output
# compression after the action has been performed. Filters have access to the request, response, and all the instance
@@ -245,201 +464,6 @@ module ActionController #:nodoc:
# filter and controller action will not be run. If +before+ renders or redirects,
# the second half of +around+ and will still run but +after+ and the
# action will not. If +around+ fails to yield, +after+ will not be run.
class FilterChain < ActiveSupport::Callbacks::CallbackChain #:nodoc:
def append_filter_to_chain(filters, filter_type, &block)
pos = find_filter_append_position(filters, filter_type)
update_filter_chain(filters, filter_type, pos, &block)
end
def prepend_filter_to_chain(filters, filter_type, &block)
pos = find_filter_prepend_position(filters, filter_type)
update_filter_chain(filters, filter_type, pos, &block)
end
def create_filters(filters, filter_type, &block)
filters, conditions = extract_options(filters, &block)
filters.map! { |filter| find_or_create_filter(filter, filter_type, conditions) }
filters
end
def skip_filter_in_chain(*filters, &test)
filters, conditions = extract_options(filters)
filters.each do |filter|
if callback = find(filter) then delete(callback) end
end if conditions.empty?
update_filter_in_chain(filters, :skip => conditions, &test)
end
private
def update_filter_chain(filters, filter_type, pos, &block)
new_filters = create_filters(filters, filter_type, &block)
insert(pos, new_filters).flatten!
end
def find_filter_append_position(filters, filter_type)
# appending an after filter puts it at the end of the call chain
# before and around filters go before the first after filter in the chain
unless filter_type == :after
each_with_index do |f,i|
return i if f.after?
end
end
return -1
end
def find_filter_prepend_position(filters, filter_type)
# prepending a before or around filter puts it at the front of the call chain
# after filters go before the first after filter in the chain
if filter_type == :after
each_with_index do |f,i|
return i if f.after?
end
return -1
end
return 0
end
def find_or_create_filter(filter, filter_type, options = {})
update_filter_in_chain([filter], options)
if found_filter = find(filter) { |f| f.type == filter_type }
found_filter
else
filter_kind = case
when filter.respond_to?(:before) && filter_type == :before
:before
when filter.respond_to?(:after) && filter_type == :after
:after
else
:filter
end
case filter_type
when :before
BeforeFilter.new(filter_kind, filter, options)
when :after
AfterFilter.new(filter_kind, filter, options)
else
AroundFilter.new(filter_kind, filter, options)
end
end
end
def update_filter_in_chain(filters, options, &test)
filters.map! { |f| block_given? ? find(f, &test) : find(f) }
filters.compact!
map! do |filter|
if filters.include?(filter)
new_filter = filter.dup
new_filter.options.merge!(options)
new_filter
else
filter
end
end
end
end
class Filter < ActiveSupport::Callbacks::Callback #:nodoc:
def before?
self.class == BeforeFilter
end
def after?
self.class == AfterFilter
end
def around?
self.class == AroundFilter
end
private
def should_not_skip?(controller)
if options[:skip]
!included_in_action?(controller, options[:skip])
else
true
end
end
def included_in_action?(controller, options)
if options[:only]
Array(options[:only]).map(&:to_s).include?(controller.action_name)
elsif options[:except]
!Array(options[:except]).map(&:to_s).include?(controller.action_name)
else
true
end
end
def should_run_callback?(controller)
should_not_skip?(controller) && included_in_action?(controller, options) && super
end
end
class AroundFilter < Filter #:nodoc:
def type
:around
end
def call(controller, &block)
if should_run_callback?(controller)
method = filter_responds_to_before_and_after? ? around_proc : self.method
# For around_filter do |controller, action|
if method.is_a?(Proc) && method.arity == 2
evaluate_method(method, controller, block)
else
evaluate_method(method, controller, &block)
end
else
block.call
end
end
private
def filter_responds_to_before_and_after?
method.respond_to?(:before) && method.respond_to?(:after)
end
def around_proc
Proc.new do |controller, action|
method.before(controller)
if controller.send!(:performed?)
controller.send!(:halt_filter_chain, method, :rendered_or_redirected)
else
begin
action.call
ensure
method.after(controller)
end
end
end
end
end
class BeforeFilter < Filter #:nodoc:
def type
:before
end
def call(controller, &block)
super
if controller.send!(:performed?)
controller.send!(:halt_filter_chain, method, :rendered_or_redirected)
end
end
end
class AfterFilter < Filter #:nodoc:
def type
:after
end
end
module ClassMethods
# The passed <tt>filters</tt> will be appended to the filter_chain and
# will execute before the action on this controller is performed.

View File

@@ -101,7 +101,7 @@ module ActionController
@https = flag
end
# Return +true+ if the session is mimicing a secure HTTPS request.
# Return +true+ if the session is mimicking a secure HTTPS request.
#
# if session.https?
# ...

View File

@@ -304,7 +304,7 @@ module ActionController #:nodoc:
end
def layout_directory?(layout_name)
@template.view_paths.find_template_file_for_path("#{File.join('layouts', layout_name)}.#{@template.template_format}.erb") ? true : false
@template.file_exists?("#{File.join('layouts', layout_name)}.#{@template.template_format}")
end
end
end

View File

@@ -114,7 +114,11 @@ module ActionController #:nodoc:
@request = controller.request
@response = controller.response
@mime_type_priority = Array(Mime::Type.lookup_by_extension(@request.parameters[:format]) || @request.accepts)
if ActionController::Base.use_accept_header
@mime_type_priority = Array(Mime::Type.lookup_by_extension(@request.parameters[:format]) || @request.accepts)
else
@mime_type_priority = [@request.format]
end
@order = []
@responses = {}

View File

@@ -72,57 +72,61 @@ module Mime
end
def parse(accept_header)
# keep track of creation order to keep the subsequent sort stable
list = []
accept_header.split(/,/).each_with_index do |header, index|
params, q = header.split(/;\s*q=/)
if params
params.strip!
list << AcceptItem.new(index, params, q) unless params.empty?
end
end
list.sort!
# Take care of the broken text/xml entry by renaming or deleting it
text_xml = list.index("text/xml")
app_xml = list.index(Mime::XML.to_s)
if text_xml && app_xml
# set the q value to the max of the two
list[app_xml].q = [list[text_xml].q, list[app_xml].q].max
# make sure app_xml is ahead of text_xml in the list
if app_xml > text_xml
list[app_xml], list[text_xml] = list[text_xml], list[app_xml]
app_xml, text_xml = text_xml, app_xml
end
# delete text_xml from the list
list.delete_at(text_xml)
elsif text_xml
list[text_xml].name = Mime::XML.to_s
end
# Look for more specific XML-based types and sort them ahead of app/xml
if app_xml
idx = app_xml
app_xml_type = list[app_xml]
while(idx < list.length)
type = list[idx]
break if type.q < app_xml_type.q
if type.name =~ /\+xml$/
list[app_xml], list[idx] = list[idx], list[app_xml]
app_xml = idx
if accept_header !~ /,/
[Mime::Type.lookup(accept_header)]
else
# keep track of creation order to keep the subsequent sort stable
list = []
accept_header.split(/,/).each_with_index do |header, index|
params, q = header.split(/;\s*q=/)
if params
params.strip!
list << AcceptItem.new(index, params, q) unless params.empty?
end
idx += 1
end
end
list.sort!
list.map! { |i| Mime::Type.lookup(i.name) }.uniq!
list
# Take care of the broken text/xml entry by renaming or deleting it
text_xml = list.index("text/xml")
app_xml = list.index(Mime::XML.to_s)
if text_xml && app_xml
# set the q value to the max of the two
list[app_xml].q = [list[text_xml].q, list[app_xml].q].max
# make sure app_xml is ahead of text_xml in the list
if app_xml > text_xml
list[app_xml], list[text_xml] = list[text_xml], list[app_xml]
app_xml, text_xml = text_xml, app_xml
end
# delete text_xml from the list
list.delete_at(text_xml)
elsif text_xml
list[text_xml].name = Mime::XML.to_s
end
# Look for more specific XML-based types and sort them ahead of app/xml
if app_xml
idx = app_xml
app_xml_type = list[app_xml]
while(idx < list.length)
type = list[idx]
break if type.q < app_xml_type.q
if type.name =~ /\+xml$/
list[app_xml], list[idx] = list[idx], list[app_xml]
app_xml = idx
end
idx += 1
end
end
list.map! { |i| Mime::Type.lookup(i.name) }.uniq!
list
end
end
end

View File

@@ -24,7 +24,7 @@ module ActionController #:nodoc:
super()
end
%w[ AUTH_TYPE CONTENT_TYPE GATEWAY_INTERFACE PATH_INFO
%w[ AUTH_TYPE GATEWAY_INTERFACE PATH_INFO
PATH_TRANSLATED QUERY_STRING REMOTE_HOST
REMOTE_IDENT REMOTE_USER SCRIPT_NAME
SERVER_NAME SERVER_PROTOCOL
@@ -98,10 +98,6 @@ module ActionController #:nodoc:
@env['REMOTE_ADDR']
end
def request_method
@env['REQUEST_METHOD'].downcase.to_sym
end
def server_port
@env['SERVER_PORT'].to_i
end
@@ -250,11 +246,11 @@ end_msg
headers['Content-Language'] = options.delete('language') if options['language']
headers['Expires'] = options.delete('expires') if options['expires']
@status = options['Status'] || "200 OK"
@status = options.delete('Status') || "200 OK"
# Convert 'cookie' header to 'Set-Cookie' headers.
# Because Set-Cookie header can appear more the once in the response body,
# we store it in a line break seperated string that will be translated to
# we store it in a line break separated string that will be translated to
# multiple Set-Cookie header by the handler.
if cookie = options.delete('cookie')
cookies = []

View File

@@ -61,7 +61,7 @@ module ActionController
request_method == :head
end
# Provides acccess to the request's HTTP headers, for example:
# Provides access to the request's HTTP headers, for example:
# request.headers["Content-Type"] # => "text/plain"
def headers
@headers ||= ActionController::Http::Headers.new(@env)
@@ -82,21 +82,34 @@ module ActionController
# Returns the accepted MIME type for the request
def accepts
@accepts ||=
if @env['HTTP_ACCEPT'].to_s.strip.empty?
[ content_type, Mime::ALL ].compact # make sure content_type being nil is not included
else
Mime::Type.parse(@env['HTTP_ACCEPT'])
begin
header = @env['HTTP_ACCEPT'].to_s.strip
if header.empty?
[content_type, Mime::ALL].compact
else
Mime::Type.parse(header)
end
end
end
# Returns the Mime type for the format used in the request. If there is no format available, the first of the
# accept types will be used. Examples:
# Returns the Mime type for the format used in the request.
#
# GET /posts/5.xml | request.format => Mime::XML
# GET /posts/5.xhtml | request.format => Mime::HTML
# GET /posts/5 | request.format => request.accepts.first (usually Mime::HTML for browsers)
# GET /posts/5 | request.format => Mime::HTML or MIME::JS, or request.accepts.first depending on the value of <tt>ActionController::Base.use_accept_header</tt>
def format
@format ||= parameters[:format] ? Mime::Type.lookup_by_extension(parameters[:format]) : accepts.first
@format ||= begin
if parameters[:format]
Mime::Type.lookup_by_extension(parameters[:format])
elsif ActionController::Base.use_accept_header
accepts.first
elsif xhr?
Mime::Type.lookup_by_extension("js")
else
Mime::Type.lookup_by_extension("html")
end
end
end
@@ -116,6 +129,26 @@ module ActionController
@format = Mime::Type.lookup_by_extension(parameters[:format])
end
# Returns a symbolized version of the <tt>:format</tt> parameter of the request.
# If no format is given it returns <tt>:js</tt>for AJAX requests and <tt>:html</tt>
# otherwise.
def template_format
parameter_format = parameters[:format]
if parameter_format
parameter_format.to_sym
elsif xhr?
:js
else
:html
end
end
def cache_format
parameter_format = parameters[:format]
parameter_format && parameter_format.to_sym
end
# Returns true if the request's "X-Requested-With" header contains
# "XMLHttpRequest". (The Prototype Javascript library sends this header with
# every Ajax request.)
@@ -232,7 +265,7 @@ EOM
parts[0..-(tld_length+2)]
end
# Return the query string, accounting for server idiosyncracies.
# Return the query string, accounting for server idiosyncrasies.
def query_string
if uri = @env['REQUEST_URI']
uri.split('?', 2)[1] || ''
@@ -241,7 +274,7 @@ EOM
end
end
# Return the request URI, accounting for server idiosyncracies.
# Return the request URI, accounting for server idiosyncrasies.
# WEBrick includes the full URL. IIS leaves REQUEST_URI blank.
def request_uri
if uri = @env['REQUEST_URI']

View File

@@ -17,7 +17,7 @@ module ActionController #:nodoc:
# forged link from another site, is done by embedding a token based on the session (which an attacker wouldn't know) in all
# forms and Ajax requests generated by Rails and then verifying the authenticity of that token in the controller. Only
# HTML/JavaScript requests are checked, so this will not protect your XML API (presumably you'll have a different authentication
# scheme there anyway). Also, GET requests are not protected as these should be indempotent anyway.
# scheme there anyway). Also, GET requests are not protected as these should be idempotent anyway.
#
# This is turned on with the <tt>protect_from_forgery</tt> method, which will check the token and raise an
# ActionController::InvalidAuthenticityToken if it doesn't match what was expected. You can customize the error message in

View File

@@ -112,19 +112,23 @@ module ActionController #:nodoc:
protected
# Exception handler called when the performance of an action raises an exception.
def rescue_action(exception)
log_error(exception) if logger
erase_results if performed?
# Let the exception alter the response if it wants.
# For example, MethodNotAllowed sets the Allow header.
if exception.respond_to?(:handle_response!)
exception.handle_response!(response)
end
if consider_all_requests_local || local_request?
rescue_action_locally(exception)
if handler_for_rescue(exception)
rescue_action_with_handler(exception)
else
rescue_action_in_public(exception)
log_error(exception) if logger
erase_results if performed?
# Let the exception alter the response if it wants.
# For example, MethodNotAllowed sets the Allow header.
if exception.respond_to?(:handle_response!)
exception.handle_response!(response)
end
if consider_all_requests_local || local_request?
rescue_action_locally(exception)
else
rescue_action_in_public(exception)
end
end
end
@@ -200,7 +204,7 @@ module ActionController #:nodoc:
def perform_action_with_rescue #:nodoc:
perform_action_without_rescue
rescue Exception => exception
rescue_action_with_handler(exception) || rescue_action(exception)
rescue_action(exception)
end
def rescues_path(template_name)

View File

@@ -296,6 +296,10 @@ module ActionController
# article_comments_url(:article_id => @article)
# article_comment_url(:article_id => @article, :id => @comment)
#
# If you don't want to load all objects from the database you might want to use the <tt>article_id</tt> directly:
#
# articles_comments_url(@comment.article_id, @comment)
#
# * <tt>:name_prefix</tt> - Define a prefix for all generated routes, usually ending in an underscore.
# Use this if you have named routes that may clash.
#

View File

@@ -88,6 +88,10 @@ module ActionController
#
# map.connect ':controller/:action/:id', :action => 'show', :defaults => { :page => 'Dashboard' }
#
# Note: The default routes, as provided by the Rails generator, make all actions in every
# controller accessible via GET requests. You should consider removing them or commenting
# them out if you're using named routes and resources.
#
# == Named routes
#
# Routes can be named with the syntax <tt>map.name_of_route options</tt>,

View File

@@ -67,10 +67,9 @@ module ActionController
options = options.dup
if options[:namespace]
options[:controller] = "#{options[:path_prefix]}/#{options[:controller]}"
options[:controller] = "#{options.delete(:namespace).sub(/\/$/, '')}/#{options[:controller]}"
options.delete(:path_prefix)
options.delete(:name_prefix)
options.delete(:namespace)
end
requirements = (options.delete(:requirements) || {}).dup

View File

@@ -12,19 +12,21 @@ module ActionController #:nodoc:
X_SENDFILE_HEADER = 'X-Sendfile'.freeze
protected
# Sends the file by streaming it 4096 bytes at a time. This way the
# whole file doesn't need to be read into memory at once. This makes
# it feasible to send even large files.
# Sends the file, by default streaming it 4096 bytes at a time. This way the
# whole file doesn't need to be read into memory at once. This makes it
# feasible to send even large files. You can optionally turn off streaming
# and send the whole file at once.
#
# Be careful to sanitize the path parameter if it coming from a web
# Be careful to sanitize the path parameter if it is coming from a web
# page. <tt>send_file(params[:path])</tt> allows a malicious user to
# download any file on your server.
#
# Options:
# * <tt>:filename</tt> - suggests a filename for the browser to use.
# Defaults to <tt>File.basename(path)</tt>.
# * <tt>:type</tt> - specifies an HTTP content type.
# Defaults to 'application/octet-stream'.
# * <tt>:type</tt> - specifies an HTTP content type. Defaults to 'application/octet-stream'.
# * <tt>:length</tt> - used to manually override the length (in bytes) of the content that
# is going to be sent to the client. Defaults to <tt>File.size(path)</tt>.
# * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
# Valid values are 'inline' and 'attachment' (default).
# * <tt>:stream</tt> - whether to send the file to the user agent as it is read (+true+)
@@ -35,6 +37,12 @@ module ActionController #:nodoc:
# * <tt>:url_based_filename</tt> - set to +true+ if you want the browser guess the filename from
# the URL, which is necessary for i18n filenames on certain browsers
# (setting <tt>:filename</tt> overrides this option).
# * <tt>:x_sendfile</tt> - uses X-Sendfile to send the file when set to +true+. This is currently
# only available with Lighttpd/Apache2 and specific modules installed and activated. Since this
# uses the web server to send the file, this may lower memory consumption on your server and
# it will not block your application for further requests.
# See http://blog.lighttpd.net/articles/2006/07/02/x-sendfile and
# http://tn123.ath.cx/mod_xsendfile/ for details. Defaults to +false+.
#
# The default Content-Type and Content-Disposition headers are
# set to download arbitrary binary files in as many browsers as
@@ -99,8 +107,7 @@ module ActionController #:nodoc:
#
# Options:
# * <tt>:filename</tt> - suggests a filename for the browser to use.
# * <tt>:type</tt> - specifies an HTTP content type.
# Defaults to 'application/octet-stream'.
# * <tt>:type</tt> - specifies an HTTP content type. Defaults to 'application/octet-stream'.
# * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
# Valid values are 'inline' and 'attachment' (default).
# * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'.

View File

@@ -15,6 +15,27 @@ module ActionController
end
end
# Superclass for Action Controller functional tests. Infers the controller under test from the test class name,
# and creates @controller, @request, @response instance variables.
#
# class WidgetsControllerTest < ActionController::TestCase
# def test_index
# get :index
# end
# end
#
# * @controller - WidgetController.new
# * @request - ActionController::TestRequest.new
# * @response - ActionController::TestResponse.new
#
# (Earlier versions of Rails required each functional test to subclass Test::Unit::TestCase and define
# @controller, @request, @response in +setup+.)
#
# If the controller cannot be inferred from the test class name, you can explicity set it with +tests+.
#
# class SpecialEdgeCaseWidgetsControllerTest < ActionController::TestCase
# tests WidgetController
# end
class TestCase < ActiveSupport::TestCase
# When the request.remote_addr remains the default for testing, which is 0.0.0.0, the exception is simply raised inline
# (bystepping the regular exception handling from rescue_action). If the request.remote_addr is anything else, the regular
@@ -41,6 +62,8 @@ module ActionController
@@controller_class = nil
class << self
# Sets the controller class name. Useful if the name can't be inferred from test class.
# Expects +controller_class+ as a constant. Example: <tt>tests WidgetController</tt>.
def tests(controller_class)
self.controller_class = controller_class
end

View File

@@ -205,24 +205,13 @@ module ActionController #:nodoc:
p.match(redirect_url) != nil
end
# Returns the template path of the file which was used to
# render this response (or nil)
def rendered_file(with_controller=false)
unless template.first_render.nil?
unless with_controller
template.first_render
else
template.first_render.split('/').last || template.first_render
end
end
# Returns the template of the file which was used to
# render this response (or nil)
def rendered_template
template._first_render
end
# Was this template rendered by a file?
def rendered_with_file?
!rendered_file.nil?
end
# A shortcut to the flash. Returns an empyt hash if no session flash exists.
# A shortcut to the flash. Returns an empty hash if no session flash exists.
def flash
session['flash'] || {}
end
@@ -404,15 +393,6 @@ module ActionController #:nodoc:
end
alias xhr :xml_http_request
def follow_redirect
redirected_controller = @response.redirected_to[:controller]
if redirected_controller && redirected_controller != @controller.controller_name
raise "Can't follow redirects outside of current controller (from #{@controller.controller_name} to #{redirected_controller})"
end
get(@response.redirected_to.delete(:action), @response.redirected_to.stringify_keys)
end
def assigns(key = nil)
if key.nil?
@response.template.assigns

View File

@@ -64,7 +64,7 @@ module HTML
#
# When using a combination of the above, the element name comes first
# followed by identifier, class names, attributes, pseudo classes and
# negation in any order. Do not seprate these parts with spaces!
# negation in any order. Do not separate these parts with spaces!
# Space separation is used for descendant selectors.
#
# For example:
@@ -158,7 +158,7 @@ module HTML
# * <tt>:not(selector)</tt> -- Match the element only if the element does not
# match the simple selector.
#
# As you can see, <tt>:nth-child<tt> pseudo class and its varient can get quite
# As you can see, <tt>:nth-child<tt> pseudo class and its variant can get quite
# tricky and the CSS specification doesn't do a much better job explaining it.
# But after reading the examples and trying a few combinations, it's easy to
# figure out.

View File

@@ -21,12 +21,14 @@
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#++
require 'action_view/template_handlers'
require 'action_view/template_file'
require 'action_view/view_load_paths'
require 'action_view/renderable'
require 'action_view/renderable_partial'
require 'action_view/template'
require 'action_view/partial_template'
require 'action_view/inline_template'
require 'action_view/paths'
require 'action_view/base'
require 'action_view/partials'

View File

@@ -3,6 +3,12 @@ module ActionView #:nodoc:
end
class MissingTemplate < ActionViewError #:nodoc:
def initialize(paths, path, template_format = nil)
full_template_path = path.include?('.') ? path : "#{path}.erb"
display_paths = paths.join(':')
template_type = (path =~ /layouts/i) ? 'layout' : 'template'
super("Missing #{template_type} #{full_template_path} in view path #{display_paths}")
end
end
# Action View templates can be written in three ways. If the template file has a <tt>.erb</tt> (or <tt>.rhtml</tt>) extension then it uses a mixture of ERb
@@ -153,11 +159,11 @@ module ActionView #:nodoc:
class Base
include ERB::Util
attr_accessor :base_path, :assigns, :template_extension, :first_render
attr_accessor :base_path, :assigns, :template_extension
attr_accessor :controller
attr_accessor :_first_render, :_last_render
attr_writer :template_format
attr_accessor :current_render_extension
attr_accessor :output_buffer
@@ -165,12 +171,13 @@ module ActionView #:nodoc:
delegate :erb_trim_mode=, :to => 'ActionView::TemplateHandlers::ERB'
end
# Specify whether file modification times should be checked to see if a template needs recompilation
@@cache_template_loading = false
cattr_accessor :cache_template_loading
def self.cache_template_loading=(*args)
ActiveSupport::Deprecation.warn("config.action_view.cache_template_loading option has been deprecated and has no affect. " <<
"Please remove it from your config files.", caller)
end
def self.cache_template_extensions=(*args)
ActiveSupport::Deprecation.warn("config.action_view.cache_template_extensions option has been deprecated and has no affect. " <<
ActiveSupport::Deprecation.warn("config.action_view.cache_template_extensions option has been deprecated and has no effect. " <<
"Please remove it from your config files.", caller)
end
@@ -179,6 +186,10 @@ module ActionView #:nodoc:
@@debug_rjs = false
cattr_accessor :debug_rjs
# A warning will be displayed whenever an action results in a cache miss on your view paths.
@@warn_cache_misses = false
cattr_accessor :warn_cache_misses
attr_internal :request
delegate :request_forgery_protection_token, :template, :params, :session, :cookies, :response, :headers,
@@ -189,19 +200,10 @@ module ActionView #:nodoc:
end
include CompiledTemplates
# Maps inline templates to their method names
cattr_accessor :method_names
@@method_names = {}
# Map method names to the names passed in local assigns so far
@@template_args = {}
# Cache public asset paths
cattr_reader :computed_public_paths
@@computed_public_paths = {}
class ObjectWrapper < Struct.new(:value) #:nodoc:
end
def self.helper_modules #:nodoc:
helpers = []
Dir.entries(File.expand_path("#{File.dirname(__FILE__)}/helpers")).sort.each do |file|
@@ -215,6 +217,10 @@ module ActionView #:nodoc:
return helpers
end
def self.process_view_paths(value)
ActionView::PathSet.new(Array(value))
end
def initialize(view_paths = [], assigns_for_first_render = {}, controller = nil)#:nodoc:
@assigns = assigns_for_first_render
@assigns_added = nil
@@ -225,19 +231,20 @@ module ActionView #:nodoc:
attr_reader :view_paths
def view_paths=(paths)
@view_paths = ViewLoadPaths.new(Array(paths))
@view_paths = self.class.process_view_paths(paths)
end
# Renders the template present at <tt>template_path</tt> (relative to the view_paths array).
# The hash in <tt>local_assigns</tt> is made available as local variables.
def render(options = {}, local_assigns = {}, &block) #:nodoc:
local_assigns ||= {}
if options.is_a?(String)
render_file(options, true, local_assigns)
render_file(options, nil, local_assigns)
elsif options == :update
update_page(&block)
elsif options.is_a?(Hash)
use_full_path = options[:use_full_path]
options = options.reverse_merge(:locals => {}, :use_full_path => true)
options = options.reverse_merge(:locals => {})
if partial_layout = options.delete(:layout)
if block_given?
@@ -250,11 +257,11 @@ module ActionView #:nodoc:
end
end
elsif options[:file]
render_file(options[:file], use_full_path || false, options[:locals])
render_file(options[:file], nil, options[:locals])
elsif options[:partial] && options[:collection]
render_partial_collection(options[:partial], options[:collection], options[:spacer_template], options[:locals], options[:as])
elsif options[:partial]
render_partial(options[:partial], ActionView::Base::ObjectWrapper.new(options[:object]), options[:locals])
render_partial(options[:partial], options[:object], options[:locals])
elsif options[:inline]
render_inline(options[:inline], options[:locals], options[:type])
end
@@ -266,42 +273,75 @@ module ActionView #:nodoc:
template_path.split('/').last[0,1] != '_'
end
# Returns a symbolized version of the <tt>:format</tt> parameter of the request,
# or <tt>:html</tt> by default.
#
# EXCEPTION: If the <tt>:format</tt> parameter is not set, the Accept header will be examined for
# whether it contains the JavaScript mime type as its first priority. If that's the case,
# it will be used. This ensures that Ajax applications can use the same URL to support both
# JavaScript and non-JavaScript users.
# The format to be used when choosing between multiple templates with
# the same name but differing formats. See +Request#template_format+
# for more details.
def template_format
return @template_format if @template_format
if controller && controller.respond_to?(:request)
parameter_format = controller.request.parameters[:format]
accept_format = controller.request.accepts.first
case
when parameter_format.blank? && accept_format != :js
@template_format = :html
when parameter_format.blank? && accept_format == :js
@template_format = :js
else
@template_format = parameter_format.to_sym
end
@template_format = controller.request.template_format
else
@template_format = :html
end
end
def file_exists?(template_path)
view_paths.template_exists?(template_file_from_name(template_path))
pick_template(template_path) ? true : false
rescue MissingTemplate
false
end
# Gets the extension for an existing template with the given template_path.
# Returns the format with the extension if that template exists.
#
# pick_template('users/show')
# # => 'users/show.html.erb'
#
# pick_template('users/legacy')
# # => 'users/legacy.rhtml'
#
def pick_template(template_path)
path = template_path.sub(/^\//, '')
if m = path.match(/(.*)\.(\w+)$/)
template_file_name, template_file_extension = m[1], m[2]
else
template_file_name = path
end
# OPTIMIZE: Checks to lookup template in view path
if template = self.view_paths["#{template_file_name}.#{template_format}"]
template
elsif template = self.view_paths[template_file_name]
template
elsif _first_render && template = self.view_paths["#{template_file_name}.#{_first_render.format_and_extension}"]
template
elsif template_format == :js && template = self.view_paths["#{template_file_name}.html"]
@template_format = :html
template
else
template = Template.new(template_path, view_paths)
if self.class.warn_cache_misses && logger = ActionController::Base.logger
logger.debug "[PERFORMANCE] Rendering a template that was " +
"not found in view path. Templates outside the view path are " +
"not cached and result in expensive disk operations. Move this " +
"file into #{view_paths.join(':')} or add the folder to your " +
"view path list"
end
template
end
end
private
# Renders the template present at <tt>template_path</tt>. If <tt>use_full_path</tt> is set to true,
# it's relative to the view_paths array, otherwise it's absolute. The hash in <tt>local_assigns</tt>
# Renders the template present at <tt>template_path</tt>. The hash in <tt>local_assigns</tt>
# is made available as local variables.
def render_file(template_path, use_full_path = true, local_assigns = {}) #:nodoc:
def render_file(template_path, use_full_path = nil, local_assigns = {}) #:nodoc:
unless use_full_path == nil
ActiveSupport::Deprecation.warn("use_full_path option has been deprecated and has no affect.", caller)
end
if defined?(ActionMailer) && defined?(ActionMailer::Base) && controller.is_a?(ActionMailer::Base) && !template_path.include?("/")
raise ActionViewError, <<-END_ERROR
Due to changes in ActionMailer, you need to provide the mailer_name along with the template name.
@@ -315,11 +355,12 @@ module ActionView #:nodoc:
END_ERROR
end
Template.new(self, template_path, use_full_path, local_assigns).render_template
template = pick_template(template_path)
template.render_template(self, local_assigns)
end
def render_inline(text, local_assigns = {}, type = nil)
InlineTemplate.new(self, text, local_assigns, type).render_template
InlineTemplate.new(text, type).render(self, local_assigns)
end
def wrap_content_for_layout(content)
@@ -342,42 +383,10 @@ module ActionView #:nodoc:
@assigns.each { |key, value| instance_variable_set("@#{key}", value) }
end
def execute(template)
send(template.method, template.locals) do |*names|
def execute(template, local_assigns = {})
send(template.method(local_assigns), local_assigns) do |*names|
instance_variable_get "@content_for_#{names.first || 'layout'}"
end
end
def template_file_from_name(template_name)
template_name = TemplateFile.from_path(template_name)
pick_template_extension(template_name) unless template_name.extension
end
# Gets the extension for an existing template with the given template_path.
# Returns the format with the extension if that template exists.
#
# pick_template_extension('users/show')
# # => 'html.erb'
#
# pick_template_extension('users/legacy')
# # => "rhtml"
#
def pick_template_extension(file)
if f = self.view_paths.find_template_file_for_path(file.dup_with_extension(template_format)) || file_from_first_render(file)
f
elsif template_format == :js && f = self.view_paths.find_template_file_for_path(file.dup_with_extension(:html))
@template_format = :html
f
else
nil
end
end
# Determine the template extension from the <tt>@first_render</tt> filename
def file_from_first_render(file)
if extension = File.basename(@first_render.to_s)[/^[^.]+\.(.+)$/, 1]
file.dup_with_extension(extension)
end
end
end
end

View File

@@ -141,7 +141,7 @@ module ActionView
#
# error_messages_for 'user_common', 'user', :object_name => 'user'
#
# If the objects cannot be located as instance variables, you can add an extra <tt>:object</tt> paremeter which gives the actual
# If the objects cannot be located as instance variables, you can add an extra <tt>:object</tt> parameter which gives the actual
# object (or array of objects to use):
#
# error_messages_for 'user', :object => @question.user

View File

@@ -209,6 +209,10 @@ module ActionView
# Note that the default javascript files will be included first. So Prototype and Scriptaculous are available to
# all subsequently included files.
#
# If you want Rails to search in all the subdirectories under javascripts, you should explicitly set <tt>:recursive</tt>:
#
# javascript_include_tag :all, :recursive => true
#
# == Caching multiple javascripts into one
#
# You can also cache multiple javascripts into one file, which requires less HTTP connections to download and can better be
@@ -235,18 +239,23 @@ module ActionView
#
# javascript_include_tag "prototype", "cart", "checkout", :cache => "shop" # when ActionController::Base.perform_caching is true =>
# <script type="text/javascript" src="/javascripts/shop.js"></script>
#
# The <tt>:recursive</tt> option is also available for caching:
#
# javascript_include_tag :all, :cache => true, :recursive => true
def javascript_include_tag(*sources)
options = sources.extract_options!.stringify_keys
cache = options.delete("cache")
recursive = options.delete("recursive")
if ActionController::Base.perform_caching && cache
joined_javascript_name = (cache == true ? "all" : cache) + ".js"
joined_javascript_path = File.join(JAVASCRIPTS_DIR, joined_javascript_name)
write_asset_file_contents(joined_javascript_path, compute_javascript_paths(sources))
write_asset_file_contents(joined_javascript_path, compute_javascript_paths(sources, recursive)) unless File.exists?(joined_javascript_path)
javascript_src_tag(joined_javascript_name, options)
else
expand_javascript_sources(sources).collect { |source| javascript_src_tag(source, options) }.join("\n")
expand_javascript_sources(sources, recursive).collect { |source| javascript_src_tag(source, options) }.join("\n")
end
end
@@ -332,13 +341,17 @@ module ActionView
# <link href="/stylesheets/random.styles" media="screen" rel="stylesheet" type="text/css" />
# <link href="/css/stylish.css" media="screen" rel="stylesheet" type="text/css" />
#
# You can also include all styles in the stylesheet directory using <tt>:all</tt> as the source:
# You can also include all styles in the stylesheets directory using <tt>:all</tt> as the source:
#
# stylesheet_link_tag :all # =>
# <link href="/stylesheets/style1.css" media="screen" rel="stylesheet" type="text/css" />
# <link href="/stylesheets/styleB.css" media="screen" rel="stylesheet" type="text/css" />
# <link href="/stylesheets/styleX2.css" media="screen" rel="stylesheet" type="text/css" />
#
# If you want Rails to search in all the subdirectories under stylesheets, you should explicitly set <tt>:recursive</tt>:
#
# stylesheet_link_tag :all, :recursive => true
#
# == Caching multiple stylesheets into one
#
# You can also cache multiple stylesheets into one file, which requires less HTTP connections and can better be
@@ -362,18 +375,23 @@ module ActionView
#
# stylesheet_link_tag "shop", "cart", "checkout", :cache => "payment" # when ActionController::Base.perform_caching is true =>
# <link href="/stylesheets/payment.css" media="screen" rel="stylesheet" type="text/css" />
#
# The <tt>:recursive</tt> option is also available for caching:
#
# stylesheet_link_tag :all, :cache => true, :recursive => true
def stylesheet_link_tag(*sources)
options = sources.extract_options!.stringify_keys
cache = options.delete("cache")
recursive = options.delete("recursive")
if ActionController::Base.perform_caching && cache
joined_stylesheet_name = (cache == true ? "all" : cache) + ".css"
joined_stylesheet_path = File.join(STYLESHEETS_DIR, joined_stylesheet_name)
write_asset_file_contents(joined_stylesheet_path, compute_stylesheet_paths(sources))
write_asset_file_contents(joined_stylesheet_path, compute_stylesheet_paths(sources, recursive)) unless File.exists?(joined_stylesheet_path)
stylesheet_tag(joined_stylesheet_name, options)
else
expand_stylesheet_sources(sources).collect { |source| stylesheet_tag(source, options) }.join("\n")
expand_stylesheet_sources(sources, recursive).collect { |source| stylesheet_tag(source, options) }.join("\n")
end
end
@@ -559,18 +577,19 @@ module ActionView
tag("link", { "rel" => "stylesheet", "type" => Mime::CSS, "media" => "screen", "href" => html_escape(path_to_stylesheet(source)) }.merge(options), false, false)
end
def compute_javascript_paths(sources)
expand_javascript_sources(sources).collect { |source| compute_public_path(source, 'javascripts', 'js', false) }
def compute_javascript_paths(*args)
expand_javascript_sources(*args).collect { |source| compute_public_path(source, 'javascripts', 'js', false) }
end
def compute_stylesheet_paths(sources)
expand_stylesheet_sources(sources).collect { |source| compute_public_path(source, 'stylesheets', 'css', false) }
def compute_stylesheet_paths(*args)
expand_stylesheet_sources(*args).collect { |source| compute_public_path(source, 'stylesheets', 'css', false) }
end
def expand_javascript_sources(sources)
def expand_javascript_sources(sources, recursive = false)
if sources.include?(:all)
all_javascript_files = Dir[File.join(JAVASCRIPTS_DIR, '*.js')].collect { |file| File.basename(file).gsub(/\.\w+$/, '') }.sort
@@all_javascript_sources ||= ((determine_source(:defaults, @@javascript_expansions).dup & all_javascript_files) + all_javascript_files).uniq
all_javascript_files = collect_asset_files(JAVASCRIPTS_DIR, ('**' if recursive), '*.js')
@@all_javascript_sources ||= {}
@@all_javascript_sources[recursive] ||= ((determine_source(:defaults, @@javascript_expansions).dup & all_javascript_files) + all_javascript_files).uniq
else
expanded_sources = sources.collect do |source|
determine_source(source, @@javascript_expansions)
@@ -580,9 +599,10 @@ module ActionView
end
end
def expand_stylesheet_sources(sources)
def expand_stylesheet_sources(sources, recursive)
if sources.first == :all
@@all_stylesheet_sources ||= Dir[File.join(STYLESHEETS_DIR, '*.css')].collect { |file| File.basename(file).gsub(/\.\w+$/, '') }.sort
@@all_stylesheet_sources ||= {}
@@all_stylesheet_sources[recursive] ||= collect_asset_files(STYLESHEETS_DIR, ('**' if recursive), '*.css')
else
sources.collect do |source|
determine_source(source, @@stylesheet_expansions)
@@ -604,10 +624,16 @@ module ActionView
end
def write_asset_file_contents(joined_asset_path, asset_paths)
unless file_exist?(joined_asset_path)
FileUtils.mkdir_p(File.dirname(joined_asset_path))
File.open(joined_asset_path, "w+") { |cache| cache.write(join_asset_file_contents(asset_paths)) }
end
FileUtils.mkdir_p(File.dirname(joined_asset_path))
File.open(joined_asset_path, "w+") { |cache| cache.write(join_asset_file_contents(asset_paths)) }
end
def collect_asset_files(*path)
dir = path.first
Dir[File.join(*path.compact)].collect do |file|
file[-(file.size - dir.size - 1)..-1].sub(/\.\w+$/, '')
end.sort
end
end
end

View File

@@ -32,8 +32,7 @@ module ActionView
# <i>Topics listed alphabetically</i>
# <% end %>
def cache(name = {}, options = nil, &block)
handler = Template.handler_class_for_extension(current_render_extension.to_sym)
handler.new(@controller).cache_fragment(block, name, options)
@controller.fragment_for(output_buffer, name, options, &block)
end
end
end

View File

@@ -34,9 +34,8 @@ module ActionView
# Return captured buffer in erb.
if block_called_from_erb?(block)
with_output_buffer { block.call(*args) }
# Return block result otherwise, but protect buffer also.
else
# Return block result otherwise, but protect buffer also.
with_output_buffer { return block.call(*args) }
end
end
@@ -123,14 +122,15 @@ module ActionView
nil
end
private
def with_output_buffer(buf = '')
self.output_buffer, old_buffer = buf, output_buffer
yield
output_buffer
ensure
self.output_buffer = old_buffer
end
# Use an alternate output buffer for the duration of the block.
# Defaults to a new empty string.
def with_output_buffer(buf = '') #:nodoc:
self.output_buffer, old_buffer = buf, output_buffer
yield
output_buffer
ensure
self.output_buffer = old_buffer
end
end
end
end

View File

@@ -153,13 +153,16 @@ module ActionView
# Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that all month
# choices are valid.
def date_select(object_name, method, options = {}, html_options = {})
InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_date_select_tag(options, html_options)
InstanceTag.new(object_name, method, self, options.delete(:object)).to_date_select_tag(options, html_options)
end
# Returns a set of select tags (one for hour, minute and optionally second) pre-selected for accessing a specified
# time-based attribute (identified by +method+) on an object assigned to the template (identified by +object+).
# You can include the seconds with <tt>:include_seconds</tt>.
#
#
# This method will also generate 3 input hidden tags, for the actual year, month and day unless the option
# <tt>:ignore_date</tt> is set to +true+.
#
# If anything is passed in the html_options hash it will be applied to every select tag in the set.
#
# ==== Examples
@@ -188,7 +191,7 @@ module ActionView
# Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that all month
# choices are valid.
def time_select(object_name, method, options = {}, html_options = {})
InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_time_select_tag(options, html_options)
InstanceTag.new(object_name, method, self, options.delete(:object)).to_time_select_tag(options, html_options)
end
# Returns a set of select tags (one for year, month, day, hour, and minute) pre-selected for accessing a specified datetime-based
@@ -214,7 +217,7 @@ module ActionView
#
# The selects are prepared for multi-parameter assignment to an Active Record object.
def datetime_select(object_name, method, options = {}, html_options = {})
InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_datetime_select_tag(options, html_options)
InstanceTag.new(object_name, method, self, options.delete(:object)).to_datetime_select_tag(options, html_options)
end
# Returns a set of html select-tags (one for year, month, day, hour, and minute) pre-selected with the +datetime+.
@@ -277,11 +280,11 @@ module ActionView
#
# # Generates a date select that discards the type of the field and defaults to the date in
# # my_date (six days after today)
# select_datetime(my_date_time, :discard_type => true)
# select_date(my_date, :discard_type => true)
#
# # Generates a date select that defaults to the datetime in my_date (six days after today)
# # prefixed with 'payday' rather than 'date'
# select_datetime(my_date_time, :prefix => 'payday')
# select_date(my_date, :prefix => 'payday')
#
def select_date(date = Date.current, options = {}, html_options = {})
options[:order] ||= []
@@ -547,23 +550,32 @@ module ActionView
# select_year(2006, :start_year => 2000, :end_year => 2010)
#
def select_year(date, options = {}, html_options = {})
val = date ? (date.kind_of?(Fixnum) ? date : date.year) : ''
if options[:use_hidden]
hidden_html(options[:field_name] || 'year', val, options)
if !date || date == 0
value = ''
middle_year = Date.today.year
elsif date.kind_of?(Fixnum)
value = middle_year = date
else
year_options = []
y = date ? (date.kind_of?(Fixnum) ? (y = (date == 0) ? Date.today.year : date) : date.year) : Date.today.year
value = middle_year = date.year
end
if options[:use_hidden]
hidden_html(options[:field_name] || 'year', value, options)
else
year_options = ''
start_year = options[:start_year] || middle_year - 5
end_year = options[:end_year] || middle_year + 5
step_val = start_year < end_year ? 1 : -1
start_year, end_year = (options[:start_year] || y-5), (options[:end_year] || y+5)
step_val = start_year < end_year ? 1 : -1
start_year.step(end_year, step_val) do |year|
year_options << ((val == year) ?
content_tag(:option, year, :value => year, :selected => "selected") :
content_tag(:option, year, :value => year)
)
if value == year
year_options << content_tag(:option, year, :value => year, :selected => "selected")
else
year_options << content_tag(:option, year, :value => year)
end
year_options << "\n"
end
select_html(options[:field_name] || 'year', year_options.join, options, html_options)
select_html(options[:field_name] || 'year', year_options, options, html_options)
end
end
@@ -646,7 +658,7 @@ module ActionView
order.reverse.each do |param|
# Send hidden fields for discarded elements once output has started
# This ensures AR can reconstruct valid dates using ParseDate
next if discard[param] && date_or_time_select.empty?
next if discard[param] && (date_or_time_select.empty? || options[:ignore_date])
date_or_time_select.insert(0, self.send("select_#{param}", datetime, options_with_prefix(position[param], options.merge(:use_hidden => discard[param])), html_options))
date_or_time_select.insert(0,

View File

@@ -2,21 +2,28 @@ module ActionView
module Helpers
# Provides a set of methods for making it easier to debug Rails objects.
module DebugHelper
# Returns a <pre>-tag that has +object+ dumped by YAML. This creates a very
# readable way to inspect an object.
# Returns a YAML representation of +object+ wrapped with <pre> and </pre>.
# If the object cannot be converted to YAML using +to_yaml+, +inspect+ will be called instead.
# Useful for inspecting an object at the time of rendering.
#
# ==== Example
# my_hash = {'first' => 1, 'second' => 'two', 'third' => [1,2,3]}
# debug(my_hash)
#
# => <pre class='debug_dump'>---
# first: 1
# second: two
# third:
# - 1
# - 2
# - 3
# </pre>
# @user = User.new({ :username => 'testing', :password => 'xyz', :age => 42}) %>
# debug(@user)
# # =>
# <pre class='debug_dump'>--- !ruby/object:User
# attributes:
# &nbsp; updated_at:
# &nbsp; username: testing
#
# &nbsp; age: 42
# &nbsp; password: xyz
# &nbsp; created_at:
# attributes_cache: {}
#
# new_record: true
# </pre>
def debug(object)
begin
Marshal::dump(object)
@@ -28,4 +35,4 @@ module ActionView
end
end
end
end
end

View File

@@ -76,7 +76,7 @@ module ActionView
# Creates a form and a scope around a specific model object that is used as
# a base for questioning about values for the fields.
#
# Rails provides succint resource-oriented form generation with +form_for+
# Rails provides succinct resource-oriented form generation with +form_for+
# like this:
#
# <% form_for @offer do |f| %>
@@ -333,7 +333,7 @@ module ActionView
# # => <label for="post_title" class="title_label">A short title</label>
#
def label(object_name, method, text = nil, options = {})
InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_label_tag(text, options)
InstanceTag.new(object_name, method, self, options.delete(:object)).to_label_tag(text, options)
end
# Returns an input tag of the "text" type tailored for accessing a specified attribute (identified by +method+) on an object
@@ -355,7 +355,7 @@ module ActionView
# # => <input type="text" id="snippet_code" name="snippet[code]" size="20" value="#{@snippet.code}" class="code_input" />
#
def text_field(object_name, method, options = {})
InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_input_field_tag("text", options)
InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("text", options)
end
# Returns an input tag of the "password" type tailored for accessing a specified attribute (identified by +method+) on an object
@@ -377,7 +377,7 @@ module ActionView
# # => <input type="text" id="account_pin" name="account[pin]" size="20" value="#{@account.pin}" class="form_input" />
#
def password_field(object_name, method, options = {})
InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_input_field_tag("password", options)
InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("password", options)
end
# Returns a hidden input tag tailored for accessing a specified attribute (identified by +method+) on an object
@@ -395,7 +395,7 @@ module ActionView
# hidden_field(:user, :token)
# # => <input type="hidden" id="user_token" name="user[token]" value="#{@user.token}" />
def hidden_field(object_name, method, options = {})
InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_input_field_tag("hidden", options)
InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("hidden", options)
end
# Returns an file upload input tag tailored for accessing a specified attribute (identified by +method+) on an object
@@ -414,7 +414,7 @@ module ActionView
# # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
#
def file_field(object_name, method, options = {})
InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_input_field_tag("file", options)
InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("file", options)
end
# Returns a textarea opening and closing tag set tailored for accessing a specified attribute (identified by +method+)
@@ -442,15 +442,44 @@ module ActionView
# # #{@entry.body}
# # </textarea>
def text_area(object_name, method, options = {})
InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_text_area_tag(options)
InstanceTag.new(object_name, method, self, options.delete(:object)).to_text_area_tag(options)
end
# Returns a checkbox tag tailored for accessing a specified attribute (identified by +method+) on an object
# assigned to the template (identified by +object+). It's intended that +method+ returns an integer and if that
# integer is above zero, then the checkbox is checked. Additional options on the input tag can be passed as a
# hash with +options+. The +checked_value+ defaults to 1 while the default +unchecked_value+
# is set to 0 which is convenient for boolean values. Since HTTP standards say that unchecked checkboxes don't post anything,
# we add a hidden value with the same name as the checkbox as a work around.
# is set to 0 which is convenient for boolean values.
#
# ==== Gotcha
#
# The HTML specification says unchecked check boxes are not successful, and
# thus web browsers do not send them. Unfortunately this introduces a gotcha:
# if an Invoice model has a +paid+ flag, and in the form that edits a paid
# invoice the user unchecks its check box, no +paid+ parameter is sent. So,
# any mass-assignment idiom like
#
# @invoice.update_attributes(params[:invoice])
#
# wouldn't update the flag.
#
# To prevent this the helper generates a hidden field with the same name as
# the checkbox after the very check box. So, the client either sends only the
# hidden field (representing the check box is unchecked), or both fields.
# Since the HTML specification says key/value pairs have to be sent in the
# same order they appear in the form and Rails parameters extraction always
# gets the first occurrence of any given key, that works in ordinary forms.
#
# Unfortunately that workaround does not work when the check box goes
# within an array-like parameter, as in
#
# <% fields_for "project[invoice_attributes][]", invoice, :index => nil do |form| %>
# <%= form.check_box :paid %>
# ...
# <% end %>
#
# because parameter name repetition is precisely what Rails seeks to distinguish
# the elements of the array.
#
# ==== Examples
# # Let's say that @post.validated? is 1:
@@ -468,7 +497,7 @@ module ActionView
# # <input name="eula[accepted]" type="hidden" value="no" />
#
def check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0")
InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_check_box_tag(options, checked_value, unchecked_value)
InstanceTag.new(object_name, method, self, options.delete(:object)).to_check_box_tag(options, checked_value, unchecked_value)
end
# Returns a radio button tag for accessing a specified attribute (identified by +method+) on an object
@@ -488,7 +517,7 @@ module ActionView
# # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" />
# # <input type="radio" id="user_receive_newsletter_no" name="user[receive_newsletter]" value="no" checked="checked" />
def radio_button(object_name, method, tag_value, options = {})
InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_radio_button_tag(tag_value, options)
InstanceTag.new(object_name, method, self, options.delete(:object)).to_radio_button_tag(tag_value, options)
end
end
@@ -501,9 +530,9 @@ module ActionView
DEFAULT_RADIO_OPTIONS = { }.freeze unless const_defined?(:DEFAULT_RADIO_OPTIONS)
DEFAULT_TEXT_AREA_OPTIONS = { "cols" => 40, "rows" => 20 }.freeze unless const_defined?(:DEFAULT_TEXT_AREA_OPTIONS)
def initialize(object_name, method_name, template_object, local_binding = nil, object = nil)
def initialize(object_name, method_name, template_object, object = nil)
@object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup
@template_object, @local_binding = template_object, local_binding
@template_object= template_object
@object = object
if @object_name.sub!(/\[\]$/,"")
if object ||= @template_object.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:to_param)
@@ -601,7 +630,11 @@ module ActionView
end
def object
@object || (@template_object.instance_variable_get("@#{@object_name}") rescue nil)
@object || @template_object.instance_variable_get("@#{@object_name}")
rescue NameError
# As @object_name may contain the nested syntax (item[subobject]) we
# need to fallback to nil.
nil
end
def value(object)

View File

@@ -96,7 +96,7 @@ module ActionView
# By default, <tt>post.person_id</tt> is the selected option. Specify <tt>:selected => value</tt> to use a different selection
# or <tt>:selected => nil</tt> to leave all options unselected.
def select(object, method, choices, options = {}, html_options = {})
InstanceTag.new(object, method, self, nil, options.delete(:object)).to_select_tag(choices, options, html_options)
InstanceTag.new(object, method, self, options.delete(:object)).to_select_tag(choices, options, html_options)
end
# Returns <tt><select></tt> and <tt><option></tt> tags for the collection of existing return values of
@@ -130,12 +130,12 @@ module ActionView
# <option value="3">M. Clark</option>
# </select>
def collection_select(object, method, collection, value_method, text_method, options = {}, html_options = {})
InstanceTag.new(object, method, self, nil, options.delete(:object)).to_collection_select_tag(collection, value_method, text_method, options, html_options)
InstanceTag.new(object, method, self, options.delete(:object)).to_collection_select_tag(collection, value_method, text_method, options, html_options)
end
# Return select and option tags for the given object and method, using country_options_for_select to generate the list of option tags.
def country_select(object, method, priority_countries = nil, options = {}, html_options = {})
InstanceTag.new(object, method, self, nil, options.delete(:object)).to_country_select_tag(priority_countries, options, html_options)
InstanceTag.new(object, method, self, options.delete(:object)).to_country_select_tag(priority_countries, options, html_options)
end
# Return select and option tags for the given object and method, using
@@ -169,7 +169,7 @@ module ActionView
#
# time_zone_select( "user", "time_zone", TZInfo::Timezone.all.sort, :model => TZInfo::Timezone)
def time_zone_select(object, method, priority_zones = nil, options = {}, html_options = {})
InstanceTag.new(object, method, self, nil, options.delete(:object)).to_time_zone_select_tag(priority_zones, options, html_options)
InstanceTag.new(object, method, self, options.delete(:object)).to_time_zone_select_tag(priority_zones, options, html_options)
end
# Accepts a container (hash, array, enumerable, your type) and returns a string of option tags. Given a container
@@ -274,9 +274,11 @@ module ActionView
end
end
# Returns a string of option tags for pretty much any country in the world. Supply a country name as +selected+ to
# have it marked as the selected option tag. You can also supply an array of countries as +priority_countries+, so
# that they will be listed above the rest of the (long) list.
# Returns a string of option tags for most countries in the
# world (as defined in COUNTRIES). Supply a country name as
# +selected+ to have it marked as the selected option tag. You
# can also supply an array of countries as +priority_countries+,
# so that they will be listed above the rest of the (long) list.
#
# NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag.
def country_options_for_select(selected = nil, priority_countries = nil)
@@ -445,19 +447,19 @@ module ActionView
class FormBuilder
def select(method, choices, options = {}, html_options = {})
@template.select(@object_name, method, choices, options.merge(:object => @object), html_options)
@template.select(@object_name, method, choices, objectify_options(options), @default_options.merge(html_options))
end
def collection_select(method, collection, value_method, text_method, options = {}, html_options = {})
@template.collection_select(@object_name, method, collection, value_method, text_method, options.merge(:object => @object), html_options)
@template.collection_select(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options))
end
def country_select(method, priority_countries = nil, options = {}, html_options = {})
@template.country_select(@object_name, method, priority_countries, options.merge(:object => @object), html_options)
@template.country_select(@object_name, method, priority_countries, objectify_options(options), @default_options.merge(html_options))
end
def time_zone_select(method, priority_zones = nil, options = {}, html_options = {})
@template.time_zone_select(@object_name, method, priority_zones, options.merge(:object => @object), html_options)
@template.time_zone_select(@object_name, method, priority_zones, objectify_options(options), @default_options.merge(html_options))
end
end
end

View File

@@ -44,13 +44,22 @@ module ActionView
include PrototypeHelper
# Returns a link that will trigger a JavaScript +function+ using the
# Returns a link of the given +name+ that will trigger a JavaScript +function+ using the
# onclick handler and return false after the fact.
#
# The first argument +name+ is used as the link text.
#
# The next arguments are optional and may include the javascript function definition and a hash of html_options.
#
# The +function+ argument can be omitted in favor of an +update_page+
# block, which evaluates to a string when the template is rendered
# (instead of making an Ajax request first).
#
# The +html_options+ will accept a hash of html attributes for the link tag. Some examples are :class => "nav_button", :id => "articles_nav_button"
#
# Note: if you choose to specify the javascript function in a block, but would like to pass html_options, set the +function+ parameter to nil
#
#
# Examples:
# link_to_function "Greeting", "alert('Hello world!')"
# Produces:
@@ -89,13 +98,21 @@ module ActionView
content_tag(:a, name, html_options.merge(:href => href, :onclick => onclick))
end
# Returns a button that'll trigger a JavaScript +function+ using the
# Returns a button with the given +name+ text that'll trigger a JavaScript +function+ using the
# onclick handler.
#
# The first argument +name+ is used as the button's value or display text.
#
# The next arguments are optional and may include the javascript function definition and a hash of html_options.
#
# The +function+ argument can be omitted in favor of an +update_page+
# block, which evaluates to a string when the template is rendered
# (instead of making an Ajax request first).
#
# The +html_options+ will accept a hash of html attributes for the link tag. Some examples are :class => "nav_button", :id => "articles_nav_button"
#
# Note: if you choose to specify the javascript function in a block, but would like to pass html_options, set the +function+ parameter to nil
#
# Examples:
# button_to_function "Greeting", "alert('Hello world!')"
# button_to_function "Delete", "if (confirm('Really?')) do_delete()"
@@ -114,32 +131,6 @@ module ActionView
tag(:input, html_options.merge(:type => 'button', :value => name, :onclick => onclick))
end
# Includes the Action Pack JavaScript libraries inside a single <script>
# tag. The function first includes prototype.js and then its core extensions,
# (determined by filenames starting with "prototype").
# Afterwards, any additional scripts will be included in undefined order.
#
# Note: The recommended approach is to copy the contents of
# lib/action_view/helpers/javascripts/ into your application's
# public/javascripts/ directory, and use +javascript_include_tag+ to
# create remote <script> links.
def define_javascript_functions
javascript = "<script type=\"#{Mime::JS}\">"
# load prototype.js and its extensions first
prototype_libs = Dir.glob(File.join(JAVASCRIPT_PATH, 'prototype*')).sort.reverse
prototype_libs.each do |filename|
javascript << "\n" << IO.read(filename)
end
# load other libraries
(Dir.glob(File.join(JAVASCRIPT_PATH, '*')) - prototype_libs).each do |filename|
javascript << "\n" << IO.read(filename)
end
javascript << '</script>'
end
JS_ESCAPE_MAP = {
'\\' => '\\\\',
'</' => '<\/',
@@ -216,7 +207,5 @@ module ActionView
end
end
end
JavascriptHelper = JavaScriptHelper unless const_defined? :JavascriptHelper
end
end

File diff suppressed because it is too large Load Diff

View File

@@ -184,7 +184,7 @@ module ActionView
HTML::WhiteListSanitizer.allowed_attributes.merge(attributes)
end
# Adds to the Set of allowed CSS properties for the #sanitize and +sanitize_css+ heleprs.
# Adds to the Set of allowed CSS properties for the #sanitize and +sanitize_css+ helpers.
#
# Rails::Initializer.run do |config|
# config.action_view.sanitized_allowed_css_properties = 'expression'

View File

@@ -193,7 +193,7 @@ module ActionView
#
# * <tt>:onDrop</tt> - Called when a +draggable_element+ is dropped onto
# this element. Override this callback with a JavaScript expression to
# change the default drop behavour. Example:
# change the default drop behaviour. Example:
#
# :onDrop => "function(draggable_element, droppable_element, event) { alert('I like bananas') }"
#

View File

@@ -64,7 +64,7 @@ module ActionView
# <% content_tag :div, :class => "strong" do -%>
# Hello world!
# <% end -%>
# # => <div class="strong"><p>Hello world!</p></div>
# # => <div class="strong">Hello world!</div>
def content_tag(name, content_or_options_with_block = nil, options = nil, escape = true, &block)
if block_given?
options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash)
@@ -110,12 +110,18 @@ module ActionView
private
BLOCK_CALLED_FROM_ERB = 'defined? __in_erb_template'
# Check whether we're called from an erb template.
# We'd return a string in any other case, but erb <%= ... %>
# can't take an <% end %> later on, so we have to use <% ... %>
# and implicitly concat.
def block_called_from_erb?(block)
block && eval(BLOCK_CALLED_FROM_ERB, block)
if RUBY_VERSION < '1.9.0'
# Check whether we're called from an erb template.
# We'd return a string in any other case, but erb <%= ... %>
# can't take an <% end %> later on, so we have to use <% ... %>
# and implicitly concat.
def block_called_from_erb?(block)
block && eval(BLOCK_CALLED_FROM_ERB, block)
end
else
def block_called_from_erb?(block)
block && eval(BLOCK_CALLED_FROM_ERB, block.binding)
end
end
def content_tag_string(name, content, options, escape = true)

View File

@@ -27,7 +27,7 @@ module ActionView
# %>
def concat(string, unused_binding = nil)
if unused_binding
ActiveSupport::Deprecation.warn("The binding argument of #concat is no longer needed. Please remove it from your views and helpers.")
ActiveSupport::Deprecation.warn("The binding argument of #concat is no longer needed. Please remove it from your views and helpers.", caller)
end
output_buffer << string

View File

@@ -63,17 +63,15 @@ module ActionView
# # calls @workshop.to_s
# # => /workshops/5
def url_for(options = {})
options ||= {}
case options
when Hash
show_path = options[:host].nil? ? true : false
options = { :only_path => show_path }.update(options.symbolize_keys)
options = { :only_path => options[:host].nil? }.update(options.symbolize_keys)
escape = options.key?(:escape) ? options.delete(:escape) : true
url = @controller.send(:url_for, options)
when String
escape = true
url = options
when NilClass
url = @controller.send(:url_for, nil)
else
escape = false
url = polymorphic_path(options)
@@ -187,7 +185,7 @@ module ActionView
# link_to "Nonsense search", searches_path(:foo => "bar", :baz => "quux")
# # => <a href="/searches?foo=bar&amp;baz=quux">Nonsense search</a>
#
# The three options specfic to +link_to+ (<tt>:confirm</tt>, <tt>:popup</tt>, and <tt>:method</tt>) are used as follows:
# The three options specific to +link_to+ (<tt>:confirm</tt>, <tt>:popup</tt>, and <tt>:method</tt>) are used as follows:
#
# link_to "Visit Other Site", "http://www.rubyonrails.org/", :confirm => "Are you sure?"
# # => <a href="http://www.rubyonrails.org/" onclick="return confirm('Are you sure?');">Visit Other Site</a>
@@ -468,7 +466,7 @@ module ActionView
email_address_obfuscated.gsub!(/\./, html_options.delete("replace_dot")) if html_options.has_key?("replace_dot")
if encode == "javascript"
"document.write('#{content_tag("a", name || email_address, html_options.merge({ "href" => "mailto:"+email_address+extras }))}');".each_byte do |c|
"document.write('#{content_tag("a", name || email_address_obfuscated, html_options.merge({ "href" => "mailto:"+email_address+extras }))}');".each_byte do |c|
string << sprintf("%%%x", c)
end
"<script type=\"#{Mime::JS}\">eval(unescape('#{string}'))</script>"

View File

@@ -1,17 +1,19 @@
module ActionView #:nodoc:
class InlineTemplate < Template #:nodoc:
def initialize(view, source, locals = {}, type = nil)
@view = view
class InlineTemplate #:nodoc:
include Renderable
attr_reader :source, :extension, :method_segment
def initialize(source, type = nil)
@source = source
@extension = type
@locals = locals || {}
@handler = self.class.handler_class_for_extension(@extension).new(@view)
@method_segment = "inline_#{@source.hash.abs}"
end
def method_key
@source
end
private
# Always recompile inline templates
def recompile?(local_assigns)
true
end
end
end

View File

@@ -1,74 +0,0 @@
module ActionView #:nodoc:
class PartialTemplate < Template #:nodoc:
attr_reader :variable_name, :object, :as
def initialize(view, partial_path, object = nil, locals = {}, as = nil)
@view_controller = view.controller if view.respond_to?(:controller)
@as = as
set_path_and_variable_name!(partial_path)
super(view, @path, true, locals)
add_object_to_local_assigns!(object)
# This is needed here in order to compile template with knowledge of 'counter'
initialize_counter!
# Prepare early. This is a performance optimization for partial collections
prepare!
end
def render
ActionController::Base.benchmark("Rendered #{@path.path_without_format_and_extension}", Logger::DEBUG, false) do
@handler.render(self)
end
end
def render_member(object)
@locals[:object] = @locals[@variable_name] = object
@locals[as] = object if as
template = render_template
@locals[@counter_name] += 1
@locals.delete(as)
@locals.delete(@variable_name)
@locals.delete(:object)
template
end
def counter=(num)
@locals[@counter_name] = num
end
private
def add_object_to_local_assigns!(object)
@locals[:object] ||=
@locals[@variable_name] ||=
if object.is_a?(ActionView::Base::ObjectWrapper)
object.value
else
object
end || @view_controller.instance_variable_get("@#{variable_name}")
@locals[as] ||= @locals[:object] if as
end
def set_path_and_variable_name!(partial_path)
if partial_path.include?('/')
@variable_name = File.basename(partial_path)
@path = "#{File.dirname(partial_path)}/_#{@variable_name}"
elsif @view_controller
@variable_name = partial_path
@path = "#{@view_controller.class.controller_path}/_#{@variable_name}"
else
@variable_name = partial_path
@path = "_#{@variable_name}"
end
@variable_name = @variable_name.sub(/\..*$/, '').to_sym
end
def initialize_counter!
@counter_name ||= "#{@variable_name}_counter".to_sym
@locals[@counter_name] = 0
end
end
end

View File

@@ -104,10 +104,11 @@ module ActionView
module Partials
private
def render_partial(partial_path, object_assigns = nil, local_assigns = {}) #:nodoc:
local_assigns ||= {}
case partial_path
when String, Symbol, NilClass
# Render the template
ActionView::PartialTemplate.new(self, partial_path, object_assigns, local_assigns).render_template
pick_template(find_partial_path(partial_path)).render_partial(self, object_assigns, local_assigns)
when ActionView::Helpers::FormBuilder
builder_partial_path = partial_path.class.to_s.demodulize.underscore.sub(/_builder$/, '')
render_partial(builder_partial_path, object_assigns, (local_assigns || {}).merge(builder_partial_path.to_sym => partial_path))
@@ -128,30 +129,28 @@ module ActionView
local_assigns = local_assigns ? local_assigns.clone : {}
spacer = partial_spacer_template ? render(:partial => partial_spacer_template) : ''
_paths = {}
_templates = {}
if partial_path.nil?
render_partial_collection_with_unknown_partial_path(collection, local_assigns, as)
else
render_partial_collection_with_known_partial_path(collection, partial_path, local_assigns, as)
index = 0
collection.map do |object|
_partial_path ||= partial_path || ActionController::RecordIdentifier.partial_path(object, controller.class.controller_path)
path = _paths[_partial_path] ||= find_partial_path(_partial_path)
template = _templates[path] ||= pick_template(path)
local_assigns[template.counter_name] = index
result = template.render_partial(self, object, local_assigns, as)
index += 1
result
end.join(spacer)
end
def render_partial_collection_with_known_partial_path(collection, partial_path, local_assigns, as)
template = ActionView::PartialTemplate.new(self, partial_path, nil, local_assigns, as)
collection.map do |element|
template.render_member(element)
end
end
def render_partial_collection_with_unknown_partial_path(collection, local_assigns, as)
templates = Hash.new
i = 0
collection.map do |element|
partial_path = ActionController::RecordIdentifier.partial_path(element, controller.class.controller_path)
template = templates[partial_path] ||= ActionView::PartialTemplate.new(self, partial_path, nil, local_assigns, as)
template.counter = i
i += 1
template.render_member(element)
def find_partial_path(partial_path)
if partial_path.include?('/')
"#{File.dirname(partial_path)}/_#{File.basename(partial_path)}"
elsif respond_to?(:controller)
"#{controller.class.controller_path}/_#{partial_path}"
else
"_#{partial_path}"
end
end
end

View File

@@ -0,0 +1,104 @@
module ActionView #:nodoc:
class PathSet < Array #:nodoc:
def self.type_cast(obj)
if obj.is_a?(String)
if Base.warn_cache_misses && defined?(Rails) && Rails.initialized?
Rails.logger.debug "[PERFORMANCE] Processing view path during a " +
"request. This an expense disk operation that should be done at " +
"boot. You can manually process this view path with " +
"ActionView::Base.process_view_paths(#{obj.inspect}) and set it " +
"as your view path"
end
Path.new(obj)
else
obj
end
end
class Path #:nodoc:
def self.eager_load_templates!
@eager_load_templates = true
end
def self.eager_load_templates?
@eager_load_templates || false
end
attr_reader :path, :paths
delegate :to_s, :to_str, :inspect, :to => :path
def initialize(path)
@path = path.freeze
reload!
end
def ==(path)
to_str == path.to_str
end
def [](path)
@paths[path]
end
# Rebuild load path directory cache
def reload!
@paths = {}
templates_in_path do |template|
# Eager load memoized methods and freeze cached template
template.freeze if self.class.eager_load_templates?
@paths[template.path] = template
@paths[template.path_without_extension] ||= template
end
@paths.freeze
end
private
def templates_in_path
(Dir.glob("#{@path}/**/*/**") | Dir.glob("#{@path}/**")).each do |file|
unless File.directory?(file)
yield Template.new(file.split("#{self}/").last, self)
end
end
end
end
def initialize(*args)
super(*args).map! { |obj| self.class.type_cast(obj) }
end
def reload!
each { |path| path.reload! }
end
def <<(obj)
super(self.class.type_cast(obj))
end
def push(*objs)
delete_paths!(objs)
super(*objs.map { |obj| self.class.type_cast(obj) })
end
def unshift(*objs)
delete_paths!(objs)
super(*objs.map { |obj| self.class.type_cast(obj) })
end
def [](template_path)
each do |path|
if template = path[template_path]
return template
end
end
nil
end
private
def delete_paths!(paths)
paths.each { |p1| delete_if { |p2| p1.to_s == p2.to_s } }
end
end
end

View File

@@ -0,0 +1,87 @@
module ActionView
module Renderable
# NOTE: The template that this mixin is beening include into is frozen
# So you can not set or modify any instance variables
def self.included(base)
@@mutex = Mutex.new
end
include ActiveSupport::Memoizable
def filename
'compiled-template'
end
def handler
Template.handler_class_for_extension(extension)
end
memoize :handler
def compiled_source
handler.new(nil).compile(self) if handler.compilable?
end
memoize :compiled_source
def render(view, local_assigns = {})
view._first_render ||= self
view._last_render = self
view.send(:evaluate_assigns)
compile(local_assigns) if handler.compilable?
handler.new(view).render(self, local_assigns)
end
def method(local_assigns)
if local_assigns && local_assigns.any?
local_assigns_keys = "locals_#{local_assigns.keys.map { |k| k.to_s }.sort.join('_')}"
end
['_run', extension, method_segment, local_assigns_keys].compact.join('_').to_sym
end
private
# Compile and evaluate the template's code (if necessary)
def compile(local_assigns)
render_symbol = method(local_assigns)
@@mutex.synchronize do
if recompile?(render_symbol)
compile!(render_symbol, local_assigns)
end
end
end
def compile!(render_symbol, local_assigns)
locals_code = local_assigns.keys.map { |key| "#{key} = local_assigns[:#{key}];" }.join
source = <<-end_src
def #{render_symbol}(local_assigns)
old_output_buffer = output_buffer;#{locals_code};#{compiled_source}
ensure
self.output_buffer = old_output_buffer
end
end_src
begin
logger = ActionController::Base.logger
logger.debug "Compiling template #{render_symbol}" if logger
ActionView::Base::CompiledTemplates.module_eval(source, filename, 0)
rescue Exception => e # errors from template code
if logger
logger.debug "ERROR: compiling #{render_symbol} RAISED #{e}"
logger.debug "Function body: #{source}"
logger.debug "Backtrace: #{e.backtrace.join("\n")}"
end
raise ActionView::TemplateError.new(self, {}, e)
end
end
# Method to check whether template compilation is necessary.
# The template will be compiled if the file has not been compiled yet, or
# if local_assigns has a new key, which isn't supported by the compiled code yet.
def recompile?(symbol)
!(frozen? && Base::CompiledTemplates.method_defined?(symbol))
end
end
end

View File

@@ -0,0 +1,36 @@
module ActionView
module RenderablePartial
# NOTE: The template that this mixin is beening include into is frozen
# So you can not set or modify any instance variables
include ActiveSupport::Memoizable
def variable_name
name.sub(/\A_/, '').to_sym
end
memoize :variable_name
def counter_name
"#{variable_name}_counter".to_sym
end
memoize :counter_name
def render(view, local_assigns = {})
ActionController::Base.benchmark("Rendered #{path_without_format_and_extension}", Logger::DEBUG, false) do
super
end
end
def render_partial(view, object = nil, local_assigns = {}, as = nil)
object ||= local_assigns[:object] ||
local_assigns[variable_name] ||
view.controller.instance_variable_get("@#{variable_name}") if view.respond_to?(:controller)
# Ensure correct object is reassigned to other accessors
local_assigns[:object] = local_assigns[variable_name] = object
local_assigns[as] = object if as
render_template(view, local_assigns)
end
end
end

View File

@@ -1,94 +1,96 @@
module ActionView #:nodoc:
class Template #:nodoc:
class Template
extend TemplateHandlers
include ActiveSupport::Memoizable
include Renderable
attr_accessor :locals
attr_reader :handler, :path, :extension, :filename, :method
attr_accessor :filename, :load_path, :base_path, :name, :format, :extension
delegate :to_s, :to => :path
def initialize(view, path, use_full_path, locals = {})
@view = view
@paths = view.view_paths
def initialize(template_path, load_paths = [])
template_path = template_path.dup
@base_path, @name, @format, @extension = split(template_path)
@base_path.to_s.gsub!(/\/$/, '') # Push to split method
@load_path, @filename = find_full_path(template_path, load_paths)
@original_path = path
@path = TemplateFile.from_path(path, !use_full_path)
@view.first_render ||= @path.to_s
@source = nil # Don't read the source until we know that it is required
set_extension_and_file_name(use_full_path)
@locals = locals || {}
@handler = self.class.handler_class_for_extension(@extension).new(@view)
# Extend with partial super powers
extend RenderablePartial if @name =~ /^_/
end
def render_template
render
def format_and_extension
(extensions = [format, extension].compact.join(".")).blank? ? nil : extensions
end
memoize :format_and_extension
def path
[base_path, [name, format, extension].compact.join('.')].compact.join('/')
end
memoize :path
def path_without_extension
[base_path, [name, format].compact.join('.')].compact.join('/')
end
memoize :path_without_extension
def path_without_format_and_extension
[base_path, name].compact.join('/')
end
memoize :path_without_format_and_extension
def source
File.read(filename)
end
memoize :source
def method_segment
segment = File.expand_path(filename)
segment.sub!(/^#{Regexp.escape(File.expand_path(RAILS_ROOT))}/, '') if defined?(RAILS_ROOT)
segment.gsub!(/([^a-zA-Z0-9_])/) { $1.ord }
end
memoize :method_segment
def render_template(view, local_assigns = {})
render(view, local_assigns)
rescue Exception => e
raise e unless filename
if TemplateError === e
e.sub_template_of(filename)
raise e
else
raise TemplateError.new(self, @view.assigns, e)
end
end
def render
prepare!
@handler.render(self)
end
def path_without_extension
@path.path_without_extension
end
def source
@source ||= File.read(self.filename)
end
def method_key
@filename
end
def base_path_for_exception
(@paths.find_load_path_for_path(@path) || @paths.first).to_s
end
def prepare!
@view.send :evaluate_assigns
@view.current_render_extension = @extension
if @handler.compilable?
@handler.compile_template(self) # compile the given template, if necessary
@method = @view.method_names[method_key] # Set the method name for this template and run it
raise TemplateError.new(self, view.assigns, e)
end
end
private
def set_extension_and_file_name(use_full_path)
@extension = @path.extension
if use_full_path
unless @extension
@path = @view.send(:template_file_from_name, @path)
raise_missing_template_exception unless @path
@extension = @path.extension
end
if @path = @paths.find_template_file_for_path(path)
@filename = @path.full_path
@extension = @path.extension
end
else
@filename = @path.full_path
end
raise_missing_template_exception if @filename.blank?
def valid_extension?(extension)
Template.template_handler_extensions.include?(extension)
end
def raise_missing_template_exception
full_template_path = @original_path.include?('.') ? @original_path : "#{@original_path}.#{@view.template_format}.erb"
display_paths = @paths.join(':')
template_type = (@original_path =~ /layouts/i) ? 'layout' : 'template'
raise(MissingTemplate, "Missing #{template_type} #{full_template_path} in view path #{display_paths}")
def find_full_path(path, load_paths)
load_paths = Array(load_paths) + [nil]
load_paths.each do |load_path|
file = [load_path, path].compact.join('/')
return load_path, file if File.exist?(file)
end
raise MissingTemplate.new(load_paths, path)
end
# Returns file split into an array
# [base_path, name, format, extension]
def split(file)
if m = file.match(/^(.*\/)?([^\.]+)\.?(\w+)?\.?(\w+)?\.?(\w+)?$/)
if m[5] # Mulipart formats
[m[1], m[2], "#{m[3]}.#{m[4]}", m[5]]
elsif m[4] # Single format
[m[1], m[2], m[3], m[4]]
else
if valid_extension?(m[3]) # No format
[m[1], m[2], nil, m[3]]
else # No extension
[m[1], m[2], m[3], nil]
end
end
end
end
end
end

View File

@@ -7,7 +7,7 @@ module ActionView
attr_reader :original_exception
def initialize(template, assigns, original_exception)
@base_path = template.base_path_for_exception
@base_path = template.base_path
@assigns, @source, @original_exception = assigns.dup, template.source, original_exception
@file_path = template.filename
@backtrace = compute_backtrace
@@ -105,6 +105,6 @@ module ActionView
end
if defined?(Exception::TraceSubstitutions)
Exception::TraceSubstitutions << [/:in\s+`_run_(html|xml).*'\s*$/, '']
Exception::TraceSubstitutions << [/:in\s+`_run_.*'\s*$/, '']
Exception::TraceSubstitutions << [%r{^\s*#{Regexp.escape RAILS_ROOT}/}, ''] if defined?(RAILS_ROOT)
end

View File

@@ -1,88 +0,0 @@
module ActionView #:nodoc:
# TemplateFile abstracts the pattern of querying a file path for its
# path with or without its extension. The path is only the partial path
# from the load path root e.g. "hello/index.html.erb" not
# "app/views/hello/index.html.erb"
class TemplateFile
def self.from_path(path, use_full_path = false)
path.is_a?(self) ? path : new(path, use_full_path)
end
def self.from_full_path(load_path, full_path)
file = new(full_path.split(load_path).last)
file.load_path = load_path
file.freeze
end
attr_accessor :load_path, :base_path, :name, :format, :extension
delegate :to_s, :inspect, :to => :path
def initialize(path, use_full_path = false)
path = path.dup
# Clear the forward slash in the beginning unless using full path
trim_forward_slash!(path) unless use_full_path
@base_path, @name, @format, @extension = split(path)
end
def freeze
@load_path.freeze
@base_path.freeze
@name.freeze
@format.freeze
@extension.freeze
super
end
def format_and_extension
extensions = [format, extension].compact.join(".")
extensions.blank? ? nil : extensions
end
def full_path
if load_path
"#{load_path}/#{path}"
else
path
end
end
def path
base_path.to_s + [name, format, extension].compact.join(".")
end
def path_without_extension
base_path.to_s + [name, format].compact.join(".")
end
def path_without_format_and_extension
"#{base_path}#{name}"
end
def dup_with_extension(extension)
file = dup
file.extension = extension ? extension.to_s : nil
file
end
private
def trim_forward_slash!(path)
path.sub!(/^\//, '')
end
# Returns file split into an array
# [base_path, name, format, extension]
def split(file)
if m = file.match(/^(.*\/)?(\w+)\.?(\w+)?\.?(\w+)?\.?(\w+)?$/)
if m[5] # Mulipart formats
[m[1], m[2], "#{m[3]}.#{m[4]}", m[5]]
elsif m[4] # Single format
[m[1], m[2], m[3], m[4]]
else # No format
[m[1], m[2], nil, m[3]]
end
end
end
end
end

View File

@@ -1,9 +1,5 @@
module ActionView
class TemplateHandler
def self.line_offset
0
end
def self.compilable?
false
end
@@ -12,7 +8,7 @@ module ActionView
@view = view
end
def render(template)
def render(template, local_assigns = {})
end
def compile(template)
@@ -21,13 +17,5 @@ module ActionView
def compilable?
self.class.compilable?
end
def line_offset
self.class.line_offset
end
# Called by CacheHelper#cache
def cache_fragment(block, name = {}, options = nil)
end
end
end

View File

@@ -5,23 +5,13 @@ module ActionView
class Builder < TemplateHandler
include Compilable
def self.line_offset
2
end
def compile(template)
content_type_handler = (@view.send!(:controller).respond_to?(:response) ? "controller.response" : "controller")
"#{content_type_handler}.content_type ||= Mime::XML\n" +
"xml = ::Builder::XmlMarkup.new(:indent => 2)\n" +
# ActionMailer does not have a response
"controller.respond_to?(:response) && controller.response.content_type ||= Mime::XML;" +
"xml = ::Builder::XmlMarkup.new(:indent => 2);" +
"self.output_buffer = xml.target!;" +
template.source +
"\nxml.target!\n"
end
def cache_fragment(block, name = {}, options = nil)
@view.fragment_for(block, name, options) do
eval('xml.target!', block.binding)
end
";xml.target!;"
end
end
end

View File

@@ -1,21 +1,8 @@
module ActionView
module TemplateHandlers
module Compilable
def self.included(base)
base.extend ClassMethod
# Map method names to their compile time
base.cattr_accessor :compile_time
base.compile_time = {}
# Map method names to the names passed in local assigns so far
base.cattr_accessor :template_args
base.template_args = {}
# Count the number of inline templates
base.cattr_accessor :inline_template_count
base.inline_template_count = 0
end
module ClassMethod
@@ -24,111 +11,10 @@ module ActionView
true
end
end
def render(template)
@view.send :execute, template
def render(template, local_assigns = {})
@view.send(:execute, template, local_assigns)
end
# Compile and evaluate the template's code
def compile_template(template)
return unless compile_template?(template)
render_symbol = assign_method_name(template)
render_source = create_template_source(template, render_symbol)
line_offset = self.template_args[render_symbol].size + self.line_offset
begin
file_name = template.filename || 'compiled-template'
ActionView::Base::CompiledTemplates.module_eval(render_source, file_name, -line_offset)
rescue Exception => e # errors from template code
if @view.logger
@view.logger.debug "ERROR: compiling #{render_symbol} RAISED #{e}"
@view.logger.debug "Function body: #{render_source}"
@view.logger.debug "Backtrace: #{e.backtrace.join("\n")}"
end
raise ActionView::TemplateError.new(template, @view.assigns, e)
end
self.compile_time[render_symbol] = Time.now
# logger.debug "Compiled template #{file_name || template}\n ==> #{render_symbol}" if logger
end
private
# Method to check whether template compilation is necessary.
# The template will be compiled if the inline template or file has not been compiled yet,
# if local_assigns has a new key, which isn't supported by the compiled code yet,
# or if the file has changed on disk and checking file mods hasn't been disabled.
def compile_template?(template)
method_key = template.method_key
render_symbol = @view.method_names[method_key]
compile_time = self.compile_time[render_symbol]
if compile_time && supports_local_assigns?(render_symbol, template.locals)
if template.filename && !@view.cache_template_loading
template_changed_since?(template.filename, compile_time)
end
else
true
end
end
def assign_method_name(template)
@view.method_names[template.method_key] ||= compiled_method_name(template)
end
def compiled_method_name(template)
['_run', self.class.to_s.demodulize.underscore, compiled_method_name_file_path_segment(template.filename)].compact.join('_').to_sym
end
def compiled_method_name_file_path_segment(file_name)
if file_name
s = File.expand_path(file_name)
s.sub!(/^#{Regexp.escape(File.expand_path(RAILS_ROOT))}/, '') if defined?(RAILS_ROOT)
s.gsub!(/([^a-zA-Z0-9_])/) { $1.ord }
s
else
(self.inline_template_count += 1).to_s
end
end
# Method to create the source code for a given template.
def create_template_source(template, render_symbol)
body = compile(template)
self.template_args[render_symbol] ||= {}
locals_keys = self.template_args[render_symbol].keys | template.locals.keys
self.template_args[render_symbol] = locals_keys.inject({}) { |h, k| h[k] = true; h }
locals_code = ""
locals_keys.each do |key|
locals_code << "#{key} = local_assigns[:#{key}]\n"
end
<<-end_src
def #{render_symbol}(local_assigns)
old_output_buffer = output_buffer;#{locals_code}#{body}
ensure
self.output_buffer = old_output_buffer
end
end_src
end
# Return true if the given template was compiled for a superset of the keys in local_assigns
def supports_local_assigns?(render_symbol, local_assigns)
local_assigns.empty? ||
((args = self.template_args[render_symbol]) && local_assigns.all? { |k,_| args.has_key?(k) })
end
# Method to handle checking a whether a template has changed since last compile; isolated so that templates
# not stored on the file system can hook and extend appropriately.
def template_changed_since?(file_name, compile_time)
lstat = File.lstat(file_name)
compile_time < lstat.mtime ||
(lstat.symlink? && compile_time < File.stat(file_name).mtime)
end
end
end
end

View File

@@ -51,12 +51,6 @@ module ActionView
src = ::ERB.new(template.source, nil, erb_trim_mode, '@output_buffer').src
"__in_erb_template=true;#{src}"
end
def cache_fragment(block, name = {}, options = nil) #:nodoc:
@view.fragment_for(block, name, options) do
@view.response.template.output_buffer
end
end
end
end
end

View File

@@ -3,24 +3,9 @@ module ActionView
class RJS < TemplateHandler
include Compilable
def self.line_offset
2
end
def compile(template)
"controller.response.content_type ||= Mime::JS\n" +
"update_page do |page|\n#{template.source}\nend"
end
def cache_fragment(block, name = {}, options = nil) #:nodoc:
@view.fragment_for(block, name, options) do
begin
debug_mode, ActionView::Base.debug_rjs = ActionView::Base.debug_rjs, false
eval('page.to_s', block.binding)
ensure
ActionView::Base.debug_rjs = debug_mode
end
end
"controller.response.content_type ||= Mime::JS;" +
"update_page do |page|;#{template.source}\nend"
end
end
end

View File

@@ -1,99 +0,0 @@
module ActionView #:nodoc:
class ViewLoadPaths < Array #:nodoc:
def self.type_cast(obj)
obj.is_a?(String) ? LoadPath.new(obj) : obj
end
class LoadPath #:nodoc:
attr_reader :path, :paths
delegate :to_s, :to_str, :inspect, :to => :path
def initialize(path)
@path = path.freeze
reload!
end
def ==(path)
to_str == path.to_str
end
# Rebuild load path directory cache
def reload!
@paths = {}
files.each do |file|
@paths[file.path] = file
@paths[file.path_without_extension] ||= file
end
@paths.freeze
end
# Tries to find the extension for the template name.
# If it does not it exist, tries again without the format extension
# find_template_file_for_partial_path('users/show') => 'html.erb'
# find_template_file_for_partial_path('users/legacy') => 'rhtml'
def find_template_file_for_partial_path(file)
@paths[file.path] || @paths[file.path_without_extension] || @paths[file.path_without_format_and_extension]
end
private
# Get all the files and directories in the path
def files_in_path
Dir.glob("#{@path}/**/*/**") | Dir.glob("#{@path}/**")
end
# Create an array of all the files within the path
def files
files_in_path.map do |file|
TemplateFile.from_full_path(@path, file) unless File.directory?(file)
end.compact
end
end
def initialize(*args)
super(*args).map! { |obj| self.class.type_cast(obj) }
end
def reload!
each { |path| path.reload! }
end
def <<(obj)
super(self.class.type_cast(obj))
end
def push(*objs)
delete_paths!(objs)
super(*objs.map { |obj| self.class.type_cast(obj) })
end
def unshift(*objs)
delete_paths!(objs)
super(*objs.map { |obj| self.class.type_cast(obj) })
end
def template_exists?(file)
find_load_path_for_path(file) ? true : false
end
def find_load_path_for_path(file)
find { |path| path.paths[file.to_s] }
end
def find_template_file_for_path(file)
file = TemplateFile.from_path(file)
each do |path|
if f = path.find_template_file_for_partial_path(file)
return f
end
end
nil
end
private
def delete_paths!(paths)
paths.each { |p1| delete_if { |p2| p1.to_s == p2.to_s } }
end
end
end

View File

@@ -22,7 +22,9 @@ ActiveSupport::Deprecation.debug = true
ActionController::Base.logger = nil
ActionController::Routing::Routes.reload rescue nil
FIXTURE_LOAD_PATH = ActionView::ViewLoadPaths::LoadPath.new(File.join(File.dirname(__FILE__), 'fixtures'))
FIXTURE_LOAD_PATH = File.join(File.dirname(__FILE__), 'fixtures')
ActionView::PathSet::Path.eager_load_templates!
ActionController::Base.view_paths = FIXTURE_LOAD_PATH
# Wrap tests that use Mocha and skip if unavailable.
def uses_mocha(test_name)

View File

@@ -41,8 +41,6 @@ class RenderPartialWithRecordIdentificationController < ActionController::Base
end
end
RenderPartialWithRecordIdentificationController.view_paths = [FIXTURE_LOAD_PATH]
class RenderPartialWithRecordIdentificationTest < ActiveRecordTestCase
fixtures :developers, :projects, :developers_projects, :topics, :replies, :companies, :mascots
@@ -56,26 +54,31 @@ class RenderPartialWithRecordIdentificationTest < ActiveRecordTestCase
def test_rendering_partial_with_has_many_and_belongs_to_association
get :render_with_has_many_and_belongs_to_association
assert_template 'projects/_project'
assert_equal 'Active RecordActive Controller', @response.body
end
def test_rendering_partial_with_has_many_association
get :render_with_has_many_association
assert_template 'replies/_reply'
assert_equal 'Birdman is better!', @response.body
end
def test_rendering_partial_with_named_scope
get :render_with_named_scope
assert_template 'replies/_reply'
assert_equal 'Birdman is better!Nuh uh!', @response.body
end
def test_render_with_record
get :render_with_record
assert_template 'developers/_developer'
assert_equal 'David', @response.body
end
def test_render_with_record_collection
get :render_with_record_collection
assert_template 'developers/_developer'
assert_equal 'DavidJamisfixture_3fixture_4fixture_5fixture_6fixture_7fixture_8fixture_9fixture_10Jamis', @response.body
end
def test_rendering_partial_with_has_one_association
@@ -118,8 +121,6 @@ class RenderPartialWithRecordIdentificationController < ActionController::Base
end
end
RenderPartialWithRecordIdentificationController.view_paths = [FIXTURE_LOAD_PATH]
class Game < Struct.new(:name, :id)
def to_param
id.to_s
@@ -137,8 +138,6 @@ module Fun
end
end
NestedController.view_paths = [FIXTURE_LOAD_PATH]
module Serious
class NestedDeeperController < ActionController::Base
def render_with_record_in_deeper_nested_controller
@@ -149,8 +148,6 @@ module Fun
render :partial => [ Game.new("Chess"), Game.new("Sudoku"), Game.new("Solitaire") ]
end
end
NestedDeeperController.view_paths = [FIXTURE_LOAD_PATH]
end
end
@@ -165,11 +162,13 @@ class RenderPartialWithRecordIdentificationAndNestedControllersTest < ActiveReco
def test_render_with_record_in_nested_controller
get :render_with_record_in_nested_controller
assert_template 'fun/games/_game'
assert_equal 'Pong', @response.body
end
def test_render_with_record_collection_in_nested_controller
get :render_with_record_collection_in_nested_controller
assert_template 'fun/games/_game'
assert_equal 'PongTank', @response.body
end
end
@@ -184,10 +183,12 @@ class RenderPartialWithRecordIdentificationAndNestedDeeperControllersTest < Acti
def test_render_with_record_in_deeper_nested_controller
get :render_with_record_in_deeper_nested_controller
assert_template 'fun/serious/games/_game'
assert_equal 'Chess', @response.body
end
def test_render_with_record_collection_in_deeper_nested_controller
get :render_with_record_collection_in_deeper_nested_controller
assert_template 'fun/serious/games/_game'
assert_equal 'ChessSudokuSolitaire', @response.body
end
end

View File

@@ -164,14 +164,6 @@ module Admin
end
end
# ---------------------------------------------------------------------------
# tell the controller where to find its templates but start from parent
# directory of test_request_response to simulate the behaviour of a
# production environment
ActionPackAssertionsController.view_paths = [FIXTURE_LOAD_PATH]
# a test case to exercise the new capabilities TestRequest & TestResponse
class ActionPackAssertionsControllerTest < Test::Unit::TestCase
# let's get this party started
@@ -232,7 +224,6 @@ class ActionPackAssertionsControllerTest < Test::Unit::TestCase
process :redirect_to_named_route
assert_redirected_to 'http://test.host/route_one'
assert_redirected_to route_one_url
assert_redirected_to :route_one_url
end
end
@@ -253,9 +244,6 @@ class ActionPackAssertionsControllerTest < Test::Unit::TestCase
assert_raise(Test::Unit::AssertionFailedError) do
assert_redirected_to route_two_url
end
assert_raise(Test::Unit::AssertionFailedError) do
assert_redirected_to :route_two_url
end
end
end
@@ -340,11 +328,11 @@ class ActionPackAssertionsControllerTest < Test::Unit::TestCase
# check if we were rendered by a file-based template?
def test_rendered_action
process :nothing
assert !@response.rendered_with_file?
assert_nil @response.rendered_template
process :hello_world
assert @response.rendered_with_file?
assert 'hello_world', @response.rendered_file
assert @response.rendered_template
assert 'hello_world', @response.rendered_template.to_s
end
# check the redirection location
@@ -419,22 +407,6 @@ class ActionPackAssertionsControllerTest < Test::Unit::TestCase
assert_equal "Mr. David", @response.body
end
def test_follow_redirect
process :redirect_to_action
assert_redirected_to :action => "flash_me"
follow_redirect
assert_equal 1, @request.parameters["id"].to_i
assert "Inconceivable!", @response.body
end
def test_follow_redirect_outside_current_action
process :redirect_to_controller
assert_redirected_to :controller => "elsewhere", :action => "flash_me"
assert_raises(RuntimeError, "Can't follow redirects outside of current controller (elsewhere)") { follow_redirect }
end
def test_assert_redirection_fails_with_incorrect_controller
process :redirect_to_controller
@@ -448,14 +420,16 @@ class ActionPackAssertionsControllerTest < Test::Unit::TestCase
assert_redirected_to :controller => 'action_pack_assertions', :action => "flash_me", :id => 1, :params => { :panda => 'fun' }
end
def test_redirected_to_url_leadling_slash
def test_redirected_to_url_leading_slash
process :redirect_to_path
assert_redirected_to '/some/path'
end
def test_redirected_to_url_no_leadling_slash
process :redirect_to_path
assert_redirected_to 'some/path'
end
def test_redirected_to_url_full_url
process :redirect_to_path
assert_redirected_to 'http://test.host/some/path'
@@ -475,7 +449,7 @@ class ActionPackAssertionsControllerTest < Test::Unit::TestCase
def test_redirected_to_with_nested_controller
@controller = Admin::InnerModuleController.new
get :redirect_to_absolute_controller
assert_redirected_to :controller => 'content'
assert_redirected_to :controller => '/content'
get :redirect_to_fellow_controller
assert_redirected_to :controller => 'admin/user'

View File

@@ -19,8 +19,6 @@ class AddressesTestController < ActionController::Base
def self.controller_path; "addresses"; end
end
AddressesTestController.view_paths = [FIXTURE_LOAD_PATH]
class AddressesTest < Test::Unit::TestCase
def setup
@controller = AddressesTestController.new

View File

@@ -6,7 +6,6 @@ CACHE_DIR = 'test_cache'
FILE_STORE_PATH = File.join(File.dirname(__FILE__), '/../temp/', CACHE_DIR)
ActionController::Base.page_cache_directory = FILE_STORE_PATH
ActionController::Base.cache_store = :file_store, FILE_STORE_PATH
ActionController::Base.view_paths = [FIXTURE_LOAD_PATH]
class PageCachingTestController < ActionController::Base
caches_page :ok, :no_content, :if => Proc.new { |c| !c.request.format.json? }
@@ -131,8 +130,7 @@ class PageCachingTest < Test::Unit::TestCase
end
def test_page_caching_conditional_options
@request.env['HTTP_ACCEPT'] = 'application/json'
get :ok
get :ok, :format=>'json'
assert_page_not_cached :ok
end
@@ -150,9 +148,8 @@ class PageCachingTest < Test::Unit::TestCase
end
end
class ActionCachingTestController < ActionController::Base
caches_action :index, :redirected, :forbidden, :if => Proc.new { |c| !c.request.format.json? }
caches_action :index, :redirected, :forbidden, :if => Proc.new { |c| !c.request.format.json? }, :expires_in => 1.hour
caches_action :show, :cache_path => 'http://test.host/custom/show'
caches_action :edit, :cache_path => Proc.new { |c| c.params[:id] ? "http://test.host/#{c.params[:id]};edit" : "http://test.host/edit" }
caches_action :with_layout
@@ -188,6 +185,7 @@ class ActionCachingTestController < ActionController::Base
expire_action :controller => 'action_caching_test', :action => 'index'
render :nothing => true
end
def expire_xml
expire_action :controller => 'action_caching_test', :action => 'index', :format => 'xml'
render :nothing => true
@@ -218,6 +216,7 @@ class ActionCachingMockController
Object.new.instance_eval(<<-EVAL)
def path; '#{@mock_path}' end
def format; 'all' end
def cache_format; nil end
self
EVAL
end
@@ -284,9 +283,19 @@ class ActionCacheTest < Test::Unit::TestCase
end
def test_action_cache_conditional_options
old_use_accept_header = ActionController::Base.use_accept_header
ActionController::Base.use_accept_header = true
@request.env['HTTP_ACCEPT'] = 'application/json'
get :index
assert !fragment_exist?('hostname.com/action_caching_test')
ActionController::Base.use_accept_header = old_use_accept_header
end
def test_action_cache_with_store_options
MockTime.expects(:now).returns(12345).once
@controller.expects(:read_fragment).with('hostname.com/action_caching_test', :expires_in => 1.hour).once
@controller.expects(:write_fragment).with('hostname.com/action_caching_test', '12345.0', :expires_in => 1.hour).once
get :index
end
def test_action_cache_with_custom_cache_path
@@ -406,12 +415,6 @@ class ActionCacheTest < Test::Unit::TestCase
assert_equal 'application/xml', @response.content_type
reset!
@request.env['HTTP_ACCEPT'] = "application/xml"
get :index
assert_equal cached_time, @response.body
assert_equal 'application/xml', @response.content_type
reset!
get :expire_xml
reset!
@@ -485,54 +488,54 @@ class FragmentCachingTest < Test::Unit::TestCase
def test_fragment_cache_key
assert_equal 'views/what a key', @controller.fragment_cache_key('what a key')
assert_equal( "views/test.host/fragment_caching_test/some_action",
@controller.fragment_cache_key(:controller => 'fragment_caching_test',:action => 'some_action'))
assert_equal "views/test.host/fragment_caching_test/some_action",
@controller.fragment_cache_key(:controller => 'fragment_caching_test',:action => 'some_action')
end
def test_read_fragment__with_caching_enabled
def test_read_fragment_with_caching_enabled
@store.write('views/name', 'value')
assert_equal 'value', @controller.read_fragment('name')
end
def test_read_fragment__with_caching_disabled
def test_read_fragment_with_caching_disabled
ActionController::Base.perform_caching = false
@store.write('views/name', 'value')
assert_nil @controller.read_fragment('name')
end
def test_fragment_exist__with_caching_enabled
def test_fragment_exist_with_caching_enabled
@store.write('views/name', 'value')
assert @controller.fragment_exist?('name')
assert !@controller.fragment_exist?('other_name')
end
def test_fragment_exist__with_caching_disabled
def test_fragment_exist_with_caching_disabled
ActionController::Base.perform_caching = false
@store.write('views/name', 'value')
assert !@controller.fragment_exist?('name')
assert !@controller.fragment_exist?('other_name')
end
def test_write_fragment__with_caching_enabled
def test_write_fragment_with_caching_enabled
assert_nil @store.read('views/name')
assert_equal 'value', @controller.write_fragment('name', 'value')
assert_equal 'value', @store.read('views/name')
end
def test_write_fragment__with_caching_disabled
def test_write_fragment_with_caching_disabled
assert_nil @store.read('views/name')
ActionController::Base.perform_caching = false
assert_equal nil, @controller.write_fragment('name', 'value')
assert_nil @store.read('views/name')
end
def test_expire_fragment__with_simple_key
def test_expire_fragment_with_simple_key
@store.write('views/name', 'value')
@controller.expire_fragment 'name'
assert_nil @store.read('views/name')
end
def test_expire_fragment__with__regexp
def test_expire_fragment_with_regexp
@store.write('views/name', 'value')
@store.write('views/another_name', 'another_value')
@store.write('views/primalgrasp', 'will not expire ;-)')
@@ -544,14 +547,14 @@ class FragmentCachingTest < Test::Unit::TestCase
assert_equal 'will not expire ;-)', @store.read('views/primalgrasp')
end
def test_fragment_for__with_disabled_caching
def test_fragment_for_with_disabled_caching
ActionController::Base.perform_caching = false
@store.write('views/expensive', 'fragment content')
fragment_computed = false
buffer = 'generated till now -> '
@controller.fragment_for(Proc.new { fragment_computed = true }, 'expensive') { buffer }
@controller.fragment_for(buffer, 'expensive') { fragment_computed = true }
assert fragment_computed
assert_equal 'generated till now -> ', buffer
@@ -562,53 +565,13 @@ class FragmentCachingTest < Test::Unit::TestCase
fragment_computed = false
buffer = 'generated till now -> '
@controller.fragment_for(Proc.new { fragment_computed = true }, 'expensive') { buffer}
@controller.fragment_for(buffer, 'expensive') { fragment_computed = true }
assert !fragment_computed
assert_equal 'generated till now -> fragment content', buffer
end
def test_cache_erb_fragment
@store.write('views/expensive', 'fragment content')
@controller.response.template.output_buffer = 'generated till now -> '
assert_equal( 'generated till now -> fragment content',
ActionView::TemplateHandlers::ERB.new(@controller).cache_fragment(Proc.new{ }, 'expensive'))
end
def test_cache_rxml_fragment
@store.write('views/expensive', 'fragment content')
xml = 'generated till now -> '
class << xml; def target!; to_s; end; end
assert_equal( 'generated till now -> fragment content',
ActionView::TemplateHandlers::Builder.new(@controller).cache_fragment(Proc.new{ }, 'expensive'))
end
def test_cache_rjs_fragment
@store.write('views/expensive', 'fragment content')
page = 'generated till now -> '
assert_equal( 'generated till now -> fragment content',
ActionView::TemplateHandlers::RJS.new(@controller).cache_fragment(Proc.new{ }, 'expensive'))
end
def test_cache_rjs_fragment_debug_mode_does_not_interfere
@store.write('views/expensive', 'fragment content')
page = 'generated till now -> '
begin
debug_mode, ActionView::Base.debug_rjs = ActionView::Base.debug_rjs, true
assert_equal( 'generated till now -> fragment content',
ActionView::TemplateHandlers::RJS.new(@controller).cache_fragment(Proc.new{ }, 'expensive'))
assert ActionView::Base.debug_rjs
ensure
ActionView::Base.debug_rjs = debug_mode
end
end
end
class FunctionalCachingController < ActionController::Base
def fragment_cached
end
@@ -625,14 +588,19 @@ class FunctionalCachingController < ActionController::Base
end
end
def formatted_fragment_cached
respond_to do |format|
format.html
format.xml
format.js
end
end
def rescue_action(e)
raise e
end
end
FunctionalCachingController.view_paths = [FIXTURE_LOAD_PATH]
class FunctionalFragmentCachingTest < Test::Unit::TestCase
def setup
ActionController::Base.perform_caching = true
@@ -662,10 +630,49 @@ CACHED
assert_match "Fragment caching in a partial", @store.read('views/test.host/functional_caching/html_fragment_cached_with_partial')
end
def test_render_inline_before_fragment_caching
get :inline_fragment_cached
assert_response :success
assert_match /Some inline content/, @response.body
assert_match /Some cached content/, @response.body
assert_match "Some cached content", @store.read('views/test.host/functional_caching/inline_fragment_cached')
end
def test_fragment_caching_in_rjs_partials
xhr :get, :js_fragment_cached_with_partial
assert_response :success
assert_match /Fragment caching in a partial/, @response.body
assert_match "Fragment caching in a partial", @store.read('views/test.host/functional_caching/js_fragment_cached_with_partial')
end
def test_html_formatted_fragment_caching
get :formatted_fragment_cached, :format => "html"
assert_response :success
expected_body = "<body>\n<p>ERB</p>\n</body>"
assert_equal expected_body, @response.body
assert_equal "<p>ERB</p>", @store.read('views/test.host/functional_caching/formatted_fragment_cached')
end
def test_xml_formatted_fragment_caching
get :formatted_fragment_cached, :format => "xml"
assert_response :success
expected_body = "<body>\n <p>Builder</p>\n</body>\n"
assert_equal expected_body, @response.body
assert_equal " <p>Builder</p>\n", @store.read('views/test.host/functional_caching/formatted_fragment_cached')
end
def test_js_formatted_fragment_caching
get :formatted_fragment_cached, :format => "js"
assert_response :success
expected_body = %(title = "Hey";\n$("element_1").visualEffect("highlight");\n) +
%($("element_2").visualEffect("highlight");\nfooter = "Bye";)
assert_equal expected_body, @response.body
assert_equal ['$("element_1").visualEffect("highlight");', '$("element_2").visualEffect("highlight");'],
@store.read('views/test.host/functional_caching/formatted_fragment_cached')
end
end

View File

@@ -23,8 +23,6 @@ class CaptureController < ActionController::Base
def rescue_action(e) raise end
end
CaptureController.view_paths = [FIXTURE_LOAD_PATH]
class CaptureTest < Test::Unit::TestCase
def setup
@controller = CaptureController.new

View File

@@ -53,6 +53,15 @@ class BaseCgiTest < Test::Unit::TestCase
end
def default_test; end
private
def set_content_data(data)
@request.env['REQUEST_METHOD'] = 'POST'
@request.env['CONTENT_LENGTH'] = data.length
@request.env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded; charset=utf-8'
@request.env['RAW_POST_DATA'] = data
end
end
class CgiRequestTest < BaseCgiTest
@@ -155,10 +164,8 @@ end
class CgiRequestParamsParsingTest < BaseCgiTest
def test_doesnt_break_when_content_type_has_charset
data = 'flamenco=love'
@request.env['CONTENT_LENGTH'] = data.length
@request.env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded; charset=utf-8'
@request.env['RAW_POST_DATA'] = data
set_content_data 'flamenco=love'
assert_equal({"flamenco"=> "love"}, @request.request_parameters)
end
@@ -168,6 +175,41 @@ class CgiRequestParamsParsingTest < BaseCgiTest
end
end
class CgiRequestContentTypeTest < BaseCgiTest
def test_html_content_type_verification
@request.env['CONTENT_TYPE'] = Mime::HTML.to_s
assert @request.content_type.verify_request?
end
def test_xml_content_type_verification
@request.env['CONTENT_TYPE'] = Mime::XML.to_s
assert !@request.content_type.verify_request?
end
end
class CgiRequestMethodTest < BaseCgiTest
def test_get
assert_equal :get, @request.request_method
end
def test_post
@request.env['REQUEST_METHOD'] = 'POST'
assert_equal :post, @request.request_method
end
def test_put
set_content_data '_method=put'
assert_equal :put, @request.request_method
end
def test_delete
set_content_data '_method=delete'
assert_equal :delete, @request.request_method
end
end
class CgiRequestNeedsRewoundTest < BaseCgiTest
def test_body_should_be_rewound
data = 'foo'

View File

@@ -119,7 +119,7 @@ class ComponentsTest < Test::Unit::TestCase
def test_component_redirect_redirects
get :calling_redirected
assert_redirected_to :action => "being_called"
assert_redirected_to :controller=>"callee", :action => "being_called"
end
def test_component_multiple_redirect_redirects

View File

@@ -45,8 +45,6 @@ class ContentTypeController < ActionController::Base
def rescue_action(e) raise end
end
ContentTypeController.view_paths = [FIXTURE_LOAD_PATH]
class ContentTypeTest < Test::Unit::TestCase
def setup
@controller = ContentTypeController.new
@@ -114,6 +112,20 @@ class ContentTypeTest < Test::Unit::TestCase
assert_equal Mime::HTML, @response.content_type
assert_equal "utf-8", @response.charset
end
end
class AcceptBasedContentTypeTest < ActionController::TestCase
tests ContentTypeController
def setup
ActionController::Base.use_accept_header = true
end
def teardown
ActionController::Base.use_accept_header = false
end
def test_render_default_content_types_for_respond_to
@request.env["HTTP_ACCEPT"] = Mime::HTML.to_s

View File

@@ -13,8 +13,6 @@ class DeprecatedBaseMethodsTest < Test::Unit::TestCase
def rescue_action(e) raise e end
end
Target.view_paths = [FIXTURE_LOAD_PATH]
def setup
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new

View File

@@ -34,8 +34,8 @@ end
class MabView < ActionView::TemplateHandler
def initialize(view)
end
def render(template)
def render(template, local_assigns)
template.source
end
end
@@ -63,6 +63,7 @@ class LayoutAutoDiscoveryTest < Test::Unit::TestCase
end
def test_third_party_template_library_auto_discovers_layout
ThirdPartyTemplateLibraryController.view_paths.reload!
@controller = ThirdPartyTemplateLibraryController.new
get :hello
assert_equal 'layouts/third_party_template_library', @controller.active_layout

View File

@@ -162,10 +162,9 @@ class RespondToController < ActionController::Base
end
end
RespondToController.view_paths = [FIXTURE_LOAD_PATH]
class MimeControllerTest < Test::Unit::TestCase
def setup
ActionController::Base.use_accept_header = true
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
@@ -173,6 +172,10 @@ class MimeControllerTest < Test::Unit::TestCase
@request.host = "www.example.com"
end
def teardown
ActionController::Base.use_accept_header = false
end
def test_html
@request.env["HTTP_ACCEPT"] = "text/html"
get :js_or_html

View File

@@ -81,12 +81,12 @@ class NewRenderTestController < ActionController::Base
def render_file_not_using_full_path
@secret = 'in the sauce'
render :file => 'test/render_file_with_ivar', :use_full_path => true
render :file => 'test/render_file_with_ivar'
end
def render_file_not_using_full_path_with_dot_in_path
@secret = 'in the sauce'
render :file => 'test/dot.directory/render_file_with_ivar', :use_full_path => true
render :file => 'test/dot.directory/render_file_with_ivar'
end
def render_xml_hello
@@ -231,13 +231,13 @@ class NewRenderTestController < ActionController::Base
end
def render_to_string_with_exception
render_to_string :file => "exception that will not be caught - this will certainly not work", :use_full_path => true
render_to_string :file => "exception that will not be caught - this will certainly not work"
end
def render_to_string_with_caught_exception
@before = "i'm before the render"
begin
render_to_string :file => "exception that will be caught- hope my future instance vars still work!", :use_full_path => true
render_to_string :file => "exception that will be caught- hope my future instance vars still work!"
rescue
end
@after = "i'm after the render"
@@ -465,9 +465,6 @@ class NewRenderTestController < ActionController::Base
end
end
NewRenderTestController.view_paths = [FIXTURE_LOAD_PATH]
Fun::GamesController.view_paths = [FIXTURE_LOAD_PATH]
class NewRenderTest < Test::Unit::TestCase
def setup
@controller = NewRenderTestController.new
@@ -489,6 +486,11 @@ class NewRenderTest < Test::Unit::TestCase
assert_equal "<html>Hello world!</html>", @response.body
end
def test_renders_default_template_for_missing_action
get :'hyphen-ated'
assert_template 'test/hyphen-ated'
end
def test_do_with_render
get :render_hello_world
assert_template "test/hello_world"
@@ -605,8 +607,7 @@ EOS
end
def test_render_with_default_from_accept_header
@request.env["HTTP_ACCEPT"] = "text/javascript"
get :greeting
xhr :get, :greeting
assert_equal "$(\"body\").visualEffect(\"highlight\");", @response.body
end

View File

@@ -51,6 +51,15 @@ class BaseRackTest < Test::Unit::TestCase
end
def default_test; end
private
def set_content_data(data)
@request.env['REQUEST_METHOD'] = 'POST'
@request.env['CONTENT_LENGTH'] = data.length
@request.env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded; charset=utf-8'
@request.env['RAW_POST_DATA'] = data
end
end
class RackRequestTest < BaseRackTest
@@ -153,10 +162,8 @@ end
class RackRequestParamsParsingTest < BaseRackTest
def test_doesnt_break_when_content_type_has_charset
data = 'flamenco=love'
@request.env['CONTENT_LENGTH'] = data.length
@request.env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded; charset=utf-8'
@request.env['RAW_POST_DATA'] = data
set_content_data 'flamenco=love'
assert_equal({"flamenco"=> "love"}, @request.request_parameters)
end
@@ -166,6 +173,41 @@ class RackRequestParamsParsingTest < BaseRackTest
end
end
class RackRequestContentTypeTest < BaseRackTest
def test_html_content_type_verification
@request.env['CONTENT_TYPE'] = Mime::HTML.to_s
assert @request.content_type.verify_request?
end
def test_xml_content_type_verification
@request.env['CONTENT_TYPE'] = Mime::XML.to_s
assert !@request.content_type.verify_request?
end
end
class RackRequestMethodTest < BaseRackTest
def test_get
assert_equal :get, @request.request_method
end
def test_post
@request.env['REQUEST_METHOD'] = 'POST'
assert_equal :post, @request.request_method
end
def test_put
set_content_data '_method=put'
assert_equal :put, @request.request_method
end
def test_delete
set_content_data '_method=delete'
assert_equal :delete, @request.request_method
end
end
class RackRequestNeedsRewoundTest < BaseRackTest
def test_body_should_be_rewound
data = 'foo'
@@ -234,3 +276,34 @@ class RackResponseTest < BaseRackTest
assert_equal ["Hello, World!"], parts
end
end
class RackResponseHeadersTest < BaseRackTest
def setup
super
@response = ActionController::RackResponse.new(@request)
@output = StringIO.new('')
@response.headers['Status'] = 200
end
def test_content_type
[204, 304].each do |c|
@response.headers['Status'] = c
assert !response_headers.has_key?("Content-Type")
end
[200, 302, 404, 500].each do |c|
@response.headers['Status'] = c
assert response_headers.has_key?("Content-Type")
end
end
def test_status
assert !response_headers.has_key?('Status')
end
private
def response_headers
@response.out(@output)[1]
end
end

View File

@@ -168,21 +168,6 @@ class RedirectTest < Test::Unit::TestCase
assert_redirected_to :action => "other_host", :only_path => false, :host => 'other.test.host'
end
def test_redirect_error_with_pretty_diff
get :host_redirect
assert_response :redirect
begin
assert_redirected_to :action => "other_host", :only_path => true
rescue Test::Unit::AssertionFailedError => err
expected_msg, redirection_msg, diff_msg = err.message.scan(/<\{[^\}]+\}>/).collect { |s| s[2..-3] }
assert_match %r("only_path"=>false), redirection_msg
assert_match %r("host"=>"other.test.host"), redirection_msg
assert_match %r("action"=>"other_host"), redirection_msg
assert_match %r("only_path"=>false), diff_msg
assert_match %r("host"=>"other.test.host"), diff_msg
end
end
def test_module_redirect
get :module_redirect
assert_response :redirect
@@ -235,9 +220,16 @@ class RedirectTest < Test::Unit::TestCase
get :redirect_to_existing_record
assert_equal "http://test.host/workshops/5", redirect_to_url
assert_redirected_to Workshop.new(5, false)
get :redirect_to_new_record
assert_equal "http://test.host/workshops", redirect_to_url
assert_redirected_to Workshop.new(5, true)
end
def test_redirect_with_partial_params
get :module_redirect
assert_redirected_to :action => 'hello_world'
end
def test_redirect_to_nil
@@ -283,7 +275,7 @@ module ModuleTest
def test_module_redirect_using_options
get :module_redirect
assert_response :redirect
assert_redirected_to :controller => 'redirect', :action => "hello_world"
assert_redirected_to :controller => '/redirect', :action => "hello_world"
end
end
end

View File

@@ -101,12 +101,7 @@ class TestController < ActionController::Base
end
def render_line_offset
begin
render :inline => '<% raise %>', :locals => {:foo => 'bar'}
rescue => exc
end
line = exc.backtrace.first
render :text => line
render :inline => '<% raise %>', :locals => {:foo => 'bar'}
end
def heading
@@ -217,9 +212,6 @@ class TestController < ActionController::Base
end
end
TestController.view_paths = [FIXTURE_LOAD_PATH]
Fun::GamesController.view_paths = [FIXTURE_LOAD_PATH]
class RenderTest < Test::Unit::TestCase
def setup
@request = ActionController::TestRequest.new
@@ -241,10 +233,15 @@ class RenderTest < Test::Unit::TestCase
end
def test_line_offset
get :render_line_offset
line = @response.body
assert(line =~ %r{:(\d+):})
assert_equal "1", $1
begin
get :render_line_offset
flunk "the action should have raised an exception"
rescue RuntimeError => exc
line = exc.backtrace.first
assert(line =~ %r{:(\d+):})
assert_equal "1", $1,
"The line offset is wrong, perhaps the wrong exception has been raised, exception was: #{exc.inspect}"
end
end
def test_render_with_forward_slash

View File

@@ -386,7 +386,7 @@ class RequestTest < Test::Unit::TestCase
def test_nil_format
@request.instance_eval { @parameters = { :format => nil } }
@request.env["HTTP_ACCEPT"] = "text/javascript"
@request.env["HTTP_X_REQUESTED_WITH"] = "XMLHttpRequest"
assert_equal Mime::JS, @request.format
end

View File

@@ -62,6 +62,11 @@ class RescueController < ActionController::Base
render :text => exception.message
end
# This is a Dispatcher exception and should be in ApplicationController.
rescue_from ActionController::RoutingError do
render :text => 'no way'
end
def raises
render :text => 'already rendered'
raise "don't panic!"
@@ -378,6 +383,10 @@ class RescueTest < Test::Unit::TestCase
assert_equal "RescueController::ResourceUnavailableToRescueAsString", @response.body
end
def test_rescue_dispatcher_exceptions
RescueController.process_with_exception(@request, @response, ActionController::RoutingError.new("Route not found"))
assert_equal "no way", @response.body
end
protected
def with_all_requests_local(local = true)

View File

@@ -28,18 +28,16 @@ module Backoffice
end
class ResourcesTest < Test::Unit::TestCase
# The assertions in these tests are incompatible with the hash method
# optimisation. This could indicate user level problems
def setup
ActionController::Base.optimise_named_routes = false
end
def tear_down
def teardown
ActionController::Base.optimise_named_routes = true
end
def test_should_arrange_actions
resource = ActionController::Resources::Resource.new(:messages,
:collection => { :rss => :get, :reorder => :post, :csv => :post },
@@ -159,14 +157,14 @@ class ResourcesTest < Test::Unit::TestCase
def test_with_collection_actions_and_name_prefix
actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete }
with_restful_routing :messages, :path_prefix => '/threads/:thread_id', :name_prefix => "thread_", :collection => actions do
assert_restful_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do |options|
actions.each do |action, method|
assert_recognizes(options.merge(:action => action), :path => "/threads/1/messages/#{action}", :method => method)
end
end
assert_restful_named_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do |options|
actions.keys.each do |action|
assert_named_route "/threads/1/messages/#{action}", "#{action}_thread_messages_path", :action => action
@@ -177,14 +175,14 @@ class ResourcesTest < Test::Unit::TestCase
def test_with_collection_action_and_name_prefix_and_formatted
actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete }
with_restful_routing :messages, :path_prefix => '/threads/:thread_id', :name_prefix => "thread_", :collection => actions do
assert_restful_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do |options|
actions.each do |action, method|
assert_recognizes(options.merge(:action => action, :format => 'xml'), :path => "/threads/1/messages/#{action}.xml", :method => method)
end
end
assert_restful_named_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do |options|
actions.keys.each do |action|
assert_named_route "/threads/1/messages/#{action}.xml", "formatted_#{action}_thread_messages_path", :action => action, :format => 'xml'
@@ -279,7 +277,7 @@ class ResourcesTest < Test::Unit::TestCase
end
end
end
def test_with_new_action_with_name_prefix
with_restful_routing :messages, :new => { :preview => :post }, :path_prefix => '/threads/:thread_id', :name_prefix => 'thread_' do
preview_options = {:action => 'preview', :thread_id => '1'}
@@ -293,7 +291,7 @@ class ResourcesTest < Test::Unit::TestCase
end
end
end
def test_with_formatted_new_action_with_name_prefix
with_restful_routing :messages, :new => { :preview => :post }, :path_prefix => '/threads/:thread_id', :name_prefix => 'thread_' do
preview_options = {:action => 'preview', :thread_id => '1', :format => 'xml'}
@@ -307,7 +305,7 @@ class ResourcesTest < Test::Unit::TestCase
end
end
end
def test_override_new_method
with_restful_routing :messages do
assert_restful_routes_for :messages do |options|
@@ -524,9 +522,9 @@ class ResourcesTest < Test::Unit::TestCase
map.resources :messages, :collection => {:search => :get}, :new => {:preview => :any}, :name_prefix => 'thread_', :path_prefix => '/threads/:thread_id'
map.resource :account, :member => {:login => :get}, :new => {:preview => :any}, :name_prefix => 'admin_', :path_prefix => '/admin'
end
action_separator = ActionController::Base.resource_action_separator
assert_simply_restful_for :messages, :name_prefix => 'thread_', :path_prefix => 'threads/1/', :options => { :thread_id => '1' }
assert_named_route "/threads/1/messages#{action_separator}search", "search_thread_messages_path", {}
assert_named_route "/threads/1/messages/new", "new_thread_message_path", {}
@@ -623,7 +621,7 @@ class ResourcesTest < Test::Unit::TestCase
assert_simply_restful_for :products, :controller => "backoffice/products"
end
end
def test_nested_resources_using_namespace
with_routing do |set|
set.draw do |map|
@@ -795,7 +793,7 @@ class ResourcesTest < Test::Unit::TestCase
yield options[:options] if block_given?
end
def assert_singleton_routes_for(singleton_name, options = {})
options[:options] ||= {}
options[:options][:controller] = options[:controller] || singleton_name.to_s.pluralize
@@ -855,7 +853,7 @@ class ResourcesTest < Test::Unit::TestCase
actual = @controller.send(route, options) rescue $!.class.name
assert_equal expected, actual, "Error on route: #{route}(#{options.inspect})"
end
def assert_resource_methods(expected, resource, action_method, method)
assert_equal expected.length, resource.send("#{action_method}_methods")[method].size, "#{resource.send("#{action_method}_methods")[method].inspect}"
expected.each do |action|

View File

@@ -2039,6 +2039,26 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
Object.send(:remove_const, :Api)
end
def test_namespace_with_path_prefix
Object.const_set(:Api, Module.new { |m| m.const_set(:ProductsController, Class.new) })
set.draw do |map|
map.namespace 'api', :path_prefix => 'prefix' do |api|
api.route 'inventory', :controller => "products", :action => 'inventory'
end
end
request.path = "/prefix/inventory"
request.method = :get
assert_nothing_raised { set.recognize(request) }
assert_equal("api/products", request.path_parameters[:controller])
assert_equal("inventory", request.path_parameters[:action])
ensure
Object.send(:remove_const, :Api)
end
def test_generate_finds_best_fit
set.draw do |map|
map.connect "/people", :controller => "people", :action => "index"

View File

@@ -19,8 +19,6 @@ class SendFileController < ActionController::Base
def rescue_action(e) raise end
end
SendFileController.view_paths = [FIXTURE_LOAD_PATH]
class SendFileTest < Test::Unit::TestCase
include TestFileUtils

View File

@@ -566,24 +566,6 @@ XML
assert_raise(RuntimeError) { ActionController::TestUploadedFile.new('non_existent_file') }
end
def test_assert_follow_redirect_to_same_controller
with_foo_routing do |set|
get :redirect_to_same_controller
assert_response :redirect
assert_redirected_to :controller => 'test_test/test', :action => 'test_uri', :id => 5
assert_nothing_raised { follow_redirect }
end
end
def test_assert_follow_redirect_to_different_controller
with_foo_routing do |set|
get :redirect_to_different_controller
assert_response :redirect
assert_redirected_to :controller => 'fail', :id => 5
assert_raise(RuntimeError) { follow_redirect }
end
end
def test_redirect_url_only_cares_about_location_header
get :create
assert_response :created

View File

@@ -1,8 +1,6 @@
require 'abstract_unit'
class ViewLoadPathsTest < Test::Unit::TestCase
ActionController::Base.view_paths = [FIXTURE_LOAD_PATH]
class TestController < ActionController::Base
def self.controller_path() "test" end
def rescue_action(e) raise end
@@ -146,18 +144,4 @@ class ViewLoadPathsTest < Test::Unit::TestCase
assert_nothing_raised { C.view_paths << 'c/path' }
assert_equal ['c/path'], C.view_paths
end
def test_find_template_file_for_path
assert_equal "test/hello_world.erb", @controller.view_paths.find_template_file_for_path("test/hello_world.erb").to_s
assert_equal "test/hello.builder", @controller.view_paths.find_template_file_for_path("test/hello.builder").to_s
assert_equal nil, @controller.view_paths.find_template_file_for_path("test/missing.erb")
end
def test_view_paths_find_template_file_for_path
assert_equal "test/formatted_html_erb.html.erb", @controller.view_paths.find_template_file_for_path("test/formatted_html_erb.html").to_s
assert_equal "test/formatted_xml_erb.xml.erb", @controller.view_paths.find_template_file_for_path("test/formatted_xml_erb.xml").to_s
assert_equal "test/hello_world.erb", @controller.view_paths.find_template_file_for_path("test/hello_world.html").to_s
assert_equal "test/hello_world.erb", @controller.view_paths.find_template_file_for_path("test/hello_world.xml").to_s
assert_equal nil, @controller.view_paths.find_template_file_for_path("test/missing.html")
end
end

View File

@@ -0,0 +1 @@
<%= developer.name %>

View File

@@ -0,0 +1 @@
<%= game.name %>

View File

@@ -0,0 +1 @@
<%= game.name %>

View File

@@ -0,0 +1,3 @@
<body>
<% cache do %><p>ERB</p><% end %>
</body>

View File

@@ -0,0 +1,6 @@
page.assign 'title', 'Hey'
cache do
page['element_1'].visual_effect :highlight
page['element_2'].visual_effect :highlight
end
page.assign 'footer', 'Bye'

View File

@@ -0,0 +1,5 @@
xml.body do
cache do
xml.p "Builder"
end
end

View File

@@ -0,0 +1,2 @@
<%= render :inline => 'Some inline content' %>
<% cache do %>Some cached content<% end %>

View File

@@ -0,0 +1 @@
<%= project.name %>

View File

@@ -0,0 +1 @@
// subdir js

View File

@@ -0,0 +1 @@
/* subdir.css */

View File

@@ -0,0 +1 @@
<%= reply.content %>

View File

@@ -0,0 +1 @@
Hello world!

View File

@@ -83,6 +83,7 @@ class AssetTagHelperTest < ActionView::TestCase
%(javascript_include_tag("common.javascript", "/elsewhere/cools")) => %(<script src="/javascripts/common.javascript" type="text/javascript"></script>\n<script src="/elsewhere/cools.js" type="text/javascript"></script>),
%(javascript_include_tag(:defaults)) => %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>),
%(javascript_include_tag(:all)) => %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>\n<script src="/javascripts/bank.js" type="text/javascript"></script>\n<script src="/javascripts/robber.js" type="text/javascript"></script>\n<script src="/javascripts/version.1.0.js" type="text/javascript"></script>),
%(javascript_include_tag(:all, :recursive => true)) => %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>\n<script src="/javascripts/bank.js" type="text/javascript"></script>\n<script src="/javascripts/robber.js" type="text/javascript"></script>\n<script src="/javascripts/subdir/subdir.js" type="text/javascript"></script>\n<script src="/javascripts/version.1.0.js" type="text/javascript"></script>),
%(javascript_include_tag(:defaults, "test")) => %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/test.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>),
%(javascript_include_tag("test", :defaults)) => %(<script src="/javascripts/test.js" type="text/javascript"></script>\n<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>)
}
@@ -108,6 +109,7 @@ class AssetTagHelperTest < ActionView::TestCase
%(stylesheet_link_tag("dir/file")) => %(<link href="/stylesheets/dir/file.css" media="screen" rel="stylesheet" type="text/css" />),
%(stylesheet_link_tag("style", :media => "all")) => %(<link href="/stylesheets/style.css" media="all" rel="stylesheet" type="text/css" />),
%(stylesheet_link_tag(:all)) => %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/version.1.0.css" media="screen" rel="stylesheet" type="text/css" />),
%(stylesheet_link_tag(:all, :recursive => true)) => %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/subdir/subdir.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/version.1.0.css" media="screen" rel="stylesheet" type="text/css" />),
%(stylesheet_link_tag(:all, :media => "all")) => %(<link href="/stylesheets/bank.css" media="all" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/robber.css" media="all" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/version.1.0.css" media="all" rel="stylesheet" type="text/css" />),
%(stylesheet_link_tag("random.styles", "/css/stylish")) => %(<link href="/stylesheets/random.styles" media="screen" rel="stylesheet" type="text/css" />\n<link href="/css/stylish.css" media="screen" rel="stylesheet" type="text/css" />),
%(stylesheet_link_tag("http://www.example.com/styles/style")) => %(<link href="http://www.example.com/styles/style.css" media="screen" rel="stylesheet" type="text/css" />)
@@ -333,8 +335,9 @@ class AssetTagHelperTest < ActionView::TestCase
ActionController::Base.asset_host = 'http://a%d.example.com'
ActionController::Base.perform_caching = true
hash = '/javascripts/cache/money.js'.hash % 4
assert_dom_equal(
%(<script src="http://a3.example.com/javascripts/cache/money.js" type="text/javascript"></script>),
%(<script src="http://a#{hash}.example.com/javascripts/cache/money.js" type="text/javascript"></script>),
javascript_include_tag(:all, :cache => "cache/money")
)
@@ -343,6 +346,27 @@ class AssetTagHelperTest < ActionView::TestCase
FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'cache', 'money.js'))
end
def test_caching_javascript_include_tag_with_all_and_recursive_puts_defaults_at_the_start_of_the_file
ENV["RAILS_ASSET_ID"] = ""
ActionController::Base.asset_host = 'http://a0.example.com'
ActionController::Base.perform_caching = true
assert_dom_equal(
%(<script src="http://a0.example.com/javascripts/combined.js" type="text/javascript"></script>),
javascript_include_tag(:all, :cache => "combined", :recursive => true)
)
assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'combined.js'))
assert_equal(
%(// prototype js\n\n// effects js\n\n// dragdrop js\n\n// controls js\n\n// application js\n\n// bank js\n\n// robber js\n\n// subdir js\n\n\n// version.1.0 js),
IO.read(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'combined.js'))
)
ensure
FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'combined.js'))
end
def test_caching_javascript_include_tag_with_all_puts_defaults_at_the_start_of_the_file
ENV["RAILS_ASSET_ID"] = ""
ActionController::Base.asset_host = 'http://a0.example.com'
@@ -373,6 +397,11 @@ class AssetTagHelperTest < ActionView::TestCase
javascript_include_tag(:all, :cache => true)
)
assert_dom_equal(
%(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>\n<script src="/javascripts/bank.js" type="text/javascript"></script>\n<script src="/javascripts/robber.js" type="text/javascript"></script>\n<script src="/javascripts/subdir/subdir.js" type="text/javascript"></script>\n<script src="/javascripts/version.1.0.js" type="text/javascript"></script>),
javascript_include_tag(:all, :cache => true, :recursive => true)
)
assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'all.js'))
assert_dom_equal(
@@ -380,6 +409,11 @@ class AssetTagHelperTest < ActionView::TestCase
javascript_include_tag(:all, :cache => "money")
)
assert_dom_equal(
%(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>\n<script src="/javascripts/bank.js" type="text/javascript"></script>\n<script src="/javascripts/robber.js" type="text/javascript"></script>\n<script src="/javascripts/subdir/subdir.js" type="text/javascript"></script>\n<script src="/javascripts/version.1.0.js" type="text/javascript"></script>),
javascript_include_tag(:all, :cache => "money", :recursive => true)
)
assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'money.js'))
end
@@ -432,6 +466,11 @@ class AssetTagHelperTest < ActionView::TestCase
stylesheet_link_tag(:all, :cache => true)
)
assert_dom_equal(
%(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/subdir/subdir.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/version.1.0.css" media="screen" rel="stylesheet" type="text/css" />),
stylesheet_link_tag(:all, :cache => true, :recursive => true)
)
assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css'))
assert_dom_equal(
@@ -439,6 +478,11 @@ class AssetTagHelperTest < ActionView::TestCase
stylesheet_link_tag(:all, :cache => "money")
)
assert_dom_equal(
%(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/subdir/subdir.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/version.1.0.css" media="screen" rel="stylesheet" type="text/css" />),
stylesheet_link_tag(:all, :cache => "money", :recursive => true)
)
assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css'))
end
end

View File

@@ -0,0 +1,41 @@
require 'abstract_unit'
require 'controller/fake_models'
uses_mocha 'TestTemplateRecompilation' do
class CompiledTemplatesTest < Test::Unit::TestCase
def setup
@view = ActionView::Base.new(ActionController::Base.view_paths, {})
@compiled_templates = ActionView::Base::CompiledTemplates
@compiled_templates.instance_methods.each do |m|
@compiled_templates.send(:remove_method, m) if m =~ /^_run_/
end
end
def test_template_gets_compiled
assert_equal 0, @compiled_templates.instance_methods.size
assert_equal "Hello world!", @view.render("test/hello_world.erb")
assert_equal 1, @compiled_templates.instance_methods.size
end
def test_template_gets_recompiled_when_using_different_keys_in_local_assigns
assert_equal 0, @compiled_templates.instance_methods.size
assert_equal "Hello world!", @view.render("test/hello_world.erb")
assert_equal "Hello world!", @view.render("test/hello_world.erb", {:foo => "bar"})
assert_equal 2, @compiled_templates.instance_methods.size
end
def test_compiled_template_will_not_be_recompiled_when_rendered_with_identical_local_assigns
assert_equal 0, @compiled_templates.instance_methods.size
assert_equal "Hello world!", @view.render("test/hello_world.erb")
ActionView::Template.any_instance.expects(:compile!).never
assert_equal "Hello world!", @view.render("test/hello_world.erb")
end
def test_compiled_template_will_always_be_recompiled_when_rendered_if_template_is_outside_cache
assert_equal 0, @compiled_templates.instance_methods.size
assert_equal "Hello world!", @view.render("#{FIXTURE_LOAD_PATH}/test/hello_world.erb")
ActionView::Template.any_instance.expects(:compile!).times(3)
3.times { assert_equal "Hello world!", @view.render("#{FIXTURE_LOAD_PATH}/test/hello_world.erb") }
end
end
end

View File

@@ -1198,6 +1198,21 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, time_select("post", "written_on")
end
def test_time_select_without_date_hidden_fields
@post = Post.new
@post.written_on = Time.local(2004, 6, 15, 15, 16, 35)
expected = %(<select id="post_written_on_4i" name="post[written_on(4i)]">\n)
0.upto(23) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 15}>#{leading_zero_on_single_digits(i)}</option>\n) }
expected << "</select>\n"
expected << " : "
expected << %(<select id="post_written_on_5i" name="post[written_on(5i)]">\n)
0.upto(59) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 16}>#{leading_zero_on_single_digits(i)}</option>\n) }
expected << "</select>\n"
assert_dom_equal expected, time_select("post", "written_on", :ignore_date => true)
end
def test_time_select_with_seconds
@post = Post.new
@post.written_on = Time.local(2004, 6, 15, 15, 16, 35)

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