Merge remote branch 'rails/master' into identity_map

Conflicts:
	activerecord/examples/performance.rb
	activerecord/lib/active_record/association_preload.rb
	activerecord/lib/active_record/associations.rb
	activerecord/lib/active_record/associations/association_proxy.rb
	activerecord/lib/active_record/autosave_association.rb
	activerecord/lib/active_record/base.rb
	activerecord/lib/active_record/nested_attributes.rb
	activerecord/test/cases/relations_test.rb
This commit is contained in:
Emilio Tagua
2011-02-15 12:01:04 -03:00
430 changed files with 12349 additions and 9518 deletions

21
Gemfile
View File

@@ -9,23 +9,20 @@ else
end
gem "rack", :git => "git://github.com/rack/rack.git"
gem "rack-test", :git => "git://github.com/brynary/rack-test.git"
gem "rake", ">= 0.8.7"
gem "mocha", ">= 0.9.8"
gem "rdoc", ">= 2.5.10"
gem "horo", ">= 1.0.2"
# for perf tests
gem "faker"
gem "rbench"
gem "addressable"
group :doc do
gem "rdoc", "~> 3.4"
gem "horo", "= 1.0.3"
gem "RedCloth", "~> 4.2" if RUBY_VERSION < "1.9.3"
end
# AS
gem "memcache-client", ">= 1.8.5"
# AM
gem "text-format", "~> 1.0.0"
platforms :mri_18 do
gem "system_timer"
gem "ruby-debug", ">= 0.10.3"
@@ -34,7 +31,7 @@ end
platforms :mri_19 do
# TODO: Remove the conditional when ruby-debug19 supports Ruby >= 1.9.3
gem "ruby-debug19" if RUBY_VERSION < "1.9.3"
gem "ruby-debug19", :require => 'ruby-debug' if RUBY_VERSION < "1.9.3"
end
platforms :ruby do
@@ -43,12 +40,12 @@ platforms :ruby do
gem "nokogiri", ">= 1.4.4"
# AR
gem "sqlite3-ruby", "~> 1.3.1", :require => 'sqlite3'
gem "sqlite3", "~> 1.3.3"
group :db do
gem "pg", ">= 0.9.0"
gem "mysql", ">= 2.8.1"
gem "mysql2", ">= 0.2.6"
gem "mysql2", :git => "git://github.com/brianmario/mysql2.git"
end
end

View File

@@ -53,8 +53,8 @@ more separate. Each of these packages can be used independently outside of
* The README file created within your application.
* The {Getting Started with Rails}[http://guides.rubyonrails.org/getting_started.html].
* The {Ruby on Rails Tutorial}[http://railstutorial.org/book].
* The {Ruby on Rails guides}[http://guides.rubyonrails.org/getting_started.html].
* The {API documentation}[http://api.rubyonrails.org].
* The {Ruby on Rails Guides}[http://guides.rubyonrails.org].
* The {API Documentation}[http://api.rubyonrails.org].
== Contributing

View File

@@ -1,6 +1,4 @@
#!/usr/bin/env rake
gem 'rdoc', '>= 2.5.10'
require 'rdoc'
require 'rdoc/task'
require 'net/http'
@@ -14,31 +12,6 @@ task :build => "all:build"
desc "Release all gems to gemcutter and create a tag"
task :release => "all:release"
# RDoc skips some files in the Rails tree due to its binary? predicate. This is a quick
# hack for edge docs, until we decide which is the correct way to address this issue.
# If not fixed in RDoc itself, via an option or something, we should probably move this
# to railties and use it also in doc:rails.
def hijack_rdoc!
require "rdoc/parser"
class << RDoc::Parser
def binary?(file)
s = File.read(file, 1024) or return false
if s[0, 2] == Marshal.dump('')[0, 2] then
true
elsif file =~ /erb\.rb$/ then
false
elsif s.index("\x00") then # ORIGINAL is s.scan(/<%|%>/).length >= 4 || s.index("\x00")
true
elsif 0.respond_to? :fdiv then
s.count("^ -~\t\r\n").fdiv(s.size) > 0.3
else # HACK 1.8.6
(s.count("^ -~\t\r\n").to_f / s.size) > 0.3
end
end
end
end
PROJECTS = %w(activesupport activemodel actionpack actionmailer activeresource activerecord railties)
desc 'Run all tests by default'
@@ -76,8 +49,6 @@ end
desc "Generate documentation for the Rails framework"
RDoc::Task.new do |rdoc|
hijack_rdoc!
rdoc.rdoc_dir = 'doc/rdoc'
rdoc.title = "Ruby on Rails Documentation"

View File

@@ -59,7 +59,7 @@ generated would look like this:
Mr. david@loudthinking.com
In previous version of rails you would call <tt>create_method_name</tt> and
In previous version of Rails you would call <tt>create_method_name</tt> and
<tt>deliver_method_name</tt>. Rails 3.0 has a much simpler interface, you
simply call the method and optionally call +deliver+ on the return value.

View File

@@ -17,7 +17,7 @@ namespace :test do
task :isolated do
ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME'))
Dir.glob("test/**/*_test.rb").all? do |file|
system(ruby, '-Ilib:test', file)
sh(ruby, '-Ilib:test', file)
end or raise "Failures"
end
end

View File

@@ -20,5 +20,5 @@ Gem::Specification.new do |s|
s.has_rdoc = true
s.add_dependency('actionpack', version)
s.add_dependency('mail', '~> 2.2.9')
s.add_dependency('mail', '~> 2.2.15')
end

View File

@@ -246,7 +246,7 @@ module ActionMailer #:nodoc:
# but Action Mailer translates them appropriately and sets the correct values.
#
# As you can pass in any header, you need to either quote the header as a string, or pass it in as
# an underscorised symbol, so the following will work:
# an underscored symbol, so the following will work:
#
# class Notifier < ActionMailer::Base
# default 'Content-Transfer-Encoding' => '7bit',
@@ -298,7 +298,7 @@ module ActionMailer #:nodoc:
#
# * <tt>sendmail_settings</tt> - Allows you to override options for the <tt>:sendmail</tt> delivery method.
# * <tt>:location</tt> - The location of the sendmail executable. Defaults to <tt>/usr/sbin/sendmail</tt>.
# * <tt>:arguments</tt> - The command line arguments. Defaults to <tt>-i -t</tt> with <tt>-f sender@addres</tt>
# * <tt>:arguments</tt> - The command line arguments. Defaults to <tt>-i -t</tt> with <tt>-f sender@address</tt>
# added automatically before the message is sent.
#
# * <tt>file_settings</tt> - Allows you to override options for the <tt>:file</tt> delivery method.
@@ -404,7 +404,7 @@ module ActionMailer #:nodoc:
end
end
def respond_to?(method, *args) #:nodoc:
def respond_to?(method, include_private = false) #:nodoc:
super || action_methods.include?(method.to_s)
end
@@ -693,15 +693,8 @@ module ActionMailer #:nodoc:
end
def each_template(paths, name, &block) #:nodoc:
Array.wrap(paths).each do |path|
templates = lookup_context.find_all(name, path)
templates = templates.uniq_by { |t| t.formats }
unless templates.empty?
templates.each(&block)
return
end
end
templates = lookup_context.find_all(name, Array.wrap(paths))
templates.uniq_by { |t| t.formats }.each(&block)
end
def create_parts_from_responses(m, responses) #:nodoc:

View File

@@ -3,17 +3,8 @@ module ActionMailer
# Uses Text::Format to take the text and format it, indented two spaces for
# each line, and wrapped at 72 columns.
def block_format(text)
begin
require 'text/format'
rescue LoadError => e
$stderr.puts "You don't have text-format installed in your application. Please add it to your Gemfile and run bundle install"
raise e
end unless defined?(Text::Format)
formatted = text.split(/\n\r\n/).collect { |paragraph|
Text::Format.new(
:columns => 72, :first_indent => 2, :body_indent => 2, :text => paragraph
).format
simple_format(paragraph)
}.join("\n")
# Make list points stand on their own line
@@ -37,5 +28,22 @@ module ActionMailer
def attachments
@_message.attachments
end
private
def simple_format(text, len = 72, indent = 2)
sentences = [[]]
text.split.each do |word|
if (sentences.last + [word]).join(' ').length > len
sentences << [word]
else
sentences.last << word
end
end
sentences.map { |sentence|
"#{" " * indent}#{sentence.join(' ')}"
}.join "\n"
end
end
end

View File

@@ -201,7 +201,7 @@ module ActionMailer
if String === @body
@parts.unshift create_inline_part(@body)
elsif @parts.empty? || @parts.all? { |p| p.content_disposition =~ /^attachment/ }
lookup_context.find_all(@template, @mailer_name).each do |template|
lookup_context.find_all(@template, [@mailer_name]).each do |template|
self.formats = template.formats
@parts << create_inline_part(render(:template => template), template.mime_type)
end
@@ -242,12 +242,12 @@ module ActionMailer
ct.to_s.split("/")
end
def parse_content_type(defaults=nil)
def parse_content_type
if @content_type.blank?
[ nil, {} ]
else
ctype, *attrs = @content_type.split(/;\s*/)
attrs = Hash[attrs.map { |attr| attr.split(/\=/, 2) }]
attrs = Hash[attrs.map { |attr| attr.split(/=/, 2) }]
[ctype, {"charset" => @charset}.merge!(attrs)]
end
end

View File

@@ -2,16 +2,18 @@ module Mail
class Message
def set_content_type(*args)
ActiveSupport::Deprecation.warn('Message#set_content_type is deprecated, please just call ' <<
'Message#content_type with the same arguments', caller[0,2])
message = 'Message#set_content_type is deprecated, please just call ' <<
'Message#content_type with the same arguments'
ActiveSupport::Deprecation.warn(message, caller[0,2])
content_type(*args)
end
alias :old_transfer_encoding :transfer_encoding
def transfer_encoding(value = nil)
if value
ActiveSupport::Deprecation.warn('Message#transfer_encoding is deprecated, please call ' <<
'Message#content_transfer_encoding with the same arguments', caller[0,2])
message = 'Message#transfer_encoding is deprecated, ' <<
'please call Message#content_transfer_encoding with the same arguments'
ActiveSupport::Deprecation.warn(message, caller[0,2])
content_transfer_encoding(value)
else
old_transfer_encoding
@@ -19,16 +21,17 @@ module Mail
end
def transfer_encoding=(value)
ActiveSupport::Deprecation.warn('Message#transfer_encoding= is deprecated, please call ' <<
'Message#content_transfer_encoding= with the same arguments', caller[0,2])
message = 'Message#transfer_encoding= is deprecated, ' <<
'please call Message#content_transfer_encoding= with the same arguments'
ActiveSupport::Deprecation.warn(message, caller[0,2])
self.content_transfer_encoding = value
end
def original_filename
ActiveSupport::Deprecation.warn('Message#original_filename is deprecated, ' <<
'please call Message#filename', caller[0,2])
message = 'Message#original_filename is deprecated, please call Message#filename'
ActiveSupport::Deprecation.warn(message, caller[0,2])
filename
end
end
end
end

View File

@@ -25,7 +25,6 @@ end
silence_warnings do
# These external dependencies have warnings :/
require 'text/format'
require 'mail'
end
@@ -79,4 +78,4 @@ def restore_delivery_method
ActionMailer::Base.delivery_method = @old_delivery_method
end
ActiveSupport::Deprecation.silenced = true
ActiveSupport::Deprecation.silenced = true

View File

@@ -0,0 +1,4 @@
Hello there,
Mr. <%= @recipient %>. Be greeted, new member!

View File

@@ -0,0 +1,46 @@
require 'abstract_unit'
require 'action_controller'
class I18nTestMailer < ActionMailer::Base
configure do |c|
c.assets_dir = ''
end
def mail_with_i18n_subject(recipient)
@recipient = recipient
I18n.locale = :de
mail(:to => recipient, :subject => "#{I18n.t :email_subject} #{recipient}",
:from => "system@loudthinking.com", :date => Time.local(2004, 12, 12))
end
end
class TestController < ActionController::Base
def send_mail
I18nTestMailer.mail_with_i18n_subject("test@localhost").deliver
render :text => 'Mail sent'
end
end
class ActionMailerI18nWithControllerTest < ActionDispatch::IntegrationTest
Routes = ActionDispatch::Routing::RouteSet.new
Routes.draw do
match ':controller(/:action(/:id))'
end
def app
Routes
end
def setup
I18n.backend.store_translations('de', :email_subject => '[Signed up] Welcome')
end
def teardown
I18n.locale = :en
end
def test_send_mail
get '/test/send_mail'
assert_equal "Mail sent", @response.body
end
end

View File

@@ -1,6 +1,14 @@
require 'abstract_unit'
class TmailCompatTest < ActiveSupport::TestCase
def setup
@silence = ActiveSupport::Deprecation.silenced
ActiveSupport::Deprecation.silenced = false
end
def teardown
ActiveSupport::Deprecation.silenced = @silence
end
def test_set_content_type_raises_deprecation_warning
mail = Mail.new
@@ -31,5 +39,4 @@ class TmailCompatTest < ActiveSupport::TestCase
end
assert_equal mail.content_transfer_encoding, "base64"
end
end

View File

@@ -1,8 +1,14 @@
*Rails 3.1.0 (unreleased)*
* brought back config.action_view.cache_template_loading, which allows to decide whether templates should be cached or not [Piotr Sarnacki]
* Add an :authenticity_token option to form_tag for custom handling or to omit the token (pass :authenticity_token => false). [Jakub Kuźma, Igor Wiedler]
* url_for and named url helpers now accept :subdomain and :domain as options [Josh Kalderimis]
* HTML5 button_tag helper. [Rizwan Reza]
* Template lookup now searches further up in the inheritance chain. [Artemave]
* Brought back config.action_view.cache_template_loading, which allows to decide whether templates should be cached or not. [Piotr Sarnacki]
* url_for and named url helpers now accept :subdomain and :domain as options, [Josh Kalderimis]
* The redirect route method now also accepts a hash of options which will only change the parts of the url in question, or an object which responds to call, allowing for redirects to be reused (check the documentation for examples). [Josh Kalderimis]
@@ -31,23 +37,24 @@
* Add Rack::Cache to the default stack. Create a Rails store that delegates to the Rails cache, so by default, whatever caching layer you are using will be used for HTTP caching. Note that Rack::Cache will be used if you use #expires_in, #fresh_when or #stale with :public => true. Otherwise, the caching rules will apply to the browser only. [Yehuda Katz, Carl Lerche]
*Rails 3.0.2 (unreleased)*
* The helper number_to_currency accepts a new :negative_format option to be able to configure how to render negative amounts. [Don Wilson]
*Rails 3.0.1 (October 15, 2010)*
* No Changes, just a version bump.
*Rails 3.0.0 (August 29, 2010)*
* password_field renders with nil value by default making the use of passwords secure by default, if you want to render you should do for instance f.password_field(:password, :value => @user.password) [Santiago Pastorino]
* Symbols and strings in routes should yield the same behavior. Note this may break existing apps that were using symbols with the new routes API. [José Valim]
* Add clear_helpers as a way to clean up all helpers added to this controller, maintaing just the helper with the same name as the controller. [José Valim]
* See http://github.com/rails/rails/compare/v3.0.0_RC...v3.0.0_RC2 for gory details
* Add clear_helpers as a way to clean up all helpers added to this controller, maintaining just the helper with the same name as the controller. [José Valim]
* Support routing constraints in functional tests. [Andrew White]

View File

@@ -262,7 +262,7 @@ methods:
layout "weblog/layout"
def index
@posts = Post.find(:all)
@posts = Post.all
end
def show

View File

@@ -21,11 +21,11 @@ Gem::Specification.new do |s|
s.add_dependency('activesupport', version)
s.add_dependency('activemodel', version)
s.add_dependency('rack-cache', '~> 0.5.3')
s.add_dependency('rack-cache', '~> 1.0.0')
s.add_dependency('builder', '~> 3.0.0')
s.add_dependency('i18n', '~> 0.5.0')
s.add_dependency('rack', '~> 1.2.1')
s.add_dependency('rack-test', '~> 0.5.6')
s.add_dependency('rack-test', '~> 0.5.7')
s.add_dependency('rack-mount', '~> 0.6.13')
s.add_dependency('tzinfo', '~> 0.3.23')
s.add_dependency('erubis', '~> 2.6.6')

View File

@@ -13,7 +13,7 @@ module AbstractController
# Override AbstractController::Base's process_action to run the
# process_action callbacks around the normal behavior.
def process_action(method_name)
def process_action(method_name, *args)
run_callbacks(:process_action, method_name) do
super
end

View File

@@ -265,11 +265,11 @@ module AbstractController
raise ArgumentError, "Layouts must be specified as a String, Symbol, false, or nil"
when nil
if name
_prefix = "layouts" unless _implied_layout_name =~ /\blayouts/
_prefixes = _implied_layout_name =~ /\blayouts/ ? [] : ["layouts"]
self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
def _layout
if template_exists?("#{_implied_layout_name}", #{_prefix.inspect})
if template_exists?("#{_implied_layout_name}", #{_prefixes.inspect})
"#{_implied_layout_name}"
else
super

View File

@@ -13,14 +13,15 @@ module AbstractController
# This is a class to fix I18n global state. Whenever you provide I18n.locale during a request,
# it will trigger the lookup_context and consequently expire the cache.
class I18nProxy < ::I18n::Config #:nodoc:
attr_reader :i18n_config, :lookup_context
attr_reader :original_config, :lookup_context
def initialize(i18n_config, lookup_context)
@i18n_config, @lookup_context = i18n_config, lookup_context
def initialize(original_config, lookup_context)
original_config = original_config.original_config if original_config.respond_to?(:original_config)
@original_config, @lookup_context = original_config, lookup_context
end
def locale
@i18n_config.locale
@original_config.locale
end
def locale=(value)
@@ -60,6 +61,20 @@ module AbstractController
end
end
end
def parent_prefixes
@parent_prefixes ||= begin
parent_controller = superclass
prefixes = []
until parent_controller.abstract?
prefixes << parent_controller.controller_path
parent_controller = parent_controller.superclass
end
prefixes
end
end
end
attr_writer :view_context_class
@@ -98,7 +113,7 @@ module AbstractController
def render_to_string(*args, &block)
options = _normalize_args(*args, &block)
_normalize_options(options)
render_to_body(options)
render_to_body(options).tap { self.response_body = nil }
end
# Raw rendering of a template to a Rack-compatible body.
@@ -114,9 +129,12 @@ module AbstractController
view_context.render(options)
end
# The prefix used in render "foo" shortcuts.
def _prefix
controller_path
# The prefixes used in render "foo" shortcuts.
def _prefixes
@_prefixes ||= begin
parent_prefixes = self.class.parent_prefixes
parent_prefixes.dup.unshift(controller_path)
end
end
private
@@ -156,7 +174,7 @@ module AbstractController
end
if (options.keys & [:partial, :file, :template, :once]).empty?
options[:prefix] ||= _prefix
options[:prefixes] ||= _prefixes
end
options[:template] ||= (options[:action] || action_name).to_s

View File

@@ -24,7 +24,7 @@ module ActionController
#
# Actions, by default, render a template in the <tt>app/views</tt> directory corresponding to the name of the controller and action
# after executing code in the action. For example, the +index+ action of the PostsController would render the
# template <tt>app/views/posts/index.erb</tt> by default after populating the <tt>@posts</tt> instance variable.
# template <tt>app/views/posts/index.html.erb</tt> by default after populating the <tt>@posts</tt> instance variable.
#
# Unlike index, the create action will not render a template. After performing its main purpose (creating a
# new post), it initiates a redirect instead. This redirect works by returning an external

View File

@@ -1,52 +1,72 @@
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 done using the cache helper available in the Action View. A template with caching might look something like:
# Fragment caching is used for caching various blocks within
# views 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 done using
# the <tt>cache</tt> helper available in the Action View. A
# template with fragment caching might look like:
#
# <b>Hello <%= @name %></b>
#
# <% cache do %>
# All the topics in the system:
# <%= render :partial => "topic", :collection => Topic.find(:all) %>
# <% end %>
#
# This cache will bind to the name of the action that called it, so if this code was part of the view for the topics/list action, you would
# be able to invalidate it using <tt>expire_fragment(:controller => "topics", :action => "list")</tt>.
# This cache will bind the name of the action that called it, so if
# this code was part of the view for the topics/list action, you
# would be able to invalidate it using:
#
# expire_fragment(:controller => "topics", :action => "list")
#
# This default behavior is of limited use if you need to cache multiple fragments per action or if the action itself is cached using
# <tt>caches_action</tt>, so we also have the option to qualify the name of the cached fragment with something like:
# This default behavior is limited if you need to cache multiple
# fragments per action or if the action itself is cached using
# <tt>caches_action</tt>. To remedy this, there is an option to
# qualify the name of the cached fragment by using the
# <tt>:action_suffix</tt> option:
#
# <% cache(:action => "list", :action_suffix => "all_topics") do %>
#
# That would result in a name such as <tt>/topics/list/all_topics</tt>, avoiding conflicts with the action cache and with any fragments that use a
# different suffix. Note that the URL doesn't have to really exist or be callable - the url_for system is just used to generate unique
# cache names that we can refer to when we need to expire the cache.
# That would result in a name such as
# <tt>/topics/list/all_topics</tt>, avoiding conflicts with the
# action cache and with any fragments that use a different suffix.
# Note that the URL doesn't have to really exist or be callable
# - the url_for system is just used to generate unique cache names
# that we can refer to when we need to expire the cache.
#
# The expiration call for this example is:
#
# expire_fragment(:controller => "topics", :action => "list", :action_suffix => "all_topics")
# expire_fragment(:controller => "topics",
# :action => "list",
# :action_suffix => "all_topics")
module Fragments
# Given a key (as described in <tt>expire_fragment</tt>), returns a key suitable for use in reading,
# writing, or expiring a cached fragment. If the key is a hash, the generated key is the return
# value of url_for on that hash (without the protocol). All keys are prefixed with <tt>views/</tt> and uses
# Given a key (as described in <tt>expire_fragment</tt>), returns
# a key suitable for use in reading, writing, or expiring a
# cached fragment. If the key is a hash, the generated key is the
# return value of url_for on that hash (without the protocol).
# All keys are prefixed with <tt>views/</tt> and uses
# ActiveSupport::Cache.expand_cache_key for the expansion.
def fragment_cache_key(key)
ActiveSupport::Cache.expand_cache_key(key.is_a?(Hash) ? url_for(key).split("://").last : key, :views)
end
# Writes <tt>content</tt> to the location signified by <tt>key</tt> (see <tt>expire_fragment</tt> for acceptable formats)
# Writes <tt>content</tt> to the location signified by
# <tt>key</tt> (see <tt>expire_fragment</tt> for acceptable formats).
def write_fragment(key, content, options = nil)
return content unless cache_configured?
key = fragment_cache_key(key)
instrument_fragment_cache :write_fragment, key do
content = content.html_safe.to_str if content.respond_to?(:html_safe)
content = content.to_str
cache_store.write(key, content, options)
end
content
end
# Reads a cached fragment from the location signified by <tt>key</tt> (see <tt>expire_fragment</tt> for acceptable formats)
# Reads a cached fragment from the location signified by <tt>key</tt>
# (see <tt>expire_fragment</tt> for acceptable formats).
def read_fragment(key, options = nil)
return unless cache_configured?
@@ -57,7 +77,8 @@ module ActionController #:nodoc:
end
end
# Check if a cached fragment from the location signified by <tt>key</tt> exists (see <tt>expire_fragment</tt> for acceptable formats)
# Check if a cached fragment from the location signified by
# <tt>key</tt> exists (see <tt>expire_fragment</tt> for acceptable formats)
def fragment_exist?(key, options = nil)
return unless cache_configured?
key = fragment_cache_key(key)
@@ -70,6 +91,7 @@ module ActionController #:nodoc:
# Removes fragments from the cache.
#
# +key+ can take one of three forms:
#
# * String - This would normally take the form of a path, like
# <tt>pages/45/notes</tt>.
# * Hash - Treated as an implicit call to +url_for+, like

View File

@@ -106,7 +106,7 @@ module ActionController #:nodoc:
end
def page_cache_path(path, extension = nil)
page_cache_directory + page_cache_file(path, extension)
page_cache_directory.to_s + page_cache_file(path, extension)
end
def instrument_page_cache(name, path)

View File

@@ -16,7 +16,11 @@ module ActionController
payload = event.payload
additions = ActionController::Base.log_process_action(payload)
message = "Completed #{payload[:status]} #{Rack::Utils::HTTP_STATUS_CODES[payload[:status]]} in %.0fms" % event.duration
status = payload[:status]
if status.nil? && payload[:exception].present?
status = Rack::Utils.status_code(ActionDispatch::ShowExceptions.rescue_responses[payload[:exception].first]) rescue nil
end
message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in %.0fms" % event.duration
message << " (#{additions.join(" | ")})" unless additions.blank?
info(message)

View File

@@ -43,12 +43,61 @@ module ActionController
end
end
# Provides a way to get a valid Rack application from a controller.
# <tt>ActionController::Metal</tt> is the simplest possible controller, providing a
# valid Rack interface without the additional niceties provided by
# <tt>ActionController::Base</tt>.
#
# A sample metal controller might look like this:
#
# class HelloController < ActionController::Metal
# def index
# self.response_body = "Hello World!"
# end
# end
#
# And then to route requests to your metal controller, you would add
# something like this to <tt>config/routes.rb</tt>:
#
# match 'hello', :to => HelloController.action(:index)
#
# The +action+ method returns a valid Rack application for the \Rails
# router to dispatch to.
#
# == Rendering Helpers
#
# <tt>ActionController::Metal</tt> by default provides no utilities for rendering
# views, partials, or other responses aside from explicitly calling of
# <tt>response_body=</tt>, <tt>content_type=</tt>, and <tt>status=</tt>. To
# add the render helpers you're used to having in a normal controller, you
# can do the following:
#
# class HelloController < ActionController::Metal
# include ActionController::Rendering
# append_view_path "#{Rails.root}/app/views"
#
# def index
# render "hello/index"
# end
# end
#
# == Redirection Helpers
#
# To add redirection helpers to your metal controller, do the following:
#
# class HelloController < ActionController::Metal
# include ActionController::Redirecting
# include Rails.application.routes.url_helpers
#
# def index
# redirect_to root_url
# end
# end
#
# == Other Helpers
#
# You can refer to the modules included in <tt>ActionController::Base</tt> to see
# other features you can bring into your metal controller.
#
# In AbstractController, dispatching is triggered directly by calling #process on a new controller.
# <tt>ActionController::Metal</tt> provides an <tt>action</tt> method that returns a valid Rack application for a
# given action. Other rack builders, such as Rack::Builder, Rack::URLMap, and the \Rails router,
# can dispatch directly to actions returned by controllers in your application.
class Metal < AbstractController::Base
abstract!
@@ -133,7 +182,7 @@ module ActionController
end
def response_body=(val)
body = val.respond_to?(:each) ? val : [val]
body = val.nil? ? nil : (val.respond_to?(:each) ? val : [val])
super body
end

View File

@@ -12,10 +12,10 @@ module ActionController
def method_for_action(action_name)
super || begin
if template_exists?(action_name.to_s, _prefix)
if template_exists?(action_name.to_s, _prefixes)
"default_render"
end
end
end
end
end
end

View File

@@ -63,13 +63,13 @@ module ActionController #:nodoc:
# might look something like this:
#
# def index
# @people = Person.find(:all)
# @people = Person.all
# end
#
# Here's the same action, with web-service support baked in:
#
# def index
# @people = Person.find(:all)
# @people = Person.all
#
# respond_to do |format|
# format.html
@@ -155,7 +155,7 @@ module ActionController #:nodoc:
# Respond to also allows you to specify a common block for different formats by using any:
#
# def index
# @people = Person.find(:all)
# @people = Person.all
#
# respond_to do |format|
# format.html
@@ -178,7 +178,7 @@ module ActionController #:nodoc:
# respond_to :html, :xml, :json
#
# def index
# @people = Person.find(:all)
# @people = Person.all
# respond_with(@person)
# end
# end
@@ -208,8 +208,8 @@ module ActionController #:nodoc:
# It also accepts a block to be given. It's used to overwrite a default
# response:
#
# def destroy
# @user = User.find(params[:id])
# def create
# @user = User.new(params[:user])
# flash[:notice] = "User was successfully created." if @user.save
#
# respond_with(@user) do |format|

View File

@@ -6,7 +6,7 @@ module ActionController
# Before processing, set the request formats in current controller formats.
def process_action(*) #:nodoc:
self.formats = request.formats.map { |x| x.to_sym }
self.formats = request.formats.map { |x| x.ref }
super
end

View File

@@ -71,25 +71,24 @@ module ActionController #:nodoc:
end
protected
def protect_from_forgery(options = {})
self.request_forgery_protection_token ||= :authenticity_token
before_filter :verify_authenticity_token, options
end
# The actual before_filter that is used. Modify this to change how you handle unverified requests.
def verify_authenticity_token
verified_request? || raise(ActionController::InvalidAuthenticityToken)
verified_request? || handle_unverified_request
end
def handle_unverified_request
reset_session
end
# Returns true or false if a request is verified. Checks:
#
# * is the format restricted? By default, only HTML requests are checked.
# * is it a GET request? Gets should be safe and idempotent
# * Does the form_authenticity_token match the given token value from the params?
# * Does the X-CSRF-Token header match the form_authenticity_token
def verified_request?
!protect_against_forgery? || request.forgery_whitelisted? ||
form_authenticity_token == params[request_forgery_protection_token]
!protect_against_forgery? || request.get? ||
form_authenticity_token == params[request_forgery_protection_token] ||
form_authenticity_token == request.headers['X-CSRF-Token']
end
# Sets the token value for the current session.

View File

@@ -77,8 +77,6 @@ module ActionController #:nodoc:
#
# respond_with(@project, :manager, @task)
#
# Check <code>polymorphic_url</code> documentation for more examples.
#
class Responder
attr_reader :controller, :request, :format, :resource, :resources, :options
@@ -115,7 +113,7 @@ module ActionController #:nodoc:
# Main entry point for responder responsible to dispatch to the proper format.
#
def respond
method = :"to_#{format}"
method = "to_#{format}"
respond_to?(method) ? send(method) : to_format
end
@@ -171,7 +169,7 @@ module ActionController #:nodoc:
# Checks whether the resource responds to the current format or not.
#
def resourceful?
resource.respond_to?(:"to_#{format}")
resource.respond_to?("to_#{format}")
end
# Returns the resource location by retrieving it from the options or

View File

@@ -170,7 +170,7 @@ module HTML
def contains_bad_protocols?(attr_name, value)
uri_attributes.include?(attr_name) &&
(value =~ /(^[^\/:]*):|(&#0*58)|(&#x70)|(%|&#37;)3A/ && !allowed_protocols.include?(value.split(protocol_separator).first))
(value =~ /(^[^\/:]*):|(&#0*58)|(&#x70)|(%|&#37;)3A/ && !allowed_protocols.include?(value.split(protocol_separator).first.downcase))
end
end
end

View File

@@ -43,7 +43,7 @@ module ActionDispatch
alias :etag? :etag
def initialize(*)
status, header, body = super
super
@cache_control = {}
@etag = self["ETag"]

View File

@@ -216,7 +216,11 @@ module Mime
end
def to_sym
@symbol || @string.to_sym
@symbol
end
def ref
to_sym || to_s
end
def ===(list)

View File

@@ -2,6 +2,7 @@ require 'tempfile'
require 'stringio'
require 'strscan'
require 'active_support/core_ext/module/deprecation'
require 'active_support/core_ext/hash/indifferent_access'
require 'active_support/core_ext/string/access'
require 'active_support/inflector'
@@ -133,8 +134,9 @@ module ActionDispatch
end
def forgery_whitelisted?
get? || xhr? || content_mime_type.nil? || !content_mime_type.verify_request?
get?
end
deprecate :forgery_whitelisted? => "it is just an alias for 'get?' now, update your code"
def media_type
content_mime_type.to_s

View File

@@ -28,8 +28,11 @@ module ActionDispatch
rewritten_url = ""
unless options[:only_path]
rewritten_url << (options[:protocol] || "http")
rewritten_url << "://" unless rewritten_url.match("://")
unless options[:protocol] == false
rewritten_url << (options[:protocol] || "http")
rewritten_url << ":" unless rewritten_url.match(%r{:|//})
end
rewritten_url << "//" unless rewritten_url.match("//")
rewritten_url << rewrite_authentication(options)
rewritten_url << host_or_subdomain_and_domain(options)
rewritten_url << ":#{options.delete(:port)}" if options[:port]

View File

@@ -1,3 +1,5 @@
require 'active_support/core_ext/module/delegation'
module ActionDispatch
# Provide callbacks to be executed before and after the request dispatch.
class Callbacks
@@ -5,10 +7,8 @@ module ActionDispatch
define_callbacks :call, :rescuable => true
def self.to_prepare(*args, &block)
ActiveSupport::Deprecation.warn "ActionDispatch::Callbacks.to_prepare is deprecated. " <<
"Please use ActionDispatch::Reloader.to_prepare instead."
ActionDispatch::Reloader.to_prepare(*args, &block)
class << self
delegate :to_prepare, :to_cleanup, :to => "ActionDispatch::Reloader"
end
def self.before(*args, &block)
@@ -25,7 +25,7 @@ module ActionDispatch
end
def call(env)
_run_call_callbacks do
run_callbacks :call do
@app.call(env)
end
end

View File

@@ -90,17 +90,14 @@ module ActionDispatch
# **.**, ***.** style TLDs like co.uk or com.au
#
# www.example.co.uk gives:
# $1 => example
# $2 => co.uk
# $& => example.co.uk
#
# example.com gives:
# $1 => example
# $2 => com
# $& => example.com
#
# lots.of.subdomains.example.local gives:
# $1 => example
# $2 => local
DOMAIN_REGEXP = /([^.]*)\.([^.]*|..\...|...\...)$/
# $& => example.local
DOMAIN_REGEXP = /[^.]*\.([^.]*|..\...|...\...)$/
def self.build(request)
secret = request.env[TOKEN_KEY]
@@ -131,11 +128,17 @@ module ActionDispatch
options[:path] ||= "/"
if options[:domain] == :all
# if there is a provided tld length then we use it otherwise default domain regexp
domain_regexp = options[:tld_length] ? /([^.]+\.?){#{options[:tld_length]}}$/ : DOMAIN_REGEXP
# if host is not ip and matches domain regexp
# (ip confirms to domain regexp so we explicitly check for ip)
options[:domain] = if (@host !~ /^[\d.]+$/) && (@host =~ DOMAIN_REGEXP)
".#{$1}.#{$2}"
options[:domain] = if (@host !~ /^[\d.]+$/) && (@host =~ domain_regexp)
".#{$&}"
end
elsif options[:domain].is_a? Array
# if host matches one of the supplied domains without a dot in front of it
options[:domain] = options[:domain].find {|domain| @host.include? domain[/^\.?(.*)$/, 1] }
end
end

View File

@@ -1,9 +1,12 @@
module ActionDispatch
# ActionDispatch::Reloader provides to_prepare and to_cleanup callbacks.
# These are analogs of ActionDispatch::Callback's before and after
# callbacks, with the difference that to_cleanup is not called until the
# ActionDispatch::Reloader provides prepare and cleanup callbacks,
# intended to assist with code reloading during development.
#
# Prepare callbacks are run before each request, and cleanup callbacks
# after each request. In this respect they are analogs of ActionDispatch::Callback's
# before and after callbacks. However, cleanup callbacks are not called until the
# request is fully complete -- that is, after #close has been called on
# the request body. This is important for streaming responses such as the
# the response body. This is important for streaming responses such as the
# following:
#
# self.response_body = lambda { |response, output|
@@ -15,7 +18,10 @@ module ActionDispatch
# classes before they are unloaded.
#
# By default, ActionDispatch::Reloader is included in the middleware stack
# only in the development environment.
# only in the development environment; specifically, when config.cache_classes
# is false. Callbacks may be registered even when it is not included in the
# middleware stack, but are executed only when +ActionDispatch::Reloader.prepare!+
# or +ActionDispatch::Reloader.cleanup!+ are called manually.
#
class Reloader
include ActiveSupport::Callbacks
@@ -23,8 +29,8 @@ module ActionDispatch
define_callbacks :prepare, :scope => :name
define_callbacks :cleanup, :scope => :name
# Add a preparation callback. Preparation callbacks are run before each
# request.
# Add a prepare callback. Prepare callbacks are run before each request, prior
# to ActionDispatch::Callback's before callbacks.
def self.to_prepare(*args, &block)
set_callback(:prepare, *args, &block)
end
@@ -35,12 +41,14 @@ module ActionDispatch
set_callback(:cleanup, *args, &block)
end
# Execute all prepare callbacks.
def self.prepare!
new(nil).send(:_run_prepare_callbacks)
new(nil).run_callbacks :prepare
end
# Execute all cleanup callbacks.
def self.cleanup!
new(nil).send(:_run_cleanup_callbacks)
new(nil).run_callbacks :cleanup
end
def initialize(app)
@@ -56,10 +64,13 @@ module ActionDispatch
end
def call(env)
_run_prepare_callbacks
run_callbacks :prepare
response = @app.call(env)
response[2].extend(CleanupOnClose)
response
rescue Exception
run_callbacks :cleanup
raise
end
end
end

View File

@@ -43,20 +43,20 @@ module ActionDispatch
end
def call(env)
status, headers, body = @app.call(env)
begin
status, headers, body = @app.call(env)
exception = nil
# Only this middleware cares about RoutingError. So, let's just raise
# it here.
# TODO: refactor this middleware to handle the X-Cascade scenario without
# having to raise an exception.
if headers['X-Cascade'] == 'pass'
raise ActionController::RoutingError, "No route matches #{env['PATH_INFO'].inspect}"
# Only this middleware cares about RoutingError. So, let's just raise
# it here.
if headers['X-Cascade'] == 'pass'
raise ActionController::RoutingError, "No route matches #{env['PATH_INFO'].inspect}"
end
rescue Exception => exception
raise exception if env['action_dispatch.show_exceptions'] == false
end
[status, headers, body]
rescue Exception => exception
raise exception if env['action_dispatch.show_exceptions'] == false
render_exception(env, exception)
exception ? render_exception(env, exception) : [status, headers, body]
end
private

View File

@@ -6,5 +6,5 @@
</h1>
<pre><%=h @exception.message %></pre>
<%= render :file => "rescues/_trace.erb" %>
<%= render :file => "rescues/_request_and_response.erb" %>
<%= render :template => "rescues/_trace" %>
<%= render :template => "rescues/_request_and_response" %>

View File

@@ -13,9 +13,5 @@
<p><%=h @exception.sub_template_message %></p>
<% @real_exception = @exception
@exception = @exception.original_exception || @exception %>
<%= render :file => "rescues/_trace.erb" %>
<% @exception = @real_exception %>
<%= render :file => "rescues/_request_and_response.erb" %>
<%= render :template => "rescues/_trace" %>
<%= render :template => "rescues/_request_and_response" %>

View File

@@ -56,6 +56,18 @@ module ActionDispatch
# resources :posts, :comments
# end
#
# Alternately, you can add prefixes to your path without using a separate
# directory by using +scope+. +scope+ takes additional options which
# apply to all enclosed routes.
#
# scope :path => "/cpanel", :as => 'admin' do
# resources :posts, :comments
# end
#
# For more, see <tt>Routing::Mapper::Resources#resources</tt>,
# <tt>Routing::Mapper::Scoping#namespace</tt>, and
# <tt>Routing::Mapper::Scoping#scope</tt>.
#
# == Named routes
#
# Routes can be named by passing an <tt>:as</tt> option,

View File

@@ -22,18 +22,22 @@ module ActionDispatch
@app, @constraints, @request = app, constraints, request
end
def call(env)
def matches?(env)
req = @request.new(env)
@constraints.each { |constraint|
if constraint.respond_to?(:matches?) && !constraint.matches?(req)
return [ 404, {'X-Cascade' => 'pass'}, [] ]
return false
elsif constraint.respond_to?(:call) && !constraint.call(*constraint_args(constraint, req))
return [ 404, {'X-Cascade' => 'pass'}, [] ]
return false
end
}
@app.call(env)
return true
end
def call(env)
matches?(env) ? @app.call(env) : [ 404, {'X-Cascade' => 'pass'}, [] ]
end
private
@@ -247,7 +251,7 @@ module ActionDispatch
#
# root :to => 'pages#main'
#
# For options, see the +match+ method's documentation, as +root+ uses it internally.
# For options, see +match+, as +root+ uses it internally.
#
# You should put the root route at the top of <tt>config/routes.rb</tt>,
# because this means it will be matched first. As this is the most popular route
@@ -256,15 +260,114 @@ module ActionDispatch
match '/', options.reverse_merge(:as => :root)
end
# When you set up a regular route, you supply a series of symbols that
# Rails maps to parts of an incoming HTTP request.
# Matches a url pattern to one or more routes. Any symbols in a pattern
# are interpreted as url query parameters and thus available as +params+
# in an action:
#
# match ':controller/:action/:id/:user_id'
# # sets :controller, :action and :id in params
# match ':controller/:action/:id'
#
# Two of these symbols are special: :controller maps to the name of a
# controller in your application, and :action maps to the name of an
# action within that controller. Anything other than :controller or
# :action will be available to the action as part of params.
# Two of these symbols are special, +:controller+ maps to the controller
# and +:action+ to the controller's action. A pattern can also map
# wildcard segments (globs) to params:
#
# match 'songs/*category/:title' => 'songs#show'
#
# # 'songs/rock/classic/stairway-to-heaven' sets
# # params[:category] = 'rock/classic'
# # params[:title] = 'stairway-to-heaven'
#
# When a pattern points to an internal route, the route's +:action+ and
# +:controller+ should be set in options or hash shorthand. Examples:
#
# match 'photos/:id' => 'photos#show'
# match 'photos/:id', :to => 'photos#show'
# match 'photos/:id', :controller => 'photos', :action => 'show'
#
# A pattern can also point to a +Rack+ endpoint i.e. anything that
# responds to +call+:
#
# match 'photos/:id' => lambda {|hash| [200, {}, "Coming soon" }
# match 'photos/:id' => PhotoRackApp
# # Yes, controller actions are just rack endpoints
# match 'photos/:id' => PhotosController.action(:show)
#
# === Options
#
# Any options not seen here are passed on as params with the url.
#
# [:controller]
# The route's controller.
#
# [:action]
# The route's action.
#
# [:path]
# The path prefix for the routes.
#
# [:module]
# The namespace for :controller.
#
# match 'path' => 'c#a', :module => 'sekret', :controller => 'posts'
# #=> Sekret::PostsController
#
# See <tt>Scoping#namespace</tt> for its scope equivalent.
#
# [:as]
# The name used to generate routing helpers.
#
# [:via]
# Allowed HTTP verb(s) for route.
#
# match 'path' => 'c#a', :via => :get
# match 'path' => 'c#a', :via => [:get, :post]
#
# [:to]
# Points to a +Rack+ endpoint. Can be an object that responds to
# +call+ or a string representing a controller's action.
#
# match 'path', :to => 'controller#action'
# match 'path', :to => lambda { [200, {}, "Success!"] }
# match 'path', :to => RackApp
#
# [:on]
# Shorthand for wrapping routes in a specific RESTful context. Valid
# values are :member, :collection, and :new. Only use within
# <tt>resource(s)</tt> block. For example:
#
# resource :bar do
# match 'foo' => 'c#a', :on => :member, :via => [:get, :post]
# end
#
# Is equivalent to:
#
# resource :bar do
# member do
# match 'foo' => 'c#a', :via => [:get, :post]
# end
# end
#
# [:constraints]
# Constrains parameters with a hash of regular expressions or an
# object that responds to #matches?
#
# match 'path/:id', :constraints => { :id => /[A-Z]\d{5}/ }
#
# class Blacklist
# def matches?(request) request.remote_ip == '1.2.3.4' end
# end
# match 'path' => 'c#a', :constraints => Blacklist.new
#
# See <tt>Scoping#constraints</tt> for more examples with its scope
# equivalent.
#
# [:defaults]
# Sets defaults for parameters
#
# # Sets params[:format] to 'jpg' by default
# match 'path' => 'c#a', :defaults => { :format => 'jpg' }
#
# See <tt>Scoping#defaults</tt> for its scope equivalent.
def match(path, options=nil)
mapping = Mapping.new(@set, @scope, path, options || {}).to_route
@set.add_route(*mapping)
@@ -279,6 +382,8 @@ module ActionDispatch
#
# mount(SomeRackApp => "some_route")
#
# For options, see +match+, as +mount+ uses it internally.
#
# All mounted applications come with routing helpers to access them.
# These are named after the class specified, so for the above example
# the helper is either +some_rack_app_path+ or +some_rack_app_url+.
@@ -349,7 +454,7 @@ module ActionDispatch
module HttpHelpers
# Define a route that only recognizes HTTP GET.
# For supported arguments, see +match+.
# For supported arguments, see <tt>Base#match</tt>.
#
# Example:
#
@@ -359,7 +464,7 @@ module ActionDispatch
end
# Define a route that only recognizes HTTP POST.
# For supported arguments, see +match+.
# For supported arguments, see <tt>Base#match</tt>.
#
# Example:
#
@@ -369,7 +474,7 @@ module ActionDispatch
end
# Define a route that only recognizes HTTP PUT.
# For supported arguments, see +match+.
# For supported arguments, see <tt>Base#match</tt>.
#
# Example:
#
@@ -379,7 +484,7 @@ module ActionDispatch
end
# Define a route that only recognizes HTTP PUT.
# For supported arguments, see +match+.
# For supported arguments, see <tt>Base#match</tt>.
#
# Example:
#
@@ -458,7 +563,7 @@ module ActionDispatch
super
end
# Used to scope a set of routes to particular constraints.
# Scopes a set of routes to the given default options.
#
# Take the following route definition as an example:
#
@@ -470,51 +575,26 @@ module ActionDispatch
# The difference here being that the routes generated are like /rails/projects/2,
# rather than /accounts/rails/projects/2.
#
# === Supported options
# [:module]
# If you want to route /posts (without the prefix /admin) to
# Admin::PostsController, you could use
# === Options
#
# scope :module => "admin" do
# resources :posts
# end
# Takes same options as <tt>Base#match</tt> and <tt>Resources#resources</tt>.
#
# [:path]
# If you want to prefix the route, you could use
# === Examples
#
# scope :path => "/admin" do
# resources :posts
# end
# # route /posts (without the prefix /admin) to Admin::PostsController
# scope :module => "admin" do
# resources :posts
# end
#
# This will prefix all of the +posts+ resource's requests with '/admin'
# # prefix the posts resource's requests with '/admin'
# scope :path => "/admin" do
# resources :posts
# end
#
# [:as]
# Prefixes the routing helpers in this scope with the specified label.
#
# scope :as => "sekret" do
# resources :posts
# end
#
# Helpers such as +posts_path+ will now be +sekret_posts_path+
#
# [:shallow_path]
#
# Prefixes nested shallow routes with the specified path.
#
# scope :shallow_path => "sekret" do
# resources :posts do
# resources :comments, :shallow => true
# end
#
# The +comments+ resource here will have the following routes generated for it:
#
# post_comments GET /sekret/posts/:post_id/comments(.:format)
# post_comments POST /sekret/posts/:post_id/comments(.:format)
# new_post_comment GET /sekret/posts/:post_id/comments/new(.:format)
# edit_comment GET /sekret/comments/:id/edit(.:format)
# comment GET /sekret/comments/:id(.:format)
# comment PUT /sekret/comments/:id(.:format)
# comment DELETE /sekret/comments/:id(.:format)
# # prefix the routing helper name: sekret_posts_path instead of posts_path
# scope :as => "sekret" do
# resources :posts
# end
def scope(*args)
options = args.extract_options!
options = options.dup
@@ -577,43 +657,31 @@ module ActionDispatch
# admin_post GET /admin/posts/:id(.:format) {:action=>"show", :controller=>"admin/posts"}
# admin_post PUT /admin/posts/:id(.:format) {:action=>"update", :controller=>"admin/posts"}
# admin_post DELETE /admin/posts/:id(.:format) {:action=>"destroy", :controller=>"admin/posts"}
# === Supported options
#
# The +:path+, +:as+, +:module+, +:shallow_path+ and +:shallow_prefix+ options all default to the name of the namespace.
# === Options
#
# [:path]
# The path prefix for the routes.
# The +:path+, +:as+, +:module+, +:shallow_path+ and +:shallow_prefix+
# options all default to the name of the namespace.
#
# namespace :admin, :path => "sekret" do
# resources :posts
# end
# For options, see <tt>Base#match</tt>. For +:shallow_path+ option, see
# <tt>Resources#resources</tt>.
#
# All routes for the above +resources+ will be accessible through +/sekret/posts+, rather than +/admin/posts+
# === Examples
#
# [:module]
# The namespace for the controllers.
# # accessible through /sekret/posts rather than /admin/posts
# namespace :admin, :path => "sekret" do
# resources :posts
# end
#
# namespace :admin, :module => "sekret" do
# resources :posts
# end
# # maps to Sekret::PostsController rather than Admin::PostsController
# namespace :admin, :module => "sekret" do
# resources :posts
# end
#
# The +PostsController+ here should go in the +Sekret+ namespace and so it should be defined like this:
#
# class Sekret::PostsController < ApplicationController
# # code go here
# end
#
# [:as]
# Changes the name used in routing helpers for this namespace.
#
# namespace :admin, :as => "sekret" do
# resources :posts
# end
#
# Routing helpers such as +admin_posts_path+ will now be +sekret_posts_path+.
#
# [:shallow_path]
# See the +scope+ method.
# # generates sekret_posts_path rather than admin_posts_path
# namespace :admin, :as => "sekret" do
# resources :posts
# end
def namespace(path, options = {})
path = path.to_s
options = { :path => path, :as => path, :module => path,
@@ -680,9 +748,9 @@ module ActionDispatch
end
# Allows you to set default parameters for a route, such as this:
# defaults :id => 'home' do
# match 'scoped_pages/(:id)', :to => 'pages#show'
# end
# defaults :id => 'home' do
# match 'scoped_pages/(:id)', :to => 'pages#show'
# end
# Using this, the +:id+ parameter here will default to 'home'.
def defaults(defaults = {})
scope(:defaults => defaults) { yield }
@@ -779,6 +847,14 @@ module ActionDispatch
# resources :posts, :comments
# end
#
# By default the :id parameter doesn't accept dots. If you need to
# use dots as part of the :id parameter add a constraint which
# overrides this restriction, e.g:
#
# resources :articles, :id => /[^\/]+/
#
# This allows any character other than a slash as part of your :id.
#
module Resources
# CANONICAL_ACTIONS holds all actions that does not need a prefix or
# a path appended since they fit properly in their scope level.
@@ -827,7 +903,8 @@ module ActionDispatch
alias :member_name :singular
# Checks for uncountable plurals, and appends "_index" if they're.
# Checks for uncountable plurals, and appends "_index" if the plural
# and singular form are the same.
def collection_name
singular == plural ? "#{plural}_index" : plural
end
@@ -906,6 +983,9 @@ module ActionDispatch
# GET /geocoder/edit
# PUT /geocoder
# DELETE /geocoder
#
# === Options
# Takes same options as +resources+.
def resource(*resources, &block)
options = resources.extract_options!
@@ -967,7 +1047,9 @@ module ActionDispatch
# PUT /photos/:id/comments/:id
# DELETE /photos/:id/comments/:id
#
# === Supported options
# === Options
# Takes same options as <tt>Base#match</tt> as well as:
#
# [:path_names]
# Allows you to change the paths of the seven default actions.
# Paths not specified are not changed.
@@ -976,20 +1058,59 @@ module ActionDispatch
#
# The above example will now change /posts/new to /posts/brand_new
#
# [:module]
# Set the module where the controller can be found. Defaults to nothing.
# [:only]
# Only generate routes for the given actions.
#
# resources :posts, :module => "admin"
# resources :cows, :only => :show
# resources :cows, :only => [:show, :index]
#
# All requests to the posts resources will now go to +Admin::PostsController+.
# [:except]
# Generate all routes except for the given actions.
#
# [:path]
# resources :cows, :except => :show
# resources :cows, :except => [:show, :index]
#
# Set a path prefix for this resource.
# [:shallow]
# Generates shallow routes for nested resource(s). When placed on a parent resource,
# generates shallow routes for all nested resources.
#
# resources :posts, :path => "admin"
# resources :posts, :shallow => true do
# resources :comments
# end
#
# All actions for this resource will now be at +/admin/posts+.
# Is the same as:
#
# resources :posts do
# resources :comments
# end
# resources :comments
#
# [:shallow_path]
# Prefixes nested shallow routes with the specified path.
#
# scope :shallow_path => "sekret" do
# resources :posts do
# resources :comments, :shallow => true
# end
# end
#
# The +comments+ resource here will have the following routes generated for it:
#
# post_comments GET /sekret/posts/:post_id/comments(.:format)
# post_comments POST /sekret/posts/:post_id/comments(.:format)
# new_post_comment GET /sekret/posts/:post_id/comments/new(.:format)
# edit_comment GET /sekret/comments/:id/edit(.:format)
# comment GET /sekret/comments/:id(.:format)
# comment PUT /sekret/comments/:id(.:format)
# comment DELETE /sekret/comments/:id(.:format)
#
# === Examples
#
# # routes call Admin::PostsController
# resources :posts, :module => "admin"
#
# # resource actions are at /admin/posts.
# resources :posts, :path => "admin"
def resources(*resources, &block)
options = resources.extract_options!
@@ -1111,7 +1232,7 @@ module ActionDispatch
end
def shallow
scope(:shallow => true) do
scope(:shallow => true, :shallow_path => @scope[:path]) do
yield
end
end
@@ -1321,7 +1442,7 @@ module ActionDispatch
name = case @scope[:scope_level]
when :nested
[member_name, prefix]
[name_prefix, prefix]
when :collection
[prefix, name_prefix, collection_name]
when :new

View File

@@ -450,7 +450,7 @@ module ActionDispatch
end
def raise_routing_error
raise ActionController::RoutingError.new("No route matches #{options.inspect}")
raise ActionController::RoutingError, "No route matches #{options.inspect}"
end
def different_controller?
@@ -540,7 +540,9 @@ module ActionDispatch
end
dispatcher = route.app
dispatcher = dispatcher.app while dispatcher.is_a?(Mapper::Constraints)
while dispatcher.is_a?(Mapper::Constraints) && dispatcher.matches?(env) do
dispatcher = dispatcher.app
end
if dispatcher.is_a?(Dispatcher) && dispatcher.controller(params, false)
dispatcher.prepare_params!(params)

View File

@@ -20,7 +20,7 @@ module ActionDispatch
#
# You can also pass an explicit status number like <tt>assert_response(501)</tt>
# or its symbolic equivalent <tt>assert_response(:not_implemented)</tt>.
# See ActionDispatch::StatusCodes for a full list.
# See Rack::Utils::SYMBOL_TO_STATUS_CODE for a full list.
#
# ==== Examples
#

View File

@@ -37,9 +37,6 @@ module ActionDispatch
#
# # Test a custom route
# assert_recognizes({:controller => 'items', :action => 'show', :id => '1'}, 'view/item1')
#
# # Check a Simply RESTful generated route
# assert_recognizes list_items_url, 'items/list'
def assert_recognizes(expected_options, path, extras={}, message=nil)
request = recognized_request_for(path)
@@ -124,7 +121,8 @@ module ActionDispatch
options[:controller] = "/#{controller}"
end
assert_generates(path.is_a?(Hash) ? path[:path] : path, options, defaults, extras, message)
generate_options = options.dup.delete_if{ |k,v| defaults.key?(k) }
assert_generates(path.is_a?(Hash) ? path[:path] : path, generate_options, defaults, extras, message)
end
# A helper to make it easier to test different route configurations.

View File

@@ -18,7 +18,7 @@ module ActionView #:nodoc:
# following loop for names:
#
# <b>Names of all the people</b>
# <% for person in @people %>
# <% @people.each do |person| %>
# Name: <%= person.name %><br/>
# <% end %>
#
@@ -232,8 +232,8 @@ module ActionView #:nodoc:
@controller_path ||= controller && controller.controller_path
end
def controller_prefix
@controller_prefix ||= controller && controller._prefix
def controller_prefixes
@controller_prefixes ||= controller && controller._prefixes
end
ActiveSupport.run_load_hooks(:action_view, self)

View File

@@ -18,7 +18,7 @@ module ActionView #:nodoc:
autoload :JavaScriptHelper, "action_view/helpers/javascript_helper"
autoload :NumberHelper
autoload :PrototypeHelper
autoload :RawOutputHelper
autoload :OutputSafetyHelper
autoload :RecordTagHelper
autoload :SanitizeHelper
autoload :ScriptaculousHelper
@@ -48,7 +48,7 @@ module ActionView #:nodoc:
include JavaScriptHelper
include NumberHelper
include PrototypeHelper
include RawOutputHelper
include OutputSafetyHelper
include RecordTagHelper
include SanitizeHelper
include ScriptaculousHelper

View File

@@ -11,7 +11,9 @@ module ActionView
attr_reader :config, :asset_paths
class_attribute :expansions
self.expansions = { }
def self.inherited(base)
base.expansions = { }
end
def initialize(config, asset_paths)
@config = config
@@ -69,9 +71,21 @@ module ActionView
if sources.first == :all
collect_asset_files(custom_dir, ('**' if recursive), "*.#{extension}")
else
sources.collect do |source|
determine_source(source, expansions)
end.flatten
sources.inject([]) do |list, source|
determined_source = determine_source(source, expansions)
update_source_list(list, determined_source)
end
end
end
def update_source_list(list, source)
case source
when String
list.delete(source)
list << source
when Array
updated_sources = source - list
list.concat(updated_sources)
end
end

View File

@@ -33,13 +33,21 @@ module ActionView
all_asset_files = (collect_asset_files(custom_dir, ('**' if recursive), "*.#{extension}") - ['application']) << 'application'
((determine_source(:defaults, expansions).dup & all_asset_files) + all_asset_files).uniq
else
expanded_sources = sources.collect do |source|
determine_source(source, expansions)
end.flatten
expanded_sources << "application" if sources.include?(:defaults) && File.exist?(File.join(custom_dir, "application.#{extension}"))
expanded_sources = sources.inject([]) do |list, source|
determined_source = determine_source(source, expansions)
update_source_list(list, determined_source)
end
add_application_js(expanded_sources, sources)
expanded_sources
end
end
def add_application_js(expanded_sources, sources)
if sources.include?(:defaults) && File.exist?(File.join(custom_dir, "application.#{extension}"))
expanded_sources.delete('application')
expanded_sources << "application"
end
end
end
@@ -59,7 +67,10 @@ module ActionView
# <script type="text/javascript" src="/javascripts/body.js"></script>
# <script type="text/javascript" src="/javascripts/tail.js"></script>
def register_javascript_expansion(expansions)
JavascriptIncludeTag.expansions.merge!(expansions)
js_expansions = JavascriptIncludeTag.expansions
expansions.each do |key, values|
js_expansions[key] = (js_expansions[key] || []) | Array(values) if values
end
end
end
@@ -170,4 +181,4 @@ module ActionView
end
end
end
end

View File

@@ -44,7 +44,10 @@ module ActionView
# <link href="/stylesheets/body.css" media="screen" rel="stylesheet" type="text/css" />
# <link href="/stylesheets/tail.css" media="screen" rel="stylesheet" type="text/css" />
def register_stylesheet_expansion(expansions)
StylesheetIncludeTag.expansions.merge!(expansions)
style_expansions = StylesheetIncludeTag.expansions
expansions.each do |key, values|
style_expansions[key] = (style_expansions[key] || []) | Array(values) if values
end
end
end
@@ -141,4 +144,4 @@ module ActionView
end
end
end
end

View File

@@ -2,31 +2,29 @@ module ActionView
# = Action View Cache Helper
module Helpers
module CacheHelper
# This helper to exposes a method for caching of view fragments.
# This helper exposes a method for caching fragments of a view
# rather than an entire action or page. This technique is useful
# caching pieces like menus, lists of newstopics, static HTML
# fragments, and so on. This method takes a block that contains
# the content you wish to cache.
#
# See ActionController::Caching::Fragments for usage instructions.
#
# A method for caching fragments of a view rather than an entire
# action or page. This technique is useful caching pieces like
# menus, lists of news topics, static HTML fragments, and so on.
# This method takes a block that contains the content you wish
# to cache. See ActionController::Caching::Fragments for more
# information.
#
# ==== Examples
# If you wanted to cache a navigation menu, you could do the
# following.
# If you want to cache a navigation menu, you can do following:
#
# <% cache do %>
# <%= render :partial => "menu" %>
# <% end %>
#
# You can also cache static content...
# You can also cache static content:
#
# <% cache do %>
# <p>Hello users! Welcome to our website!</p>
# <% end %>
#
# ...and static content mixed with RHTML content.
# Static content with embedded ruby content can be cached as
# well:
#
# <% cache do %>
# Topics:
@@ -46,8 +44,8 @@ module ActionView
private
# TODO: Create an object that has caching read/write on it
def fragment_for(name = {}, options = nil, &block) #:nodoc:
if controller.fragment_exist?(name, options)
controller.read_fragment(name, options)
if fragment = controller.read_fragment(name, options)
fragment
else
# VIEW TODO: Make #capture usable outside of ERB
# This dance is needed because Builder can't use capture

View File

@@ -1,5 +1,6 @@
require 'date'
require 'action_view/helpers/tag_helper'
require 'active_support/core_ext/date/conversions'
require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/object/with_options'
@@ -566,6 +567,27 @@ module ActionView
def select_year(date, options = {}, html_options = {})
DateTimeSelector.new(date, options, html_options).select_year
end
# Returns an html time tag for the given date or time.
#
# ==== Examples
# time_tag Date.today # =>
# <time datetime="2010-11-04">November 04, 2010</time>
# time_tag Time.now # =>
# <time datetime="2010-11-04T17:55:45+01:00">November 04, 2010 17:55</time>
# time_tag Date.yesterday, 'Yesterday' # =>
# <time datetime="2010-11-03">Yesterday</time>
# time_tag Date.today, :pubdate => true # =>
# <time datetime="2010-11-04" pubdate="pubdate">November 04, 2010</time>
#
def time_tag(date_or_time, *args)
options = args.extract_options!
format = options.delete(:format) || :long
content = args.first || I18n.l(date_or_time, :format => format)
datetime = date_or_time.acts_like?(:time) ? date_or_time.xmlschema : date_or_time.rfc3339
content_tag(:time, content, options.reverse_merge(:datetime => datetime))
end
end
class DateTimeSelector #:nodoc:

View File

@@ -298,6 +298,23 @@ module ActionView
#
# If you don't need to attach a form to a model instance, then check out
# FormTagHelper#form_tag.
#
# === Form to external resources
#
# When you build forms to external resources sometimes you need to set an authenticity token or just render a form
# without it, for example when you submit data to a payment gateway number and types of fields could be limited.
#
# To set an authenticity token you need to pass an <tt>:authenticity_token</tt> parameter
#
# <%= form_for @invoice, :url => external_url, :authenticity_token => 'external_token' do |f|
# ...
# <% end %>
#
# If you don't want to an authenticity token field be rendered at all just pass <tt>false</tt>:
#
# <%= form_for @invoice, :url => external_url, :authenticity_token => false do |f|
# ...
# <% end %>
def form_for(record, options = {}, &proc)
raise ArgumentError, "Missing block" unless block_given?
@@ -314,6 +331,8 @@ module ActionView
end
options[:html][:remote] = options.delete(:remote)
options[:html][:authenticity_token] = options.delete(:authenticity_token)
builder = options[:parent_builder] = instantiate_builder(object_name, object, options, &proc)
fields_for = fields_for(object_name, object, options, &proc)
default_options = builder.multipart? ? { :multipart => true } : {}
@@ -530,8 +549,11 @@ module ActionView
# <% end %>
# ...
# <% end %>
def fields_for(record, record_object = nil, options = nil, &block)
capture(instantiate_builder(record, record_object, options, &block), &block)
def fields_for(record, record_object = nil, options = {}, &block)
builder = instantiate_builder(record, record_object, options, &block)
output = capture(builder, &block)
output.concat builder.hidden_field(:id) if output && options[:hidden_field_id] && !builder.emitted_hidden_id?
output
end
# Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object
@@ -858,8 +880,7 @@ module ActionView
end
end
module InstanceTagMethods #:nodoc:
extend ActiveSupport::Concern
class InstanceTag
include Helpers::CaptureHelper, Context, Helpers::TagHelper, Helpers::FormTagHelper
attr_reader :object, :method_name, :object_name
@@ -1025,7 +1046,7 @@ module ActionView
self.class.value_before_type_cast(object, @method_name)
end
module ClassMethods
class << self
def value(object, method_name)
object.send method_name if object
end
@@ -1111,10 +1132,6 @@ module ActionView
end
end
class InstanceTag
include InstanceTagMethods
end
class FormBuilder
# The methods which wrap a form helper call.
class_attribute :field_helpers
@@ -1248,7 +1265,7 @@ module ActionView
def submit(value=nil, options={})
value, options = nil, value if value.is_a?(Hash)
value ||= submit_default_value
@template.submit_tag(value, options.reverse_merge(:id => "#{object_name}_submit"))
@template.submit_tag(value, options)
end
def emitted_hidden_id?
@@ -1309,14 +1326,8 @@ module ActionView
def fields_for_nested_model(name, object, options, block)
object = convert_to_model(object)
if object.persisted?
@template.fields_for(name, object, options) do |builder|
block.call(builder)
@template.concat builder.hidden_field(:id) unless builder.emitted_hidden_id?
end
else
@template.fields_for(name, object, options, &block)
end
options[:hidden_field_id] = object.persisted?
@template.fields_for(name, object, options, &block)
end
def nested_child_index(name)

View File

@@ -533,7 +533,7 @@ module ActionView
else
selected = Array.wrap(selected)
options = selected.extract_options!.symbolize_keys
[ options[:selected] || selected , options[:disabled] ]
[ options.include?(:selected) ? options[:selected] : selected, options[:disabled] ]
end
end

View File

@@ -25,6 +25,9 @@ module ActionView
# * <tt>:method</tt> - The method to use when submitting the form, usually either "get" or "post".
# If "put", "delete", or another verb is used, a hidden input with name <tt>_method</tt>
# is added to simulate the verb over post.
# * <tt>:authenticity_token</tt> - Authenticity token to use in the form. Use only if you need to
# pass custom authenticity token string, or to not add authenticity_token field at all
# (by passing <tt>false</tt>).
# * A list of parameters to feed to the URL the form will be posted to.
# * <tt>:remote</tt> - If set to true, will allow the Unobtrusive JavaScript drivers to control the
# submit behaviour. By default this behaviour is an ajax submit.
@@ -47,6 +50,12 @@ module ActionView
# <%= form_tag('/posts', :remote => true) %>
# # => <form action="/posts" method="post" data-remote="true">
#
# form_tag('http://far.away.com/form', :authenticity_token => false)
# # form without authenticity token
#
# form_tag('http://far.away.com/form', :authenticity_token => "cf50faa3fe97702ca1ae")
# # form with custom authenticity token
#
def form_tag(url_for_options = {}, options = {}, *parameters_for_url, &block)
html_options = html_options_for_form(url_for_options, options, *parameters_for_url)
if block_given?
@@ -401,6 +410,59 @@ module ActionView
tag :input, { "type" => "submit", "name" => "commit", "value" => value }.update(options.stringify_keys)
end
# Creates a button element that defines a <tt>submit</tt> button,
# <tt>reset</tt>button or a generic button which can be used in
# JavaScript, for example. You can use the button tag as a regular
# submit tag but it isn't supported in legacy browsers. However,
# the button tag allows richer labels such as images and emphasis,
# so this helper will also accept a block.
#
# ==== Options
# * <tt>:confirm => 'question?'</tt> - If present, the
# unobtrusive JavaScript drivers will provide a prompt with
# the question specified. If the user accepts, the form is
# processed normally, otherwise no action is taken.
# * <tt>:disabled</tt> - If true, the user will not be able to
# use this input.
# * <tt>:disable_with</tt> - Value of this parameter will be
# used as the value for a disabled version of the submit
# button when the form is submitted. This feature is provided
# by the unobtrusive JavaScript driver.
# * Any other key creates standard HTML options for the tag.
#
# ==== Examples
# button_tag
# # => <button name="button" type="submit">Button</button>
#
# button_tag(:type => 'button') do
# content_tag(:strong, 'Ask me!')
# end
# # => <button name="button" type="button">
# <strong>Ask me!</strong>
# </button>
#
# button_tag "Checkout", :disable_with => "Please wait..."
# # => <button data-disable-with="Please wait..." name="button"
# type="submit">Checkout</button>
#
def button_tag(content_or_options = nil, options = nil, &block)
options = content_or_options if block_given? && content_or_options.is_a?(Hash)
options ||= {}
options.stringify_keys!
if disable_with = options.delete("disable_with")
options["data-disable-with"] = disable_with
end
if confirm = options.delete("confirm")
options["data-confirm"] = confirm
end
options.reverse_merge! 'name' => 'button', 'type' => 'submit'
content_tag :button, content_or_options || 'Button', options, &block
end
# Displays an image which when clicked will submit the form.
#
# <tt>source</tt> is passed to AssetTagHelper#path_to_image
@@ -534,6 +596,7 @@ module ActionView
html_options["action"] = url_for(url_for_options, *parameters_for_url)
html_options["accept-charset"] = "UTF-8"
html_options["data-remote"] = true if html_options.delete("remote")
html_options["authenticity_token"] = html_options.delete("authenticity_token") if html_options.has_key?("authenticity_token")
end
end
@@ -541,6 +604,7 @@ module ActionView
snowman_tag = tag(:input, :type => "hidden",
:name => "utf8", :value => "&#x2713;".html_safe)
authenticity_token = html_options.delete("authenticity_token")
method = html_options.delete("method").to_s
method_tag = case method
@@ -549,10 +613,10 @@ module ActionView
''
when /^post$/i, "", nil
html_options["method"] = "post"
token_tag
token_tag(authenticity_token)
else
html_options["method"] = "post"
tag(:input, :type => "hidden", :name => "_method", :value => method) + token_tag
tag(:input, :type => "hidden", :name => "_method", :value => method) + token_tag(authenticity_token)
end
tags = snowman_tag << method_tag
@@ -572,11 +636,12 @@ module ActionView
output.safe_concat("</form>")
end
def token_tag
unless protect_against_forgery?
def token_tag(token)
if token == false || !protect_against_forgery?
''
else
tag(:input, :type => "hidden", :name => request_forgery_protection_token.to_s, :value => form_authenticity_token)
token = form_authenticity_token if token.nil?
tag(:input, :type => "hidden", :name => request_forgery_protection_token.to_s, :value => token)
end
end

View File

@@ -0,0 +1,38 @@
require 'active_support/core_ext/string/output_safety'
module ActionView #:nodoc:
# = Action View Raw Output Helper
module Helpers #:nodoc:
module OutputSafetyHelper
# This method outputs without escaping a string. Since escaping tags is
# now default, this can be used when you don't want Rails to automatically
# escape tags. This is not recommended if the data is coming from the user's
# input.
#
# For example:
#
# <%=raw @user.name %>
def raw(stringish)
stringish.to_s.html_safe
end
# This method returns a html safe string similar to what <tt>Array#join</tt>
# would return. All items in the array, including the supplied separator, are
# html escaped unless they are html safe, and the returned string is marked
# as html safe.
#
# safe_join(["<p>foo</p>".html_safe, "<p>bar</p>"], "<br />")
# # => "<p>foo</p>&lt;br /&gt;&lt;p&gt;bar&lt;/p&gt;"
#
# safe_join(["<p>foo</p>".html_safe, "<p>bar</p>".html_safe], "<br />".html_safe)
# # => "<p>foo</p><br /><p>bar</p>"
#
def safe_join(array, sep=$,)
sep ||= "".html_safe
sep = ERB::Util.html_escape(sep)
array.map { |i| ERB::Util.html_escape(i) }.join(sep).html_safe
end
end
end
end

View File

@@ -1,18 +0,0 @@
module ActionView #:nodoc:
# = Action View Raw Output Helper
module Helpers #:nodoc:
module RawOutputHelper
# This method outputs without escaping a string. Since escaping tags is
# now default, this can be used when you don't want Rails to automatically
# escape tags. This is not recommended if the data is coming from the user's
# input.
#
# For example:
#
# <%=raw @user.name %>
def raw(stringish)
stringish.to_s.html_safe
end
end
end
end

View File

@@ -14,7 +14,7 @@ module ActionView
BOOLEAN_ATTRIBUTES = %w(disabled readonly multiple checked autobuffer
autoplay controls loop selected hidden scoped async
defer reversed ismap seemless muted required
autofocus novalidate formnovalidate open).to_set
autofocus novalidate formnovalidate open pubdate).to_set
BOOLEAN_ATTRIBUTES.merge(BOOLEAN_ATTRIBUTES.map {|attribute| attribute.to_sym })
# Returns an empty HTML tag of type +name+ which by default is XHTML

View File

@@ -459,7 +459,7 @@ module ActionView
end
AUTO_LINK_RE = %r{
(?: ([\w+.:-]+:)// | www\. )
(?: ([0-9A-Za-z+.:-]+:)// | www\. )
[^\s<]+
}x

View File

@@ -253,8 +253,9 @@ module ActionView
# using the +link_to+ method with the <tt>:method</tt> modifier as described in
# the +link_to+ documentation.
#
# The generated form element has a class name of <tt>button_to</tt>
# to allow styling of the form itself and its children. You can control
# By default, the generated form element has a class name of <tt>button_to</tt>
# to allow styling of the form itself and its children. This can be changed
# using the <tt>:form_class</tt> modifier within +html_options+. You can control
# the form submission and input element behavior using +html_options+.
# This method accepts the <tt>:method</tt> and <tt>:confirm</tt> modifiers
# described in the +link_to+ documentation. If no <tt>:method</tt> modifier
@@ -275,6 +276,8 @@ module ActionView
# processed normally, otherwise no action is taken.
# * <tt>:remote</tt> - If set to true, will allow the Unobtrusive JavaScript drivers to control the
# submit behaviour. By default this behaviour is an ajax submit.
# * <tt>:form_class</tt> - This controls the class of the form within which the submit button will
# be placed
#
# ==== Examples
# <%= button_to "New", :action => "new" %>
@@ -283,6 +286,12 @@ module ActionView
# # </form>"
#
#
# <%= button_to "New", :action => "new", :form_class => "new-thing" %>
# # => "<form method="post" action="/controller/new" class="new-thing">
# # <div><input value="New" type="submit" /></div>
# # </form>"
#
#
# <%= button_to "Delete Image", { :action => "delete", :id => @image.id },
# :confirm => "Are you sure?", :method => :delete %>
# # => "<form method="post" action="/images/delete/1" class="button_to">
@@ -312,6 +321,7 @@ module ActionView
end
form_method = method.to_s == 'get' ? 'get' : 'post'
form_class = html_options.delete('form_class') || 'button_to'
remote = html_options.delete('remote')
@@ -327,7 +337,7 @@ module ActionView
html_options.merge!("type" => "submit", "value" => name)
("<form method=\"#{form_method}\" action=\"#{ERB::Util.html_escape(url)}\" #{"data-remote=\"true\"" if remote} class=\"button_to\"><div>" +
("<form method=\"#{form_method}\" action=\"#{ERB::Util.html_escape(url)}\" #{"data-remote=\"true\"" if remote} class=\"#{ERB::Util.html_escape(form_class)}\"><div>" +
method_tag + tag("input", html_options) + request_token_tag + "</div></form>").html_safe
end
@@ -487,13 +497,14 @@ module ActionView
email_address_obfuscated = email_address.dup
email_address_obfuscated.gsub!(/@/, html_options.delete("replace_at")) if html_options.key?("replace_at")
email_address_obfuscated.gsub!(/\./, html_options.delete("replace_dot")) if html_options.key?("replace_dot")
case encode
when "javascript"
string =
"document.write('#{content_tag("a", name || email_address_obfuscated.html_safe, html_options.merge("href" => "mailto:#{email_address}#{extras}".html_safe))}');".unpack('C*').map { |c|
sprintf("%%%x", c)
}.join
string = ''
html = content_tag("a", name || email_address_obfuscated.html_safe, html_options.merge("href" => "mailto:#{email_address}#{extras}".html_safe))
html = escape_javascript(html)
"document.write('#{html}');".each_byte do |c|
string << sprintf("%%%x", c)
end
"<script type=\"#{Mime::JS}\">eval(decodeURIComponent('#{string}'))</script>".html_safe
when "hex"
email_address_encoded = email_address_obfuscated.unpack('C*').map {|c|

View File

@@ -78,17 +78,17 @@ module ActionView
@view_paths = ActionView::Base.process_view_paths(paths)
end
def find(name, prefix = nil, partial = false, keys = [])
@view_paths.find(*args_for_lookup(name, prefix, partial, keys))
def find(name, prefixes = [], partial = false, keys = [])
@view_paths.find(*args_for_lookup(name, prefixes, partial, keys))
end
alias :find_template :find
def find_all(name, prefix = nil, partial = false, keys = [])
@view_paths.find_all(*args_for_lookup(name, prefix, partial, keys))
def find_all(name, prefixes = [], partial = false, keys = [])
@view_paths.find_all(*args_for_lookup(name, prefixes, partial, keys))
end
def exists?(name, prefix = nil, partial = false, keys = [])
@view_paths.exists?(*args_for_lookup(name, prefix, partial, keys))
def exists?(name, prefixes = [], partial = false, keys = [])
@view_paths.exists?(*args_for_lookup(name, prefixes, partial, keys))
end
alias :template_exists? :exists?
@@ -107,18 +107,26 @@ module ActionView
protected
def args_for_lookup(name, prefix, partial, keys) #:nodoc:
name, prefix = normalize_name(name, prefix)
[name, prefix, partial || false, @details, details_key, keys]
def args_for_lookup(name, prefixes, partial, keys) #:nodoc:
name, prefixes = normalize_name(name, prefixes)
[name, prefixes, partial || false, @details, details_key, keys]
end
# Support legacy foo.erb names even though we now ignore .erb
# as well as incorrectly putting part of the path in the template
# name instead of the prefix.
def normalize_name(name, prefix) #:nodoc:
def normalize_name(name, prefixes) #:nodoc:
name = name.to_s.gsub(handlers_regexp, '')
parts = name.split('/')
return parts.pop, [prefix, *parts].compact.join("/")
name = parts.pop
prefixes = if prefixes.blank?
[parts.join('/')]
else
prefixes.map { |prefix| [prefix, *parts].compact.join('/') }
end
return name, prefixes
end
def default_handlers #:nodoc:
@@ -156,11 +164,11 @@ module ActionView
@frozen_formats = true
end
# Overload formats= to reject [:"*/*"] values.
# Overload formats= to reject ["*/*"] values.
def formats=(values)
if values && values.size == 1
value = values.first
values = nil if value == :"*/*"
values = nil if value == "*/*"
values << :html if value == :js
end
super(values)
@@ -178,11 +186,11 @@ module ActionView
end
# Overload locale= to also set the I18n.locale. If the current I18n.config object responds
# to i18n_config, it means that it's has a copy of the original I18n configuration and it's
# to original_config, it means that it's has a copy of the original I18n configuration and it's
# acting as proxy, which we need to skip.
def locale=(value)
if value
config = I18n.config.respond_to?(:i18n_config) ? I18n.config.i18n_config : I18n.config
config = I18n.config.respond_to?(:original_config) ? I18n.config.original_config : I18n.config
config.locale = value
end
@@ -237,4 +245,4 @@ module ActionView
include Details
include ViewPaths
end
end
end

View File

@@ -12,19 +12,19 @@ module ActionView
#
# <%= render :partial => "account" %>
#
# This would render "advertiser/_account.erb" and pass the instance variable @account in as a local variable
# This would render "advertiser/_account.html.erb" and pass the instance variable @account in as a local variable
# +account+ to the template for display.
#
# In another template for Advertiser#buy, we could have:
#
# <%= render :partial => "account", :locals => { :account => @buyer } %>
#
# <% for ad in @advertisements %>
# <% @advertisements.each do |ad| %>
# <%= render :partial => "ad", :locals => { :ad => ad } %>
# <% end %>
#
# This would first render "advertiser/_account.erb" with @buyer passed in as the local variable +account+, then
# render "advertiser/_ad.erb" and pass the local variable +ad+ to the template for display.
# This would first render "advertiser/_account.html.erb" with @buyer passed in as the local variable +account+, then
# render "advertiser/_ad.html.erb" and pass the local variable +ad+ to the template for display.
#
# == The :as and :object options
#
@@ -40,16 +40,16 @@ module ActionView
# With the <tt>:as</tt> option we can specify a different name for said local variable. For example, if we
# wanted it to be +agreement+ instead of +contract+ we'd do:
#
# <%= render :partial => "contract", :as => :agreement %>
# <%= render :partial => "contract", :as => 'agreement' %>
#
# The <tt>:object</tt> option can be used to directly specify which object is rendered into the partial;
# useful when the template's object is elsewhere, in a different ivar or in a local variable for instance.
#
#
# Revisiting a previous example we could have written this code:
#
#
# <%= render :partial => "account", :object => @buyer %>
#
# <% for ad in @advertisements %>
# <% @advertisements.each do |ad| %>
# <%= render :partial => "ad", :object => ad %>
# <% end %>
#
@@ -64,12 +64,12 @@ module ActionView
#
# <%= render :partial => "ad", :collection => @advertisements %>
#
# This will render "advertiser/_ad.erb" and pass the local variable +ad+ to the template for display. An
# This will render "advertiser/_ad.html.erb" and pass the local variable +ad+ to the template for display. An
# iteration counter will automatically be made available to the template with a name of the form
# +partial_name_counter+. In the case of the example above, the template would be fed +ad_counter+.
#
# The <tt>:as</tt> option may be used when rendering partials.
#
#
# You can specify a partial to be rendered between elements via the <tt>:spacer_template</tt> option.
# The following example will render <tt>advertiser/_ad_divider.html.erb</tt> between each ad partial:
#
@@ -89,7 +89,7 @@ module ActionView
#
# <%= render :partial => "advertisement/ad", :locals => { :ad => @advertisement } %>
#
# This will render the partial "advertisement/_ad.erb" regardless of which controller this is being called from.
# This will render the partial "advertisement/_ad.html.erb" regardless of which controller this is being called from.
#
# == Rendering objects with the RecordIdentifier
#

View File

@@ -11,13 +11,15 @@ module ActionView #:nodoc:
end
def find(*args)
find_all(*args).first || raise(MissingTemplate.new(self, "#{args[1]}/#{args[0]}", args[3], args[2]))
find_all(*args).first || raise(MissingTemplate.new(self, *args))
end
def find_all(*args)
each do |resolver|
templates = resolver.find_all(*args)
return templates unless templates.empty?
def find_all(path, prefixes = [], *args)
prefixes.each do |prefix|
each do |resolver|
templates = resolver.find_all(path, prefix, *args)
return templates unless templates.empty?
end
end
[]
end

View File

@@ -108,11 +108,11 @@ module ActionView
locals << @variable_counter if @collection
find_template(path, locals)
end
end
end
def find_template(path=@path, locals=@locals.keys)
prefix = @view.controller_prefix unless path.include?(?/)
@lookup_context.find_template(path, prefix, true, locals)
prefixes = path.include?(?/) ? [] : @view.controller_prefixes
@lookup_context.find_template(path, prefixes, true, locals)
end
def collection_with_template
@@ -152,14 +152,14 @@ module ActionView
object = object.to_model if object.respond_to?(:to_model)
object.class.model_name.partial_path.dup.tap do |partial|
path = @view.controller_prefix
path = @view.controller_prefixes.first
partial.insert(0, "#{File.dirname(path)}/") if partial.include?(?/) && path.include?(?/)
end
end
end
def retrieve_variable(path)
variable = @options[:as] || path[%r'_?(\w+)(\.\w+)*$', 1].to_sym
variable = @options[:as].try(:to_sym) || path[%r'_?(\w+)(\.\w+)*$', 1].to_sym
variable_counter = :"#{variable}_counter" if @collection
[variable, variable_counter]
end

View File

@@ -22,14 +22,14 @@ module ActionView
def render_once(options)
paths, locals = options[:once], options[:locals] || {}
layout, keys = options[:layout], locals.keys
prefix = options.fetch(:prefix, @view.controller_prefix)
prefixes = options.fetch(:prefixes, @view.controller_prefixes)
raise "render :once expects a String or an Array to be given" unless paths
render_with_layout(layout, locals) do
contents = []
Array.wrap(paths).each do |path|
template = find_template(path, prefix, false, keys)
template = find_template(path, prefixes, false, keys)
contents << render_template(template, nil, locals) if @rendered.add?(template)
end
contents.join("\n")
@@ -43,13 +43,13 @@ module ActionView
if options.key?(:text)
Template::Text.new(options[:text], formats.try(:first))
elsif options.key?(:file)
with_fallbacks { find_template(options[:file], options[:prefix], false, keys) }
with_fallbacks { find_template(options[:file], options[:prefixes], false, keys) }
elsif options.key?(:inline)
handler = Template.handler_for_extension(options[:type] || "erb")
Template.new(options[:inline], "inline template", handler, :locals => keys)
elsif options.key?(:template)
options[:template].respond_to?(:render) ?
options[:template] : find_template(options[:template], options[:prefix], false, keys)
options[:template] : find_template(options[:template], options[:prefixes], false, keys)
end
end

View File

@@ -123,7 +123,7 @@ module ActionView
@locals = details[:locals] || []
@virtual_path = details[:virtual_path]
@updated_at = details[:updated_at] || Time.now
@formats = Array.wrap(format).map(&:to_sym)
@formats = Array.wrap(format).map { |f| f.is_a?(Mime::Type) ? f.ref : f }
end
# Render a template. If the template was not compiled yet, it is done
@@ -163,7 +163,7 @@ module ActionView
name = pieces.pop
partial = !!name.sub!(/^_/, "")
lookup.disable_cache do
lookup.find_template(name, pieces.join('/'), partial, @locals)
lookup.find_template(name, [ pieces.join('/') ], partial, @locals)
end
end

View File

@@ -27,7 +27,7 @@ module ActionView
class MissingTemplate < ActionViewError #:nodoc:
attr_reader :path
def initialize(paths, path, details, partial)
def initialize(paths, path, prefixes, partial, details, *)
@path = path
display_paths = paths.compact.map{ |p| p.to_s.inspect }.join(", ")
template_type = if partial
@@ -38,7 +38,11 @@ module ActionView
'template'
end
super("Missing #{template_type} #{path} with #{details.inspect} in view paths #{display_paths}")
searched_paths = prefixes.map { |prefix| [prefix, path].join("/") }
out = "Missing #{template_type} #{searched_paths.join(", ")} with #{details.inspect}. Searched in:\n"
out += paths.compact.map { |p| " * #{p.to_s.inspect}\n" }.join
super out
end
end
@@ -52,6 +56,7 @@ module ActionView
attr_reader :original_exception, :backtrace
def initialize(template, assigns, original_exception)
super(original_exception.message)
@template, @assigns, @original_exception = template, assigns.dup, original_exception
@sub_templates = nil
@backtrace = original_exception.backtrace
@@ -61,10 +66,6 @@ module ActionView
@template.identifier
end
def message
ActiveSupport::Deprecation.silence { original_exception.message }
end
def sub_template_message
if @sub_templates
"Trace of template inclusion: " +

View File

@@ -47,7 +47,7 @@ module ActionView
path
end
# Hnadles templates caching. If a key is given and caching is on
# Handles templates caching. If a key is given and caching is on
# always check the cache before hitting the resolver. Otherwise,
# it always hits the resolver but check if the resolver is fresher
# before returning it.
@@ -109,18 +109,27 @@ module ActionView
def query(path, exts, formats)
query = File.join(@path, path)
exts.each do |ext|
query << '{' << ext.map {|e| e && ".#{e}" }.join(',') << ',}'
end
query << exts.map { |ext|
"{#{ext.compact.map { |e| ".#{e}" }.join(',')},}"
}.join
query.gsub!(/\{\.html,/, "{.html,.text.html,")
query.gsub!(/\{\.text,/, "{.text,.text.plain,")
templates = []
sanitizer = Hash.new { |h,k| h[k] = Dir["#{File.dirname(k)}/*"] }
Dir[query].each do |p|
next if File.directory?(p) || !sanitizer[p].include?(p)
Dir[query].reject { |p| File.directory?(p) }.map do |p|
handler, format = extract_handler_and_format(p, formats)
contents = File.open(p, "rb") {|io| io.read }
Template.new(contents, File.expand_path(p), handler,
templates << Template.new(contents, File.expand_path(p), handler,
:virtual_path => path, :format => format, :updated_at => mtime(p))
end
templates
end
# Returns the file mtime from the filesystem.

View File

@@ -156,10 +156,8 @@ module ActionView
# The instance of ActionView::Base that is used by +render+.
def view
@view ||= begin
view = ActionView::Base.new(ActionController::Base.view_paths, {}, @controller)
view = @controller.view_context
view.singleton_class.send :include, _helpers
view.singleton_class.send :include, @controller._routes.url_helpers
view.singleton_class.send :delegate, :alert, :notice, :to => "request.flash"
view.extend(Locals)
view.locals = self.locals
view.output_buffer = self.output_buffer
@@ -171,6 +169,7 @@ module ActionView
INTERNAL_IVARS = %w{
@__name__
@__io__
@_assertion_wrapped
@_assertions
@_result

View File

@@ -13,7 +13,11 @@ module ActionView #:nodoc:
@hash = hash
end
private
def to_s
@hash.keys.join(', ')
end
private
def query(path, exts, formats)
query = ""

View File

@@ -30,14 +30,14 @@ module AbstractController
class RenderingController < AbstractController::Base
include ::AbstractController::Rendering
def _prefix() end
def _prefixes
[]
end
def render(options = {})
if options.is_a?(String)
options = {:_template_name => options}
end
options[:_prefix] = _prefix
super
end
@@ -116,8 +116,8 @@ module AbstractController
name.underscore
end
def _prefix
self.class.prefix
def _prefixes
[self.class.prefix]
end
end
@@ -157,10 +157,10 @@ module AbstractController
private
def self.layout(formats)
begin
find_template(name.underscore, {:formats => formats}, :_prefix => "layouts")
find_template(name.underscore, {:formats => formats}, :_prefixes => ["layouts"])
rescue ActionView::MissingTemplate
begin
find_template("application", {:formats => formats}, :_prefix => "layouts")
find_template("application", {:formats => formats}, :_prefixes => ["layouts"])
rescue ActionView::MissingTemplate
end
end

View File

@@ -22,7 +22,7 @@ module AbstractController
class TestCallbacks1 < ActiveSupport::TestCase
test "basic callbacks work" do
controller = Callback1.new
result = controller.process(:index)
controller.process(:index)
assert_equal "Hello world", controller.response_body
end
end
@@ -62,7 +62,7 @@ module AbstractController
end
test "before_filter works" do
result = @controller.process(:index)
@controller.process(:index)
assert_equal "Hello world", @controller.response_body
end
@@ -78,7 +78,7 @@ module AbstractController
test "before_filter with overwritten condition" do
@controller = Callback2Overwrite.new
result = @controller.process(:index)
@controller.process(:index)
assert_equal "", @controller.response_body
end
end
@@ -103,12 +103,12 @@ module AbstractController
end
test "before_filter works with procs" do
result = @controller.process(:index)
@controller.process(:index)
assert_equal "Hello world", @controller.response_body
end
test "after_filter works with procs" do
result = @controller.process(:index)
@controller.process(:index)
assert_equal "Goodbye", @controller.instance_variable_get("@second")
end
end
@@ -152,7 +152,7 @@ module AbstractController
end
test "when :except is specified, an after filter is not triggered on that action" do
result = @controller.process(:index)
@controller.process(:index)
assert !@controller.instance_variable_defined?("@authenticated")
end
end
@@ -186,17 +186,17 @@ module AbstractController
end
test "when :only is specified with an array, a before filter is triggered on that action" do
result = @controller.process(:index)
@controller.process(:index)
assert_equal "Hello, World", @controller.response_body
end
test "when :only is specified with an array, a before filter is not triggered on other actions" do
result = @controller.process(:sekrit_data)
@controller.process(:sekrit_data)
assert_equal "true", @controller.response_body
end
test "when :except is specified with an array, an after filter is not triggered on that action" do
result = @controller.process(:index)
@controller.process(:index)
assert !@controller.instance_variable_defined?("@authenticated")
end
end
@@ -216,12 +216,12 @@ module AbstractController
end
test "when a callback is modified in a child with :only, it works for the :only action" do
result = @controller.process(:index)
@controller.process(:index)
assert_equal "Hello world", @controller.response_body
end
test "when a callback is modified in a child with :only, it does not work for other actions" do
result = @controller.process(:not_index)
@controller.process(:not_index)
assert_equal "", @controller.response_body
end
end
@@ -246,5 +246,26 @@ module AbstractController
end
end
class CallbacksWithArgs < ControllerWithCallbacks
set_callback :process_action, :before, :first
def first
@text = "Hello world"
end
def index(text)
self.response_body = @text + text
end
end
class TestCallbacksWithArgs < ActiveSupport::TestCase
test "callbacks still work when invoking process with multiple args" do
controller = CallbacksWithArgs.new
controller.process(:index, " Howdy!")
assert_equal "Hello world Howdy!", controller.response_body
end
end
end
end

View File

@@ -6,8 +6,8 @@ module AbstractController
class ControllerRenderer < AbstractController::Base
include AbstractController::Rendering
def _prefix
"renderer"
def _prefixes
%w[renderer]
end
self.view_paths = [ActionView::FixtureResolver.new(

View File

@@ -156,6 +156,17 @@ class PageCachingTest < ActionController::TestCase
assert_page_not_cached :ok
end
def test_page_caching_directory_set_as_pathname
begin
ActionController::Base.page_cache_directory = Pathname.new(FILE_STORE_PATH)
get :ok
assert_response :ok
assert_page_cached :ok
ensure
ActionController::Base.page_cache_directory = FILE_STORE_PATH
end
end
private
def assert_page_cached(action, message = "#{action} should have been cached")
assert page_cached?(action), message
@@ -257,7 +268,6 @@ class ActionCachingMockController
end
def request
mocked_path = @mock_path
Object.new.instance_eval(<<-EVAL)
def path; '#{@mock_path}' end
def format; 'all' end
@@ -416,7 +426,6 @@ class ActionCacheTest < ActionController::TestCase
get :index
assert_response :success
new_cached_time = content_to_cache
assert_not_equal cached_time, @response.body
end

View File

@@ -664,7 +664,7 @@ class FilterTest < ActionController::TestCase
end
def test_prepending_and_appending_around_filter
controller = test_process(MixedFilterController)
test_process(MixedFilterController)
assert_equal " before aroundfilter before procfilter before appended aroundfilter " +
" after appended aroundfilter after procfilter after aroundfilter ",
MixedFilterController.execution_log
@@ -677,26 +677,26 @@ class FilterTest < ActionController::TestCase
end
def test_before_filter_rendering_breaks_filtering_chain_for_after_filter
response = test_process(RenderingController)
test_process(RenderingController)
assert_equal %w( before_filter_rendering ), assigns["ran_filter"]
assert !assigns["ran_action"]
end
def test_before_filter_redirects_breaks_filtering_chain_for_after_filter
response = test_process(BeforeFilterRedirectionController)
test_process(BeforeFilterRedirectionController)
assert_response :redirect
assert_equal "http://test.host/filter_test/before_filter_redirection/target_of_redirection", redirect_to_url
assert_equal %w( before_filter_redirects ), assigns["ran_filter"]
end
def test_before_filter_rendering_breaks_filtering_chain_for_preprend_after_filter
response = test_process(RenderingForPrependAfterFilterController)
test_process(RenderingForPrependAfterFilterController)
assert_equal %w( before_filter_rendering ), assigns["ran_filter"]
assert !assigns["ran_action"]
end
def test_before_filter_redirects_breaks_filtering_chain_for_preprend_after_filter
response = test_process(BeforeFilterRedirectionForPrependAfterFilterController)
test_process(BeforeFilterRedirectionForPrependAfterFilterController)
assert_response :redirect
assert_equal "http://test.host/filter_test/before_filter_redirection_for_prepend_after_filter/target_of_redirection", redirect_to_url
assert_equal %w( before_filter_redirects ), assigns["ran_filter"]

View File

@@ -32,6 +32,11 @@ module Another
cache_page("Super soaker", "/index.html")
render :nothing => true
end
def with_exception
raise Exception
end
end
end
@@ -139,20 +144,20 @@ class ACLogSubscriberTest < ActionController::TestCase
wait
assert_equal 4, logs.size
assert_match(/Exist fragment\? views\/foo/, logs[1])
assert_match(/Read fragment views\/foo/, logs[1])
assert_match(/Write fragment views\/foo/, logs[2])
ensure
@controller.config.perform_caching = true
end
def test_with_fragment_cache_and_percent_in_key
@controller.config.perform_caching = true
get :with_fragment_cache_and_percent_in_key
wait
assert_equal 4, logs.size
assert_match(/Exist fragment\? views\/foo%bar/, logs[1])
assert_match(/Write fragment views\/foo%bar/, logs[2])
assert_match(/Read fragment views\/foo/, logs[1])
assert_match(/Write fragment views\/foo/, logs[2])
ensure
@controller.config.perform_caching = true
end
@@ -169,6 +174,16 @@ class ACLogSubscriberTest < ActionController::TestCase
@controller.config.perform_caching = true
end
def test_process_action_with_exception_includes_http_status_code
begin
get :with_exception
wait
rescue Exception
end
assert_equal 2, logs.size
assert_match(/Completed 500/, logs.last)
end
def logs
@logs ||= @logger.logged(:info)
end

View File

@@ -565,7 +565,7 @@ class RespondWithController < ActionController::Base
def using_resource_with_action
respond_with(resource, :action => :foo) do |format|
format.html { raise ActionView::MissingTemplate.new([], "foo/bar", {}, false) }
format.html { raise ActionView::MissingTemplate.new([], "bar", ["foo"], {}, false) }
end
end
@@ -658,7 +658,7 @@ class RespondWithControllerTest < ActionController::TestCase
@request.accept = "application/json"
get :using_hash_resource
assert_equal "application/json", @response.content_type
assert_equal %Q[{"result":["david",13]}], @response.body
assert_equal %Q[{"result":{"name":"david","id":13}}], @response.body
end
def test_using_resource_with_block

View File

@@ -35,7 +35,7 @@ module BareMetalTest
class HeadTest < ActiveSupport::TestCase
test "head works on its own" do
status, headers, body = HeadController.action(:index).call(Rack::MockRequest.env_for("/"))
status = HeadController.action(:index).call(Rack::MockRequest.env_for("/")).first
assert_equal 404, status
end
end

View File

@@ -7,6 +7,10 @@ module ContentNegotiation
self.view_paths = [ActionView::FixtureResolver.new(
"content_negotiation/basic/hello.html.erb" => "Hello world <%= request.formats.first.to_s %>!"
)]
def all
render :text => self.formats.inspect
end
end
class TestContentNegotiation < Rack::TestCase
@@ -14,5 +18,10 @@ module ContentNegotiation
get "/content_negotiation/basic/hello", {}, "HTTP_ACCEPT" => "*/*"
assert_body "Hello world */*!"
end
test "Not all mimes are converted to symbol" do
get "/content_negotiation/basic/all", {}, "HTTP_ACCEPT" => "text/plain, mime/another"
assert_body '[:text, "mime/another"]'
end
end
end

View File

@@ -18,8 +18,8 @@ module RenderTemplate
self.view_paths = [RESOLVER]
def _prefix
"test"
def _prefixes
%w(test)
end
def multiple
@@ -39,11 +39,11 @@ module RenderTemplate
end
def with_prefix
render :once => "result", :prefix => "other"
render :once => "result", :prefixes => %w(other)
end
def with_nil_prefix
render :once => "test/result", :prefix => nil
render :once => "test/result", :prefixes => []
end
end

View File

@@ -9,7 +9,10 @@ module RenderPartial
"render_partial/basic/basic.html.erb" => "<%= @test_unchanged = 'goodbye' %><%= render :partial => 'basic' %><%= @test_unchanged %>",
"render_partial/basic/with_json.html.erb" => "<%= render 'with_json.json' %>",
"render_partial/basic/_with_json.json.erb" => "<%= render 'final' %>",
"render_partial/basic/_final.json.erb" => "{ final: json }"
"render_partial/basic/_final.json.erb" => "{ final: json }",
"render_partial/basic/overriden.html.erb" => "<%= @test_unchanged = 'goodbye' %><%= render :partial => 'overriden' %><%= @test_unchanged %>",
"render_partial/basic/_overriden.html.erb" => "ParentPartial!",
"render_partial/child/_overriden.html.erb" => "OverridenPartial!"
)]
def html_with_json_inside_json
@@ -20,7 +23,13 @@ module RenderPartial
@test_unchanged = 'hello'
render :action => "basic"
end
def overriden
@test_unchanged = 'hello'
end
end
class ChildController < BasicController; end
class TestPartial < Rack::TestCase
testing BasicController
@@ -37,4 +46,18 @@ module RenderPartial
end
end
class TestInheritedPartial < Rack::TestCase
testing ChildController
test "partial from parent controller gets picked if missing in child one" do
get :changing
assert_response("goodbyeBasicPartial!goodbye")
end
test "partial from child controller gets picked" do
get :overriden
assert_response("goodbyeOverridenPartial!goodbye")
end
end
end

View File

@@ -6,7 +6,11 @@ module Render
"render/blank_render/index.html.erb" => "Hello world!",
"render/blank_render/access_request.html.erb" => "The request: <%= request.method.to_s.upcase %>",
"render/blank_render/access_action_name.html.erb" => "Action Name: <%= action_name %>",
"render/blank_render/access_controller_name.html.erb" => "Controller Name: <%= controller_name %>"
"render/blank_render/access_controller_name.html.erb" => "Controller Name: <%= controller_name %>",
"render/blank_render/overriden_with_own_view_paths_appended.html.erb" => "parent content",
"render/blank_render/overriden_with_own_view_paths_prepended.html.erb" => "parent content",
"render/blank_render/overriden.html.erb" => "parent content",
"render/child_render/overriden.html.erb" => "child content"
)]
def index
@@ -21,6 +25,15 @@ module Render
render :action => "access_action_name"
end
def overriden_with_own_view_paths_appended
end
def overriden_with_own_view_paths_prepended
end
def overriden
end
private
def secretz
@@ -35,6 +48,11 @@ module Render
end
end
class ChildRenderController < BlankRenderController
append_view_path ActionView::FixtureResolver.new("render/child_render/overriden_with_own_view_paths_appended.html.erb" => "child content")
prepend_view_path ActionView::FixtureResolver.new("render/child_render/overriden_with_own_view_paths_prepended.html.erb" => "child content")
end
class RenderTest < Rack::TestCase
test "render with blank" do
with_routing do |set|
@@ -94,4 +112,26 @@ module Render
assert_body "Controller Name: blank_render"
end
end
class TestViewInheritance < Rack::TestCase
test "Template from child controller gets picked over parent one" do
get "/render/child_render/overriden"
assert_body "child content"
end
test "Template from child controller with custom view_paths prepended gets picked over parent one" do
get "/render/child_render/overriden_with_own_view_paths_prepended"
assert_body "child content"
end
test "Template from child controller with custom view_paths appended gets picked over parent one" do
get "/render/child_render/overriden_with_own_view_paths_appended"
assert_body "child content"
end
test "Template from parent controller gets picked if missing in child controller" do
get "/render/child_render/index"
assert_body "Hello world!"
end
end
end

View File

@@ -26,6 +26,10 @@ class RenderJsonTest < ActionController::TestCase
render :json => nil
end
def render_json_render_to_string
render :text => render_to_string(:json => '[]')
end
def render_json_hello_world
render :json => ActiveSupport::JSON.encode(:hello => 'world')
end
@@ -76,6 +80,12 @@ class RenderJsonTest < ActionController::TestCase
assert_equal 'application/json', @response.content_type
end
def test_render_json_render_to_string
get :render_json_render_to_string
assert_equal '[]', @response.body
end
def test_render_json
get :render_json_hello_world
assert_equal '{"hello":"world"}', @response.body

View File

@@ -125,6 +125,10 @@ class TestController < ActionController::Base
render :action => "hello_world"
end
def render_action_upcased_hello_world
render :action => "Hello_world"
end
def render_action_hello_world_as_string
render "hello_world"
end
@@ -742,6 +746,12 @@ class RenderTest < ActionController::TestCase
assert_template "test/hello_world"
end
def test_render_action_upcased
assert_raise ActionView::MissingTemplate do
get :render_action_upcased_hello_world
end
end
# :ported:
def test_render_action_hello_world_as_string
get :render_action_hello_world_as_string

View File

@@ -29,8 +29,7 @@ class ActionController::TestRequestTest < ActiveSupport::TestCase
end
def test_session_id_different_on_each_call
prev_id =
assert_not_equal(@request.session_options[:id], ActionController::TestRequest.new.session_options[:id])
end
end
end

View File

@@ -12,6 +12,14 @@ module RequestForgeryProtectionActions
render :inline => "<%= button_to('New', '/') {} %>"
end
def external_form
render :inline => "<%= form_tag('http://farfar.away/form', :authenticity_token => 'external_token') {} %>"
end
def external_form_without_protection
render :inline => "<%= form_tag('http://farfar.away/form', :authenticity_token => false) {} %>"
end
def unsafe
render :text => 'pwn'
end
@@ -20,6 +28,14 @@ module RequestForgeryProtectionActions
render :inline => "<%= csrf_meta_tags %>"
end
def external_form_for
render :inline => "<%= form_for(:some_resource, :authenticity_token => 'external_token') {} %>"
end
def form_for_without_protection
render :inline => "<%= form_for(:some_resource, :authenticity_token => false ) {} %>"
end
def rescue_action(e) raise e end
end
@@ -29,6 +45,16 @@ class RequestForgeryProtectionController < ActionController::Base
protect_from_forgery :only => %w(index meta)
end
class RequestForgeryProtectionControllerUsingOldBehaviour < ActionController::Base
include RequestForgeryProtectionActions
protect_from_forgery :only => %w(index meta)
def handle_unverified_request
raise(ActionController::InvalidAuthenticityToken)
end
end
class FreeCookieController < RequestForgeryProtectionController
self.allow_forgery_protection = false
@@ -51,152 +77,92 @@ end
# common test methods
module RequestForgeryProtectionTests
def teardown
ActionController::Base.request_forgery_protection_token = nil
def setup
@token = "cf50faa3fe97702ca1ae"
ActiveSupport::SecureRandom.stubs(:base64).returns(@token)
ActionController::Base.request_forgery_protection_token = :authenticity_token
end
def test_should_render_form_with_token_tag
get :index
assert_not_blocked do
get :index
end
assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token
end
def test_should_render_button_to_with_token_tag
get :show_button
assert_not_blocked do
get :show_button
end
assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token
end
def test_should_allow_get
get :index
assert_response :success
assert_not_blocked { get :index }
end
def test_should_allow_post_without_token_on_unsafe_action
post :unsafe
assert_response :success
assert_not_blocked { post :unsafe }
end
def test_should_not_allow_html_post_without_token
@request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s
assert_raise(ActionController::InvalidAuthenticityToken) { post :index, :format => :html }
def test_should_not_allow_post_without_token
assert_blocked { post :index }
end
def test_should_not_allow_html_put_without_token
@request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s
assert_raise(ActionController::InvalidAuthenticityToken) { put :index, :format => :html }
def test_should_not_allow_post_without_token_irrespective_of_format
assert_blocked { post :index, :format=>'xml' }
end
def test_should_not_allow_html_delete_without_token
@request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s
assert_raise(ActionController::InvalidAuthenticityToken) { delete :index, :format => :html }
def test_should_not_allow_put_without_token
assert_blocked { put :index }
end
def test_should_allow_api_formatted_post_without_token
assert_nothing_raised do
post :index, :format => 'xml'
end
def test_should_not_allow_delete_without_token
assert_blocked { delete :index }
end
def test_should_not_allow_api_formatted_put_without_token
assert_nothing_raised do
put :index, :format => 'xml'
end
end
def test_should_allow_api_formatted_delete_without_token
assert_nothing_raised do
delete :index, :format => 'xml'
end
end
def test_should_not_allow_api_formatted_post_sent_as_url_encoded_form_without_token
assert_raise(ActionController::InvalidAuthenticityToken) do
@request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s
post :index, :format => 'xml'
end
end
def test_should_not_allow_api_formatted_put_sent_as_url_encoded_form_without_token
assert_raise(ActionController::InvalidAuthenticityToken) do
@request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s
put :index, :format => 'xml'
end
end
def test_should_not_allow_api_formatted_delete_sent_as_url_encoded_form_without_token
assert_raise(ActionController::InvalidAuthenticityToken) do
@request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s
delete :index, :format => 'xml'
end
end
def test_should_not_allow_api_formatted_post_sent_as_multipart_form_without_token
assert_raise(ActionController::InvalidAuthenticityToken) do
@request.env['CONTENT_TYPE'] = Mime::MULTIPART_FORM.to_s
post :index, :format => 'xml'
end
end
def test_should_not_allow_api_formatted_put_sent_as_multipart_form_without_token
assert_raise(ActionController::InvalidAuthenticityToken) do
@request.env['CONTENT_TYPE'] = Mime::MULTIPART_FORM.to_s
put :index, :format => 'xml'
end
end
def test_should_not_allow_api_formatted_delete_sent_as_multipart_form_without_token
assert_raise(ActionController::InvalidAuthenticityToken) do
@request.env['CONTENT_TYPE'] = Mime::MULTIPART_FORM.to_s
delete :index, :format => 'xml'
end
end
def test_should_allow_xhr_post_without_token
assert_nothing_raised { xhr :post, :index }
end
def test_should_allow_xhr_put_without_token
assert_nothing_raised { xhr :put, :index }
end
def test_should_allow_xhr_delete_without_token
assert_nothing_raised { xhr :delete, :index }
end
def test_should_allow_xhr_post_with_encoded_form_content_type_without_token
@request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s
assert_nothing_raised { xhr :post, :index }
def test_should_not_allow_xhr_post_without_token
assert_blocked { xhr :post, :index }
end
def test_should_allow_post_with_token
post :index, :authenticity_token => @token
assert_response :success
assert_not_blocked { post :index, :authenticity_token => @token }
end
def test_should_allow_put_with_token
put :index, :authenticity_token => @token
assert_response :success
assert_not_blocked { put :index, :authenticity_token => @token }
end
def test_should_allow_delete_with_token
delete :index, :authenticity_token => @token
assert_not_blocked { delete :index, :authenticity_token => @token }
end
def test_should_allow_post_with_token_in_header
@request.env['HTTP_X_CSRF_TOKEN'] = @token
assert_not_blocked { post :index }
end
def test_should_allow_delete_with_token_in_header
@request.env['HTTP_X_CSRF_TOKEN'] = @token
assert_not_blocked { delete :index }
end
def test_should_allow_put_with_token_in_header
@request.env['HTTP_X_CSRF_TOKEN'] = @token
assert_not_blocked { put :index }
end
def assert_blocked
session[:something_like_user_id] = 1
yield
assert_nil session[:something_like_user_id], "session values are still present"
assert_response :success
end
def test_should_allow_post_with_xml
@request.env['CONTENT_TYPE'] = Mime::XML.to_s
post :index, :format => 'xml'
assert_response :success
end
def test_should_allow_put_with_xml
@request.env['CONTENT_TYPE'] = Mime::XML.to_s
put :index, :format => 'xml'
assert_response :success
end
def test_should_allow_delete_with_xml
@request.env['CONTENT_TYPE'] = Mime::XML.to_s
delete :index, :format => 'xml'
def assert_not_blocked
assert_nothing_raised { yield }
assert_response :success
end
end
@@ -205,16 +171,6 @@ end
class RequestForgeryProtectionControllerTest < ActionController::TestCase
include RequestForgeryProtectionTests
def setup
@controller = RequestForgeryProtectionController.new
@request = ActionController::TestRequest.new
@request.format = :html
@response = ActionController::TestResponse.new
@token = "cf50faa3fe97702ca1ae"
ActiveSupport::SecureRandom.stubs(:base64).returns(@token)
ActionController::Base.request_forgery_protection_token = :authenticity_token
end
test 'should emit a csrf-token meta tag' do
ActiveSupport::SecureRandom.stubs(:base64).returns(@token + '<=?')
@@ -226,6 +182,15 @@ class RequestForgeryProtectionControllerTest < ActionController::TestCase
end
end
class RequestForgeryProtectionControllerUsingOldBehaviourTest < ActionController::TestCase
include RequestForgeryProtectionTests
def assert_blocked
assert_raises(ActionController::InvalidAuthenticityToken) do
yield
end
end
end
class FreeCookieControllerTest < ActionController::TestCase
def setup
@controller = FreeCookieController.new
@@ -258,13 +223,23 @@ class FreeCookieControllerTest < ActionController::TestCase
end
end
class CustomAuthenticityParamControllerTest < ActionController::TestCase
def setup
ActionController::Base.request_forgery_protection_token = :custom_token_name
super
end
def teardown
ActionController::Base.request_forgery_protection_token = :authenticity_token
super
end
def test_should_allow_custom_token
post :index, :authenticity_token => 'foobar'
post :index, :custom_token_name => 'foobar'
assert_response :ok
end
end

View File

@@ -701,7 +701,7 @@ class RouteSetTest < ActiveSupport::TestCase
set.draw do
match '/users/index' => 'users#index'
end
params = set.recognize_path('/users/index', :method => :get)
set.recognize_path('/users/index', :method => :get)
assert_equal 1, set.routes.size
end
@@ -980,7 +980,7 @@ class RouteSetTest < ActiveSupport::TestCase
match '/profile' => 'profile#index'
end
params = set.recognize_path("/profile") rescue nil
set.recognize_path("/profile") rescue nil
assert !Object.const_defined?("Profiler__"), "Profiler should not be loaded"
end

View File

@@ -95,16 +95,29 @@ module AbstractController
)
end
def test_protocol_with_and_without_separator
def test_protocol_with_and_without_separators
add_host!
assert_equal('https://www.basecamphq.com/c/a/i',
W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https')
)
assert_equal('https://www.basecamphq.com/c/a/i',
W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https:')
)
assert_equal('https://www.basecamphq.com/c/a/i',
W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https://')
)
end
def test_without_protocol
add_host!
assert_equal('//www.basecamphq.com/c/a/i',
W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => '//')
)
assert_equal('//www.basecamphq.com/c/a/i',
W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => false)
)
end
def test_trailing_slash
add_host!
options = {:controller => 'foo', :trailing_slash => true, :action => 'bar', :id => '33'}

View File

@@ -216,7 +216,7 @@ class WebServiceTest < ActionDispatch::IntegrationTest
def test_typecast_as_yaml
with_test_route_set do
with_params_parsers Mime::YAML => :yaml do
yaml = <<-YAML
yaml = (<<-YAML).strip
---
data:
a: 15

View File

@@ -29,14 +29,16 @@ class DispatcherTest < ActiveSupport::TestCase
assert_equal 4, Foo.b
end
def test_to_prepare_deprecation
prepared = false
assert_deprecated do
ActionDispatch::Callbacks.to_prepare { prepared = true }
end
def test_to_prepare_and_cleanup_delegation
prepared = cleaned = false
ActionDispatch::Callbacks.to_prepare { prepared = true }
ActionDispatch::Callbacks.to_prepare { cleaned = true }
ActionDispatch::Reloader.prepare!
assert prepared
ActionDispatch::Reloader.cleanup!
assert cleaned
end
private

View File

@@ -95,6 +95,26 @@ class CookiesTest < ActionController::TestCase
head :ok
end
def set_cookie_with_domain_and_tld
cookies[:user_name] = {:value => "rizwanreza", :domain => :all, :tld_length => 2}
head :ok
end
def delete_cookie_with_domain_and_tld
cookies.delete(:user_name, :domain => :all, :tld_length => 2)
head :ok
end
def set_cookie_with_domains
cookies[:user_name] = {:value => "rizwanreza", :domain => %w(example1.com example2.com .example3.com)}
head :ok
end
def delete_cookie_with_domains
cookies.delete(:user_name, :domain => %w(example1.com example2.com .example3.com))
head :ok
end
def symbol_key
cookies[:user_name] = "david"
head :ok
@@ -322,6 +342,67 @@ class CookiesTest < ActionController::TestCase
assert_cookie_header "user_name=; domain=.nextangle.com; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT"
end
def test_cookie_with_all_domain_option_and_tld_length
get :set_cookie_with_domain_and_tld
assert_response :success
assert_cookie_header "user_name=rizwanreza; domain=.nextangle.com; path=/"
end
def test_cookie_with_all_domain_option_using_a_non_standard_tld_and_tld_length
@request.host = "two.subdomains.nextangle.local"
get :set_cookie_with_domain_and_tld
assert_response :success
assert_cookie_header "user_name=rizwanreza; domain=.nextangle.local; path=/"
end
def test_cookie_with_all_domain_option_using_host_with_port_and_tld_length
@request.host = "nextangle.local:3000"
get :set_cookie_with_domain_and_tld
assert_response :success
assert_cookie_header "user_name=rizwanreza; domain=.nextangle.local; path=/"
end
def test_deleting_cookie_with_all_domain_option_and_tld_length
get :delete_cookie_with_domain_and_tld
assert_response :success
assert_cookie_header "user_name=; domain=.nextangle.com; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT"
end
def test_cookie_with_several_preset_domains_using_one_of_these_domains
@request.host = "example1.com"
get :set_cookie_with_domains
assert_response :success
assert_cookie_header "user_name=rizwanreza; domain=example1.com; path=/"
end
def test_cookie_with_several_preset_domains_using_other_domain
@request.host = "other-domain.com"
get :set_cookie_with_domains
assert_response :success
assert_cookie_header "user_name=rizwanreza; path=/"
end
def test_cookie_with_several_preset_domains_using_shared_domain
@request.host = "example3.com"
get :set_cookie_with_domains
assert_response :success
assert_cookie_header "user_name=rizwanreza; domain=.example3.com; path=/"
end
def test_deletings_cookie_with_several_preset_domains_using_one_of_these_domains
@request.host = "example2.com"
get :delete_cookie_with_domains
assert_response :success
assert_cookie_header "user_name=; domain=example2.com; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT"
end
def test_deletings_cookie_with_several_preset_domains_using_other_domain
@request.host = "other-domain.com"
get :delete_cookie_with_domains
assert_response :success
assert_cookie_header "user_name=; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT"
end
def test_cookies_hash_is_indifferent_access
[:symbol_key, :string_key].each do |cookie_key|
get cookie_key

View File

@@ -131,6 +131,13 @@ class MimeTypeTest < ActiveSupport::TestCase
assert unverified.each { |type| assert !Mime.const_get(type.to_s.upcase).verify_request?, "Nonverifiable Mime Type is verified: #{type.inspect}" }
end
test "references gives preference to symbols before strings" do
assert_equal :html, Mime::HTML.ref
another = Mime::Type.lookup("foo/bar")
assert_nil another.to_sym
assert_equal "foo/bar", another.ref
end
test "regexp matcher" do
assert Mime::JS =~ "text/javascript"
assert Mime::JS =~ "application/javascript"

View File

@@ -116,6 +116,20 @@ class ReloaderTest < Test::Unit::TestCase
assert cleaned
end
def test_cleanup_callbacks_are_called_on_exceptions
cleaned = false
Reloader.to_cleanup { cleaned = true }
begin
call_and_return_body do
raise "error"
end
rescue
end
assert cleaned
end
private
def call_and_return_body(&block)
@reloader ||= Reloader.new(block || proc {[200, {}, 'response']})

View File

@@ -427,7 +427,7 @@ class RequestTest < ActiveSupport::TestCase
begin
request = stub_request(mock_rack_env)
request.parameters
rescue TypeError => e
rescue TypeError
# rack will raise a TypeError when parsing this query string
end
assert_equal({}, request.parameters)

View File

@@ -18,7 +18,7 @@ class ResponseTest < ActiveSupport::TestCase
body.each { |part| parts << part }
assert_equal ["Hello, World!"], parts
end
test "status handled properly in initialize" do
assert_equal 200, ActionDispatch::Response.new('200 OK').status
end
@@ -26,7 +26,7 @@ class ResponseTest < ActiveSupport::TestCase
test "utf8 output" do
@response.body = [1090, 1077, 1089, 1090].pack("U*")
status, headers, body = @response.to_a
status, headers, _ = @response.to_a
assert_equal 200, status
assert_equal({
"Content-Type" => "text/html; charset=utf-8"
@@ -52,20 +52,20 @@ class ResponseTest < ActiveSupport::TestCase
test "content type" do
[204, 304].each do |c|
@response.status = c.to_s
status, headers, body = @response.to_a
_, headers, _ = @response.to_a
assert !headers.has_key?("Content-Type"), "#{c} should not have Content-Type header"
end
[200, 302, 404, 500].each do |c|
@response.status = c.to_s
status, headers, body = @response.to_a
_, headers, _ = @response.to_a
assert headers.has_key?("Content-Type"), "#{c} did not have Content-Type header"
end
end
test "does not include Status header" do
@response.status = "200 OK"
status, headers, body = @response.to_a
_, headers, _ = @response.to_a
assert !headers.has_key?('Status')
end

View File

@@ -0,0 +1,103 @@
require 'abstract_unit'
require 'controller/fake_controllers'
class SecureArticlesController < ArticlesController; end
class BlockArticlesController < ArticlesController; end
class RoutingAssertionsTest < ActionController::TestCase
def setup
@routes = ActionDispatch::Routing::RouteSet.new
@routes.draw do
resources :articles
scope 'secure', :constraints => { :protocol => 'https://' } do
resources :articles, :controller => 'secure_articles'
end
scope 'block', :constraints => lambda { |r| r.ssl? } do
resources :articles, :controller => 'block_articles'
end
end
end
def test_assert_generates
assert_generates('/articles', { :controller => 'articles', :action => 'index' })
assert_generates('/articles/1', { :controller => 'articles', :action => 'show', :id => '1' })
end
def test_assert_generates_with_defaults
assert_generates('/articles/1/edit', { :controller => 'articles', :action => 'edit' }, { :id => '1' })
end
def test_assert_generates_with_extras
assert_generates('/articles', { :controller => 'articles', :action => 'index', :page => '1' }, {}, { :page => '1' })
end
def test_assert_recognizes
assert_recognizes({ :controller => 'articles', :action => 'index' }, '/articles')
assert_recognizes({ :controller => 'articles', :action => 'show', :id => '1' }, '/articles/1')
end
def test_assert_recognizes_with_extras
assert_recognizes({ :controller => 'articles', :action => 'index', :page => '1' }, '/articles', { :page => '1' })
end
def test_assert_recognizes_with_method
assert_recognizes({ :controller => 'articles', :action => 'create' }, { :path => '/articles', :method => :post })
assert_recognizes({ :controller => 'articles', :action => 'update', :id => '1' }, { :path => '/articles/1', :method => :put })
end
def test_assert_recognizes_with_hash_constraint
assert_raise(ActionController::RoutingError) do
assert_recognizes({ :controller => 'secure_articles', :action => 'index' }, 'http://test.host/secure/articles')
end
assert_recognizes({ :controller => 'secure_articles', :action => 'index' }, 'https://test.host/secure/articles')
end
def test_assert_recognizes_with_block_constraint
assert_raise(ActionController::RoutingError) do
assert_recognizes({ :controller => 'block_articles', :action => 'index' }, 'http://test.host/block/articles')
end
assert_recognizes({ :controller => 'block_articles', :action => 'index' }, 'https://test.host/block/articles')
end
def test_assert_routing
assert_routing('/articles', :controller => 'articles', :action => 'index')
end
def test_assert_routing_with_defaults
assert_routing('/articles/1/edit', { :controller => 'articles', :action => 'edit', :id => '1' }, { :id => '1' })
end
def test_assert_routing_with_extras
assert_routing('/articles', { :controller => 'articles', :action => 'index', :page => '1' }, { }, { :page => '1' })
end
def test_assert_routing_with_hash_constraint
assert_raise(ActionController::RoutingError) do
assert_routing('http://test.host/secure/articles', { :controller => 'secure_articles', :action => 'index' })
end
assert_routing('https://test.host/secure/articles', { :controller => 'secure_articles', :action => 'index' })
end
def test_assert_routing_with_block_constraint
assert_raise(ActionController::RoutingError) do
assert_routing('http://test.host/block/articles', { :controller => 'block_articles', :action => 'index' })
end
assert_routing('https://test.host/block/articles', { :controller => 'block_articles', :action => 'index' })
end
def test_with_routing
with_routing do |routes|
routes.draw do
resources :articles, :path => 'artikel'
end
assert_routing('/artikel', :controller => 'articles', :action => 'index')
assert_raise(ActionController::RoutingError) do
assert_routing('/articles', { :controller => 'articles', :action => 'index' })
end
end
end
end

View File

@@ -187,7 +187,12 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
resources :posts, :only => [:index, :show] do
resources :comments, :except => :destroy
namespace :admin do
root :to => "index#index"
end
resources :comments, :except => :destroy do
get "views" => "comments#views", :as => :views
end
end
resource :past, :only => :destroy
@@ -205,6 +210,14 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
scope '/hello' do
shallow do
resources :notes do
resources :trackbacks
end
end
end
resources :threads, :shallow => true do
resource :owner
resources :messages do
@@ -1676,6 +1689,60 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
def test_shallow_nested_resources_within_scope
with_test_routes do
get '/hello/notes/1/trackbacks'
assert_equal 'trackbacks#index', @response.body
assert_equal '/hello/notes/1/trackbacks', note_trackbacks_path(:note_id => 1)
get '/hello/notes/1/edit'
assert_equal 'notes#edit', @response.body
assert_equal '/hello/notes/1/edit', edit_note_path(:id => '1')
get '/hello/notes/1/trackbacks/new'
assert_equal 'trackbacks#new', @response.body
assert_equal '/hello/notes/1/trackbacks/new', new_note_trackback_path(:note_id => 1)
get '/hello/trackbacks/1'
assert_equal 'trackbacks#show', @response.body
assert_equal '/hello/trackbacks/1', trackback_path(:id => '1')
get '/hello/trackbacks/1/edit'
assert_equal 'trackbacks#edit', @response.body
assert_equal '/hello/trackbacks/1/edit', edit_trackback_path(:id => '1')
put '/hello/trackbacks/1'
assert_equal 'trackbacks#update', @response.body
post '/hello/notes/1/trackbacks'
assert_equal 'trackbacks#create', @response.body
delete '/hello/trackbacks/1'
assert_equal 'trackbacks#destroy', @response.body
get '/hello/notes'
assert_equal 'notes#index', @response.body
post '/hello/notes'
assert_equal 'notes#create', @response.body
get '/hello/notes/new'
assert_equal 'notes#new', @response.body
assert_equal '/hello/notes/new', new_note_path
get '/hello/notes/1'
assert_equal 'notes#show', @response.body
assert_equal '/hello/notes/1', note_path(:id => 1)
put '/hello/notes/1'
assert_equal 'notes#update', @response.body
delete '/hello/notes/1'
assert_equal 'notes#destroy', @response.body
end
end
def test_custom_resource_routes_are_scoped
with_test_routes do
assert_equal '/customers/recent', recent_customers_path
@@ -2246,6 +2313,18 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
def test_nested_route_in_nested_resource
get "/posts/1/comments/2/views"
assert_equal "comments#views", @response.body
assert_equal "/posts/1/comments/2/views", post_comment_views_path(:post_id => '1', :comment_id => '2')
end
def test_root_in_deeply_nested_scope
get "/posts/1/admin"
assert_equal "admin/index#index", @response.body
assert_equal "/posts/1/admin", post_admin_root_path(:post_id => '1')
end
private
def with_test_routes
yield

View File

@@ -194,7 +194,6 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
with_test_route_set do
get '/set_session_value'
assert_response :success
session_payload = response.body
assert_equal "_myapp_session=#{response.body}; path=/; HttpOnly",
headers['Set-Cookie']

View File

@@ -0,0 +1,4 @@
Before
<%= render :partial => "test/partial.html.erb" %>
<%= yield %>
After

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