Merge branch 'master' of git://github.com/rails/rails
This commit is contained in:
commit
2bcc2ebf44
9
Gemfile
9
Gemfile
@ -7,14 +7,17 @@ gem "rails", "3.0.0.beta1"
|
||||
gem "rake", ">= 0.8.7"
|
||||
gem "mocha", ">= 0.9.8"
|
||||
|
||||
if RUBY_VERSION < '1.9'
|
||||
gem "ruby-debug", ">= 0.10.3"
|
||||
group :mri do
|
||||
if RUBY_VERSION < '1.9'
|
||||
gem "system_timer"
|
||||
gem "ruby-debug", ">= 0.10.3"
|
||||
end
|
||||
end
|
||||
|
||||
# AR
|
||||
gem "sqlite3-ruby", ">= 1.2.5", :require => 'sqlite3'
|
||||
|
||||
group :test do
|
||||
group :db do
|
||||
gem "pg", ">= 0.9.0"
|
||||
gem "mysql", ">= 2.8.1"
|
||||
end
|
||||
|
8
Rakefile
8
Rakefile
@ -49,13 +49,13 @@ end
|
||||
|
||||
desc "Install gems for all projects."
|
||||
task :install => :gem do
|
||||
require File.expand_path("../actionpack/lib/action_pack/version", __FILE__)
|
||||
version = File.read("RAILS_VERSION").strip
|
||||
(PROJECTS - ["railties"]).each do |project|
|
||||
puts "INSTALLING #{project}"
|
||||
system("gem install #{project}/pkg/#{project}-#{ActionPack::VERSION::STRING}.gem --no-ri --no-rdoc")
|
||||
system("gem install #{project}/pkg/#{project}-#{version}.gem --no-ri --no-rdoc")
|
||||
end
|
||||
system("gem install railties/pkg/railties-#{ActionPack::VERSION::STRING}.gem --no-ri --no-rdoc")
|
||||
system("gem install pkg/rails-#{ActionPack::VERSION::STRING}.gem --no-ri --no-rdoc")
|
||||
system("gem install railties/pkg/railties-#{version}.gem --no-ri --no-rdoc")
|
||||
system("gem install pkg/rails-#{version}.gem --no-ri --no-rdoc")
|
||||
end
|
||||
|
||||
desc "Generate documentation for the Rails framework"
|
||||
|
@ -1,6 +1,7 @@
|
||||
require 'mail'
|
||||
require 'action_mailer/tmail_compat'
|
||||
require 'action_mailer/collector'
|
||||
require 'active_support/core_ext/array/wrap'
|
||||
|
||||
module ActionMailer #:nodoc:
|
||||
# Action Mailer allows you to send email from your application using a mailer model and views.
|
||||
@ -590,7 +591,7 @@ def collect_responses_and_parts_order(headers) #:nodoc:
|
||||
responses, parts_order = [], nil
|
||||
|
||||
if block_given?
|
||||
collector = ActionMailer::Collector.new(self) { render(action_name) }
|
||||
collector = ActionMailer::Collector.new(lookup_context) { render(action_name) }
|
||||
yield(collector)
|
||||
parts_order = collector.responses.map { |r| r[:content_type] }
|
||||
responses = collector.responses
|
||||
@ -604,8 +605,10 @@ def collect_responses_and_parts_order(headers) #:nodoc:
|
||||
templates_name = headers.delete(:template_name) || action_name
|
||||
|
||||
each_template(templates_path, templates_name) do |template|
|
||||
self.formats = template.formats
|
||||
|
||||
responses << {
|
||||
:body => render(:_template => template),
|
||||
:body => render(:template => template),
|
||||
:content_type => template.mime_type.to_s
|
||||
}
|
||||
end
|
||||
@ -615,7 +618,7 @@ def collect_responses_and_parts_order(headers) #:nodoc:
|
||||
end
|
||||
|
||||
def each_template(paths, name, &block) #:nodoc:
|
||||
Array(paths).each do |path|
|
||||
Array.wrap(paths).each do |path|
|
||||
templates = lookup_context.find_all(name, path)
|
||||
templates = templates.uniq_by { |t| t.formats }
|
||||
|
||||
|
@ -11,7 +11,6 @@ def initialize(context, &block)
|
||||
@context = context
|
||||
@responses = []
|
||||
@default_render = block
|
||||
@default_formats = context.formats
|
||||
end
|
||||
|
||||
def any(*args, &block)
|
||||
@ -21,16 +20,11 @@ def any(*args, &block)
|
||||
end
|
||||
alias :all :any
|
||||
|
||||
def custom(mime, options={}, &block)
|
||||
def custom(mime, options={})
|
||||
options.reverse_merge!(:content_type => mime.to_s)
|
||||
@context.formats = [mime.to_sym]
|
||||
options[:body] = if block
|
||||
block.call
|
||||
else
|
||||
@default_render.call
|
||||
end
|
||||
@context.freeze_formats([mime.to_sym])
|
||||
options[:body] = block_given? ? yield : @default_render.call
|
||||
@responses << options
|
||||
@context.formats = @default_formats
|
||||
end
|
||||
end
|
||||
end
|
@ -31,9 +31,6 @@ module OldApi #:nodoc:
|
||||
# replies to this message.
|
||||
adv_attr_accessor :reply_to
|
||||
|
||||
# Specify additional headers to be added to the message.
|
||||
adv_attr_accessor :headers
|
||||
|
||||
# Specify the order in which parts should be sorted, based on content-type.
|
||||
# This defaults to the value for the +default_implicit_parts_order+.
|
||||
adv_attr_accessor :implicit_parts_order
|
||||
@ -207,7 +204,8 @@ def create_parts
|
||||
@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|
|
||||
@parts << create_inline_part(render(:_template => template), template.mime_type)
|
||||
self.formats = template.formats
|
||||
@parts << create_inline_part(render(:template => template), template.mime_type)
|
||||
end
|
||||
|
||||
if @parts.size > 1
|
||||
|
@ -3,14 +3,14 @@
|
||||
|
||||
module ActionMailer
|
||||
class Railtie < Rails::Railtie
|
||||
railtie_name :action_mailer
|
||||
config.action_mailer = ActiveSupport::OrderedOptions.new
|
||||
|
||||
initializer "action_mailer.url_for", :before => :load_environment_config do |app|
|
||||
ActionMailer.base_hook { include app.routes.url_helpers }
|
||||
end
|
||||
|
||||
require "action_mailer/railties/log_subscriber"
|
||||
log_subscriber ActionMailer::Railties::LogSubscriber.new
|
||||
log_subscriber :action_mailer, ActionMailer::Railties::LogSubscriber.new
|
||||
|
||||
initializer "action_mailer.logger" do
|
||||
ActionMailer.base_hook { self.logger ||= Rails.logger }
|
||||
|
@ -1,8 +1,10 @@
|
||||
require 'active_support/core_ext/array/wrap'
|
||||
|
||||
module ActionMailer
|
||||
module Railties
|
||||
class LogSubscriber < Rails::LogSubscriber
|
||||
def deliver(event)
|
||||
recipients = Array(event.payload[:to]).join(', ')
|
||||
recipients = Array.wrap(event.payload[:to]).join(', ')
|
||||
info("\nSent mail to #{recipients} (%1.fms)" % event.duration)
|
||||
debug(event.payload[:mail])
|
||||
end
|
||||
@ -17,4 +19,4 @@ def logger
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1,4 +1,23 @@
|
||||
require File.expand_path('../../../load_paths', __FILE__)
|
||||
# Pathname has a warning, so require it first while silencing
|
||||
# warnings to shut it up.
|
||||
#
|
||||
# Also, in 1.9, Bundler creates warnings due to overriding
|
||||
# Rubygems methods
|
||||
begin
|
||||
old, $VERBOSE = $VERBOSE, nil
|
||||
require 'pathname'
|
||||
require File.expand_path('../../../load_paths', __FILE__)
|
||||
ensure
|
||||
$VERBOSE = old
|
||||
end
|
||||
|
||||
|
||||
require 'active_support/core_ext/kernel/reporting'
|
||||
silence_warnings do
|
||||
# These external dependencies have warnings :/
|
||||
require 'text/format'
|
||||
require 'mail'
|
||||
end
|
||||
|
||||
lib = File.expand_path("#{File.dirname(__FILE__)}/../lib")
|
||||
$:.unshift(lib) unless $:.include?('lib') || $:.include?(lib)
|
||||
|
@ -2,6 +2,8 @@
|
||||
require 'abstract_unit'
|
||||
|
||||
class BaseTest < ActiveSupport::TestCase
|
||||
# TODO Add some tests for implicity layout render and url helpers
|
||||
# so we can get rid of old base tests altogether with old base.
|
||||
class BaseMailer < ActionMailer::Base
|
||||
self.mailer_name = "base_mailer"
|
||||
|
||||
@ -72,13 +74,20 @@ def explicit_multipart_with_any(hash = {})
|
||||
end
|
||||
end
|
||||
|
||||
def custom_block(include_html=false)
|
||||
def explicit_multipart_with_options(include_html = false)
|
||||
mail do |format|
|
||||
format.text(:content_transfer_encoding => "base64"){ render "welcome" }
|
||||
format.html{ render "welcome" } if include_html
|
||||
end
|
||||
end
|
||||
|
||||
def explicit_multipart_with_one_template(hash = {})
|
||||
mail(hash) do |format|
|
||||
format.html
|
||||
format.text
|
||||
end
|
||||
end
|
||||
|
||||
def implicit_different_template(template_name='')
|
||||
mail(:template_name => template_name)
|
||||
end
|
||||
@ -146,6 +155,13 @@ def different_layout(layout_name='')
|
||||
assert_equal("Hello there", email.body.encoded)
|
||||
end
|
||||
|
||||
test "should set template content type if mail has only one part" do
|
||||
mail = BaseMailer.html_only
|
||||
assert_equal('text/html', mail.mime_type)
|
||||
mail = BaseMailer.plain_text_only
|
||||
assert_equal('text/plain', mail.mime_type)
|
||||
end
|
||||
|
||||
# Custom headers
|
||||
test "custom headers" do
|
||||
email = BaseMailer.welcome
|
||||
@ -160,7 +176,7 @@ def different_layout(layout_name='')
|
||||
assert_equal('1234@mikel.me.com', mail['In-Reply-To'].decoded)
|
||||
end
|
||||
|
||||
test "can pass random headers in as a hash" do
|
||||
test "can pass random headers in as a hash to headers" do
|
||||
hash = {'X-Special-Domain-Specific-Header' => "SecretValue",
|
||||
'In-Reply-To' => '1234@mikel.me.com' }
|
||||
mail = BaseMailer.welcome_with_headers(hash)
|
||||
@ -364,6 +380,11 @@ def different_layout(layout_name='')
|
||||
assert_equal("HTML Explicit Multipart", email.parts[1].body.encoded)
|
||||
end
|
||||
|
||||
test "explicit multipart have a boundary" do
|
||||
mail = BaseMailer.explicit_multipart
|
||||
assert_not_nil(mail.content_type_parameters[:boundary])
|
||||
end
|
||||
|
||||
test "explicit multipart does not sort order" do
|
||||
order = ["text/html", "text/plain"]
|
||||
with_default BaseMailer, :parts_order => order do
|
||||
@ -397,7 +418,7 @@ def different_layout(layout_name='')
|
||||
assert_equal("TEXT Explicit Multipart Templates", email.parts[1].body.encoded)
|
||||
end
|
||||
|
||||
test "explicit multipart with any" do
|
||||
test "explicit multipart with format.any" do
|
||||
email = BaseMailer.explicit_multipart_with_any
|
||||
assert_equal(2, email.parts.size)
|
||||
assert_equal("multipart/alternative", email.mime_type)
|
||||
@ -407,8 +428,8 @@ def different_layout(layout_name='')
|
||||
assert_equal("Format with any!", email.parts[1].body.encoded)
|
||||
end
|
||||
|
||||
test "explicit multipart with options" do
|
||||
email = BaseMailer.custom_block(true)
|
||||
test "explicit multipart with format(Hash)" do
|
||||
email = BaseMailer.explicit_multipart_with_options(true)
|
||||
email.ready_to_send!
|
||||
assert_equal(2, email.parts.size)
|
||||
assert_equal("multipart/alternative", email.mime_type)
|
||||
@ -418,28 +439,23 @@ def different_layout(layout_name='')
|
||||
assert_equal("7bit", email.parts[1].content_transfer_encoding)
|
||||
end
|
||||
|
||||
test "explicit multipart should be multipart" do
|
||||
mail = BaseMailer.explicit_multipart
|
||||
assert_not_nil(mail.content_type_parameters[:boundary])
|
||||
end
|
||||
|
||||
test "should set a content type if only has an html part" do
|
||||
mail = BaseMailer.html_only
|
||||
assert_equal('text/html', mail.mime_type)
|
||||
end
|
||||
|
||||
test "should set a content type if only has an plain text part" do
|
||||
mail = BaseMailer.plain_text_only
|
||||
assert_equal('text/plain', mail.mime_type)
|
||||
end
|
||||
|
||||
test "explicit multipart with one part is rendered as body" do
|
||||
email = BaseMailer.custom_block
|
||||
test "explicit multipart with one part is rendered as body and options are merged" do
|
||||
email = BaseMailer.explicit_multipart_with_options
|
||||
assert_equal(0, email.parts.size)
|
||||
assert_equal("text/plain", email.mime_type)
|
||||
assert_equal("base64", email.content_transfer_encoding)
|
||||
end
|
||||
|
||||
test "explicit multipart with one template has the expected format" do
|
||||
email = BaseMailer.explicit_multipart_with_one_template
|
||||
assert_equal(2, email.parts.size)
|
||||
assert_equal("multipart/alternative", email.mime_type)
|
||||
assert_equal("text/html", email.parts[0].mime_type)
|
||||
assert_equal("[:html]", email.parts[0].body.encoded)
|
||||
assert_equal("text/plain", email.parts[1].mime_type)
|
||||
assert_equal("[:text]", email.parts[1].body.encoded)
|
||||
end
|
||||
|
||||
# Class level API with method missing
|
||||
test "should respond to action methods" do
|
||||
assert BaseMailer.respond_to?(:welcome)
|
||||
|
1
actionmailer/test/fixtures/base_mailer/explicit_multipart_with_one_template.erb
vendored
Normal file
1
actionmailer/test/fixtures/base_mailer/explicit_multipart_with_one_template.erb
vendored
Normal file
@ -0,0 +1 @@
|
||||
<%= self.formats.inspect %>
|
@ -69,47 +69,25 @@ def test_should_pickup_default_layout
|
||||
|
||||
def test_should_pickup_multipart_layout
|
||||
mail = AutoLayoutMailer.multipart
|
||||
# CHANGED: content_type returns an object
|
||||
# assert_equal "multipart/alternative", mail.content_type
|
||||
assert_equal "multipart/alternative", mail.mime_type
|
||||
assert_equal 2, mail.parts.size
|
||||
|
||||
# CHANGED: content_type returns an object
|
||||
# assert_equal 'text/plain', mail.parts.first.content_type
|
||||
assert_equal 'text/plain', mail.parts.first.mime_type
|
||||
|
||||
# CHANGED: body returns an object
|
||||
# assert_equal "text/plain layout - text/plain multipart", mail.parts.first.body
|
||||
assert_equal "text/plain layout - text/plain multipart", mail.parts.first.body.to_s
|
||||
|
||||
# CHANGED: content_type returns an object
|
||||
# assert_equal 'text/html', mail.parts.last.content_type
|
||||
assert_equal 'text/html', mail.parts.last.mime_type
|
||||
|
||||
# CHANGED: body returns an object
|
||||
# assert_equal "Hello from layout text/html multipart", mail.parts.last.body
|
||||
assert_equal "Hello from layout text/html multipart", mail.parts.last.body.to_s
|
||||
end
|
||||
|
||||
def test_should_pickup_multipartmixed_layout
|
||||
mail = AutoLayoutMailer.multipart("multipart/mixed")
|
||||
# CHANGED: content_type returns an object
|
||||
# assert_equal "multipart/mixed", mail.content_type
|
||||
assert_equal "multipart/mixed", mail.mime_type
|
||||
assert_equal 2, mail.parts.size
|
||||
|
||||
# CHANGED: content_type returns an object
|
||||
# assert_equal 'text/plain', mail.parts.first.content_type
|
||||
assert_equal 'text/plain', mail.parts.first.mime_type
|
||||
# CHANGED: body returns an object
|
||||
# assert_equal "text/plain layout - text/plain multipart", mail.parts.first.body
|
||||
assert_equal "text/plain layout - text/plain multipart", mail.parts.first.body.to_s
|
||||
|
||||
# CHANGED: content_type returns an object
|
||||
# assert_equal 'text/html', mail.parts.last.content_type
|
||||
assert_equal 'text/html', mail.parts.last.mime_type
|
||||
# CHANGED: body returns an object
|
||||
# assert_equal "Hello from layout text/html multipart", mail.parts.last.body
|
||||
assert_equal "Hello from layout text/html multipart", mail.parts.last.body.to_s
|
||||
end
|
||||
|
||||
|
@ -340,6 +340,8 @@ def setup
|
||||
|
||||
@original_logger = TestMailer.logger
|
||||
@recipient = 'test@localhost'
|
||||
|
||||
TestMailer.delivery_method = :test
|
||||
end
|
||||
|
||||
def teardown
|
||||
@ -444,9 +446,6 @@ def test_custom_templating_extension
|
||||
expected.from = "system@loudthinking.com"
|
||||
expected.date = Time.local(2004, 12, 12)
|
||||
|
||||
# Stub the render method so no alternative renderers need be present.
|
||||
ActionView::Base.any_instance.stubs(:render).returns("Hello there, \n\nMr. #{@recipient}")
|
||||
|
||||
# Now that the template is registered, there should be one part. The text/plain part.
|
||||
created = nil
|
||||
assert_nothing_raised { created = TestMailer.custom_templating_extension(@recipient) }
|
||||
@ -1194,6 +1193,6 @@ def test_should_still_raise_exception_with_expected_message_when_calling_an_unde
|
||||
RespondToMailer.not_a_method
|
||||
end
|
||||
|
||||
assert_match(/undefined method.*not_a_method/, error.message)
|
||||
assert_match(/method.*not_a_method/, error.message)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -10,7 +10,7 @@ class ActionMailer::Base
|
||||
include AppRoutes.url_helpers
|
||||
end
|
||||
|
||||
class TestMailer < ActionMailer::Base
|
||||
class UrlTestMailer < ActionMailer::Base
|
||||
default_url_options[:host] = 'www.basecamphq.com'
|
||||
|
||||
configure do |c|
|
||||
@ -26,14 +26,6 @@ def signed_up_with_url(recipient)
|
||||
@recipient = recipient
|
||||
@welcome_url = url_for :host => "example.com", :controller => "welcome", :action => "greeting"
|
||||
end
|
||||
|
||||
class <<self
|
||||
attr_accessor :received_body
|
||||
end
|
||||
|
||||
def receive(mail)
|
||||
self.class.received_body = mail.body
|
||||
end
|
||||
end
|
||||
|
||||
class ActionMailerUrlTest < Test::Unit::TestCase
|
||||
@ -65,7 +57,7 @@ def teardown
|
||||
end
|
||||
|
||||
def test_signed_up_with_url
|
||||
TestMailer.delivery_method = :test
|
||||
UrlTestMailer.delivery_method = :test
|
||||
|
||||
AppRoutes.draw do |map|
|
||||
map.connect ':controller/:action/:id'
|
||||
@ -80,14 +72,14 @@ def test_signed_up_with_url
|
||||
expected.date = Time.local(2004, 12, 12)
|
||||
|
||||
created = nil
|
||||
assert_nothing_raised { created = TestMailer.signed_up_with_url(@recipient) }
|
||||
assert_nothing_raised { created = UrlTestMailer.signed_up_with_url(@recipient) }
|
||||
assert_not_nil created
|
||||
|
||||
expected.message_id = '<123@456>'
|
||||
created.message_id = '<123@456>'
|
||||
assert_equal expected.encoded, created.encoded
|
||||
|
||||
assert_nothing_raised { TestMailer.signed_up_with_url(@recipient).deliver }
|
||||
assert_nothing_raised { UrlTestMailer.signed_up_with_url(@recipient).deliver }
|
||||
assert_not_nil ActionMailer::Base.deliveries.first
|
||||
delivered = ActionMailer::Base.deliveries.first
|
||||
|
||||
|
@ -12,6 +12,7 @@
|
||||
module AbstractController
|
||||
extend ActiveSupport::Autoload
|
||||
|
||||
autoload :Assigns
|
||||
autoload :Base
|
||||
autoload :Callbacks
|
||||
autoload :Collector
|
||||
|
21
actionpack/lib/abstract_controller/assigns.rb
Normal file
21
actionpack/lib/abstract_controller/assigns.rb
Normal file
@ -0,0 +1,21 @@
|
||||
module AbstractController
|
||||
module Assigns
|
||||
# This method should return a hash with assigns.
|
||||
# You can overwrite this configuration per controller.
|
||||
# :api: public
|
||||
def view_assigns
|
||||
hash = {}
|
||||
variables = instance_variable_names
|
||||
variables -= protected_instance_variables if respond_to?(:protected_instance_variables)
|
||||
variables.each { |name| hash[name] = instance_variable_get(name) }
|
||||
hash
|
||||
end
|
||||
|
||||
# This method assigns the hash specified in _assigns_hash to the given object.
|
||||
# :api: private
|
||||
# TODO Ideally, this should be done on AV::Base.new initialization.
|
||||
def _evaluate_assigns(object)
|
||||
view_assigns.each { |k,v| object.instance_variable_set(k, v) }
|
||||
end
|
||||
end
|
||||
end
|
@ -6,16 +6,10 @@ module Helpers
|
||||
|
||||
include Rendering
|
||||
|
||||
def self.next_serial
|
||||
@helper_serial ||= 0
|
||||
@helper_serial += 1
|
||||
end
|
||||
|
||||
included do
|
||||
class_attribute :_helpers, :_helper_serial
|
||||
class_attribute :_helpers
|
||||
delegate :_helpers, :to => :'self.class'
|
||||
self._helpers = Module.new
|
||||
self._helper_serial = ::AbstractController::Helpers.next_serial
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
@ -95,8 +89,6 @@ def #{meth}(*args, &blk)
|
||||
# helper(:three, BlindHelper) { def mice() 'mice' end }
|
||||
#
|
||||
def helper(*args, &block)
|
||||
self._helper_serial = AbstractController::Helpers.next_serial + 1
|
||||
|
||||
modules_for_helpers(args).each do |mod|
|
||||
add_template_helper(mod)
|
||||
end
|
||||
|
@ -1,3 +1,5 @@
|
||||
require "active_support/core_ext/module/remove_method"
|
||||
|
||||
module AbstractController
|
||||
# Layouts reverse the common pattern of including shared headers and footers in many templates to isolate changes in
|
||||
# repeated setups. The inclusion pattern has pages that look like this:
|
||||
@ -182,7 +184,9 @@ module LayoutConditions
|
||||
#
|
||||
# ==== Returns
|
||||
# Boolean:: True if the action has a layout, false otherwise.
|
||||
def _action_has_layout?
|
||||
def action_has_layout?
|
||||
return unless super
|
||||
|
||||
conditions = _layout_conditions
|
||||
|
||||
if only = conditions[:only]
|
||||
@ -237,6 +241,8 @@ def _implied_layout_name
|
||||
# name, return that string. Otherwise, use the superclass'
|
||||
# layout (which might also be implied)
|
||||
def _write_layout_method
|
||||
remove_possible_method(:_layout)
|
||||
|
||||
case defined?(@_layout) ? @_layout : nil
|
||||
when String
|
||||
self.class_eval %{def _layout; #{@_layout.inspect} end}
|
||||
@ -287,6 +293,17 @@ def _normalize_options(options)
|
||||
end
|
||||
end
|
||||
|
||||
attr_writer :action_has_layout
|
||||
|
||||
def initialize(*)
|
||||
@action_has_layout = true
|
||||
super
|
||||
end
|
||||
|
||||
def action_has_layout?
|
||||
@action_has_layout
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# This will be overwritten by _write_layout_method
|
||||
@ -326,13 +343,13 @@ def _layout_for_option(name)
|
||||
# Template:: The template object for the default layout (or nil)
|
||||
def _default_layout(require_layout = false)
|
||||
begin
|
||||
layout_name = _layout if _action_has_layout?
|
||||
layout_name = _layout if action_has_layout?
|
||||
rescue NameError => e
|
||||
raise NoMethodError,
|
||||
"You specified #{@_layout.inspect} as the layout, but no such method was found"
|
||||
end
|
||||
|
||||
if require_layout && _action_has_layout? && !layout_name
|
||||
if require_layout && action_has_layout? && !layout_name
|
||||
raise ArgumentError,
|
||||
"There was no default layout for #{self.class} in #{view_paths.inspect}"
|
||||
end
|
||||
@ -343,9 +360,5 @@ def _default_layout(require_layout = false)
|
||||
def _include_layout?(options)
|
||||
(options.keys & [:text, :inline, :partial]).empty? || options.key?(:layout)
|
||||
end
|
||||
|
||||
def _action_has_layout?
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -31,6 +31,7 @@ def locale=(value)
|
||||
|
||||
module Rendering
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
include AbstractController::ViewPaths
|
||||
|
||||
# Overwrite process to setup I18n proxy.
|
||||
@ -41,21 +42,52 @@ def process(*) #:nodoc:
|
||||
I18n.config = old_config
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
def view_context_class
|
||||
@view_context_class ||= begin
|
||||
controller = self
|
||||
Class.new(ActionView::Base) do
|
||||
if controller.respond_to?(:_helpers)
|
||||
include controller._helpers
|
||||
|
||||
if controller.respond_to?(:_router)
|
||||
include controller._router.url_helpers
|
||||
end
|
||||
|
||||
# TODO: Fix RJS to not require this
|
||||
self.helpers = controller._helpers
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
attr_writer :view_context_class
|
||||
|
||||
def view_context_class
|
||||
@view_context_class || self.class.view_context_class
|
||||
end
|
||||
|
||||
def initialize(*)
|
||||
@view_context_class = nil
|
||||
super
|
||||
end
|
||||
|
||||
# An instance of a view class. The default view class is ActionView::Base
|
||||
#
|
||||
# The view class must have the following methods:
|
||||
# View.for_controller[controller]
|
||||
# View.new[lookup_context, assigns, controller]
|
||||
# Create a new ActionView instance for a controller
|
||||
# View#render_template[options]
|
||||
# View#render[options]
|
||||
# Returns String with the rendered template
|
||||
#
|
||||
# Override this method in a module to change the default behavior.
|
||||
def view_context
|
||||
@_view_context ||= ActionView::Base.for_controller(self)
|
||||
view_context_class.new(lookup_context, view_assigns, self)
|
||||
end
|
||||
|
||||
# Mostly abstracts the fact that calling render twice is a DoubleRenderError.
|
||||
# Delegates render_to_body and sticks the result in self.response_body.
|
||||
# Normalize arguments, options and then delegates render_to_body and
|
||||
# sticks the result in self.response_body.
|
||||
def render(*args, &block)
|
||||
options = _normalize_args(*args, &block)
|
||||
_normalize_options(options)
|
||||
@ -74,12 +106,13 @@ def render_to_body(options = {})
|
||||
# :api: plugin
|
||||
def render_to_string(options={})
|
||||
_normalize_options(options)
|
||||
AbstractController::Rendering.body_to_s(render_to_body(options))
|
||||
render_to_body(options)
|
||||
end
|
||||
|
||||
# Find and renders a template based on the options given.
|
||||
def _render_template(options)
|
||||
view_context.render_template(options) { |template| _with_template_hook(template) }
|
||||
# :api: private
|
||||
def _render_template(options) #:nodoc:
|
||||
view_context.render(options)
|
||||
end
|
||||
|
||||
# The prefix used in render "foo" shortcuts.
|
||||
@ -87,20 +120,19 @@ def _prefix
|
||||
controller_path
|
||||
end
|
||||
|
||||
# Return a string representation of a Rack-compatible response body.
|
||||
def self.body_to_s(body)
|
||||
if body.respond_to?(:to_str)
|
||||
body
|
||||
else
|
||||
strings = []
|
||||
body.each { |part| strings << part.to_s }
|
||||
body.close if body.respond_to?(:close)
|
||||
strings.join
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# This method should return a hash with assigns.
|
||||
# You can overwrite this configuration per controller.
|
||||
# :api: public
|
||||
def view_assigns
|
||||
hash = {}
|
||||
variables = instance_variable_names
|
||||
variables -= protected_instance_variables if respond_to?(:protected_instance_variables)
|
||||
variables.each { |name| hash[name.to_s[1..-1]] = instance_variable_get(name) }
|
||||
hash
|
||||
end
|
||||
|
||||
# Normalize options by converting render "foo" to render :action => "foo" and
|
||||
# render "foo/bar" to render :file => "foo/bar".
|
||||
def _normalize_args(action=nil, options={})
|
||||
@ -134,9 +166,5 @@ def _normalize_options(options)
|
||||
|
||||
def _process_options(options)
|
||||
end
|
||||
|
||||
def _with_template_hook(template)
|
||||
self.formats = template.formats
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -7,7 +7,7 @@ module ViewPaths
|
||||
self._view_paths = ActionView::PathSet.new
|
||||
end
|
||||
|
||||
delegate :template_exists?, :view_paths, :formats, :formats=,
|
||||
delegate :find_template, :template_exists?, :view_paths, :formats, :formats=,
|
||||
:locale, :locale=, :to => :lookup_context
|
||||
|
||||
# LookupContext is the object responsible to hold all information required to lookup
|
||||
@ -29,10 +29,6 @@ def prepend_view_path(path)
|
||||
lookup_context.view_paths.unshift(*path)
|
||||
end
|
||||
|
||||
def template_exists?(*args)
|
||||
lookup_context.exists?(*args)
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# Append a path to the list of view paths for this controller.
|
||||
#
|
||||
|
@ -2,7 +2,6 @@ module ActionController
|
||||
class Base < Metal
|
||||
abstract!
|
||||
|
||||
include AbstractController::Callbacks
|
||||
include AbstractController::Layouts
|
||||
include AbstractController::Translation
|
||||
|
||||
@ -20,9 +19,11 @@ class Base < Metal
|
||||
include SessionManagement
|
||||
include ActionController::Caching
|
||||
include ActionController::MimeResponds
|
||||
include ActionController::PolymorphicRoutes
|
||||
|
||||
# Rails 2.x compatibility
|
||||
include ActionController::Compatibility
|
||||
include ActionController::ImplicitRender
|
||||
|
||||
include ActionController::Cookies
|
||||
include ActionController::Flash
|
||||
@ -36,8 +37,12 @@ class Base < Metal
|
||||
# Add instrumentations hooks at the bottom, to ensure they instrument
|
||||
# all the methods properly.
|
||||
include ActionController::Instrumentation
|
||||
include ImplicitRender
|
||||
|
||||
# Before callbacks should also be executed the earliest as possible, so
|
||||
# also include them at the bottom.
|
||||
include AbstractController::Callbacks
|
||||
|
||||
# The same with rescue, append it at the end to wrap as much as possible.
|
||||
include ActionController::Rescue
|
||||
|
||||
def self.inherited(klass)
|
||||
|
@ -80,6 +80,7 @@ module ClassMethods
|
||||
def caches_action(*actions)
|
||||
return unless cache_configured?
|
||||
options = actions.extract_options!
|
||||
options[:layout] = true unless options.key?(:layout)
|
||||
filter_options = options.extract!(:if, :unless).merge(:only => actions)
|
||||
cache_options = options.extract!(:layout, :cache_path).merge(:store_options => options)
|
||||
|
||||
@ -87,14 +88,12 @@ def caches_action(*actions)
|
||||
end
|
||||
end
|
||||
|
||||
def _render_cache_fragment(cache, extension, layout)
|
||||
render :text => cache, :layout => layout, :content_type => Mime[extension || :html]
|
||||
end
|
||||
|
||||
def _save_fragment(name, layout, options)
|
||||
def _save_fragment(name, options)
|
||||
return unless caching_allowed?
|
||||
|
||||
content = layout ? view_context.content_for(:layout) : response_body
|
||||
content = response_body
|
||||
content = content.join if content.is_a?(Array)
|
||||
|
||||
write_fragment(name, content, options)
|
||||
end
|
||||
|
||||
@ -112,7 +111,7 @@ def expire_action(options = {})
|
||||
|
||||
class ActionCacheFilter #:nodoc:
|
||||
def initialize(options, &block)
|
||||
@cache_path, @store_options, @layout =
|
||||
@cache_path, @store_options, @cache_layout =
|
||||
options.values_at(:cache_path, :store_options, :layout)
|
||||
end
|
||||
|
||||
@ -125,12 +124,19 @@ def filter(controller)
|
||||
|
||||
cache_path = ActionCachePath.new(controller, path_options || {})
|
||||
|
||||
if cache = controller.read_fragment(cache_path.path, @store_options)
|
||||
controller._render_cache_fragment(cache, cache_path.extension, @layout == false)
|
||||
else
|
||||
body = controller.read_fragment(cache_path.path, @store_options)
|
||||
|
||||
unless body
|
||||
controller.action_has_layout = false unless @cache_layout
|
||||
yield
|
||||
controller._save_fragment(cache_path.path, @layout == false, @store_options)
|
||||
controller.action_has_layout = true
|
||||
body = controller._save_fragment(cache_path.path, @store_options)
|
||||
end
|
||||
|
||||
body = controller.render_to_string(:text => cache, :layout => true) unless @cache_layout
|
||||
|
||||
controller.response_body = body
|
||||
controller.content_type = Mime[cache_path.extension || :html]
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -34,26 +34,13 @@ def fragment_cache_key(key)
|
||||
ActiveSupport::Cache.expand_cache_key(key.is_a?(Hash) ? url_for(key).split("://").last : key, :views)
|
||||
end
|
||||
|
||||
def fragment_for(buffer, name = {}, options = nil, &block) #:nodoc:
|
||||
if perform_caching
|
||||
if fragment_exist?(name, options)
|
||||
buffer.safe_concat(read_fragment(name, options))
|
||||
else
|
||||
pos = buffer.length
|
||||
block.call
|
||||
write_fragment(name, buffer[pos..-1], options)
|
||||
end
|
||||
else
|
||||
block.call
|
||||
end
|
||||
end
|
||||
|
||||
# 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)
|
||||
|
||||
key = fragment_cache_key(key)
|
||||
instrument_fragment_cache :write_fragment, key do
|
||||
content = content.html_safe.to_str if content.respond_to?(:html_safe)
|
||||
cache_store.write(key, content, options)
|
||||
end
|
||||
content
|
||||
@ -62,10 +49,11 @@ def write_fragment(key, content, options = nil)
|
||||
# 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?
|
||||
key = fragment_cache_key(key)
|
||||
|
||||
key = fragment_cache_key(key)
|
||||
instrument_fragment_cache :read_fragment, key do
|
||||
cache_store.read(key, options)
|
||||
result = cache_store.read(key, options)
|
||||
result.respond_to?(:html_safe) ? result.html_safe : result
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -63,7 +63,7 @@ def ip_spoofing_check=(value)
|
||||
def ip_spoofing_check
|
||||
ActiveSupport::Deprecation.warn "ActionController::Base.ip_spoofing_check is deprecated. " <<
|
||||
"Configuring ip_spoofing_check on the application configures a middleware.", caller
|
||||
Rails.application.config.action_disaptch.ip_spoofing_check
|
||||
Rails.application.config.action_dispatch.ip_spoofing_check
|
||||
end
|
||||
|
||||
def trusted_proxies=(value)
|
||||
|
@ -34,11 +34,12 @@ def controller_name
|
||||
# and response object available. You might wish to control the
|
||||
# environment and response manually for performance reasons.
|
||||
|
||||
attr_internal :status, :headers, :content_type, :response, :request
|
||||
attr_internal :headers, :response, :request
|
||||
delegate :session, :to => "@_request"
|
||||
|
||||
def initialize(*)
|
||||
@_headers = {}
|
||||
@_headers = {"Content-Type" => "text/html"}
|
||||
@_status = 200
|
||||
super
|
||||
end
|
||||
|
||||
@ -62,10 +63,19 @@ def location=(url)
|
||||
headers["Location"] = url
|
||||
end
|
||||
|
||||
def status
|
||||
@_status
|
||||
end
|
||||
|
||||
def status=(status)
|
||||
@_status = Rack::Utils.status_code(status)
|
||||
end
|
||||
|
||||
def response_body=(val)
|
||||
body = val.respond_to?(:each) ? val : [val]
|
||||
super body
|
||||
end
|
||||
|
||||
# :api: private
|
||||
def dispatch(name, request)
|
||||
@_request = request
|
||||
|
@ -40,15 +40,6 @@ def rescue_action(env)
|
||||
def initialize_template_class(*) end
|
||||
def assign_shortcuts(*) end
|
||||
|
||||
def template
|
||||
@template ||= view_context
|
||||
end
|
||||
|
||||
def process_action(*)
|
||||
template
|
||||
super
|
||||
end
|
||||
|
||||
def _normalize_options(options)
|
||||
if options[:action] && options[:action].to_s.include?(?/)
|
||||
ActiveSupport::Deprecation.warn "Giving a path to render :action is deprecated. " <<
|
||||
|
@ -261,7 +261,8 @@ def retrieve_response_from_mimes(mimes=nil, &block)
|
||||
block.call(collector) if block_given?
|
||||
|
||||
if format = request.negotiate_mime(collector.order)
|
||||
self.formats = [format.to_sym]
|
||||
self.content_type ||= format.to_s
|
||||
lookup_context.freeze_formats([format.to_sym])
|
||||
collector.response_for(format)
|
||||
else
|
||||
head :not_acceptable
|
||||
|
@ -5,10 +5,8 @@ module ActionController
|
||||
module RackDelegation
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
delegate :headers, :status=, :location=, :content_type=,
|
||||
:status, :location, :content_type, :to => "@_response"
|
||||
end
|
||||
delegate :headers, :status=, :location=, :content_type=,
|
||||
:status, :location, :content_type, :to => "@_response"
|
||||
|
||||
def dispatch(action, request)
|
||||
@_response = ActionDispatch::Response.new
|
||||
|
@ -76,7 +76,7 @@ def _compute_redirect_to_location(options)
|
||||
# The scheme name consist of a letter followed by any combination of
|
||||
# letters, digits, and the plus ("+"), period ("."), or hyphen ("-")
|
||||
# characters; and is terminated by a colon (":").
|
||||
when %r{^\w[\w\d+.-]*:.*}
|
||||
when %r{^\w[\w+.-]*:.*}
|
||||
options
|
||||
when String
|
||||
request.protocol + request.host_with_port + options
|
||||
|
@ -87,8 +87,9 @@ def self._write_render_options
|
||||
end
|
||||
|
||||
add :update do |proc, options|
|
||||
view_context = self.view_context
|
||||
generator = ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.new(view_context, &proc)
|
||||
self.content_type = Mime::JS
|
||||
self.content_type = Mime::JS
|
||||
self.response_body = generator.to_s
|
||||
end
|
||||
end
|
||||
|
@ -5,26 +5,31 @@ module Rendering
|
||||
include ActionController::RackDelegation
|
||||
include AbstractController::Rendering
|
||||
|
||||
def process(*)
|
||||
# Before processing, set the request formats in current controller formats.
|
||||
def process_action(*) #:nodoc:
|
||||
self.formats = request.formats.map { |x| x.to_sym }
|
||||
super
|
||||
end
|
||||
|
||||
def render(*args)
|
||||
# Check for double render errors and set the content_type after rendering.
|
||||
def render(*args) #:nodoc:
|
||||
raise ::AbstractController::DoubleRenderError if response_body
|
||||
super
|
||||
self.content_type ||= Mime[formats.first].to_s
|
||||
response_body
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def _normalize_args(action=nil, options={}, &blk)
|
||||
# Normalize arguments by catching blocks and setting them on :update.
|
||||
def _normalize_args(action=nil, options={}, &blk) #:nodoc:
|
||||
options = super
|
||||
options[:update] = blk if block_given?
|
||||
options
|
||||
end
|
||||
|
||||
def _normalize_options(options)
|
||||
# Normalize both text and status options.
|
||||
def _normalize_options(options) #:nodoc:
|
||||
if options.key?(:text) && options[:text].respond_to?(:to_text)
|
||||
options[:text] = options[:text].to_text
|
||||
end
|
||||
@ -36,7 +41,8 @@ def _normalize_options(options)
|
||||
super
|
||||
end
|
||||
|
||||
def _process_options(options)
|
||||
# Process controller specific options, as status, content-type and location.
|
||||
def _process_options(options) #:nodoc:
|
||||
status, content_type, location = options.values_at(:status, :content_type, :location)
|
||||
|
||||
self.status = status if status
|
||||
@ -46,10 +52,5 @@ def _process_options(options)
|
||||
super
|
||||
end
|
||||
|
||||
def _with_template_hook(template)
|
||||
super
|
||||
self.content_type ||= template.mime_type.to_s
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
@ -12,7 +12,7 @@ module RequestForgeryProtection
|
||||
included do
|
||||
# Sets the token parameter name for RequestForgery. Calling +protect_from_forgery+
|
||||
# sets it to <tt>:authenticity_token</tt> by default.
|
||||
config.request_forgery_protection_token ||= true
|
||||
config.request_forgery_protection_token ||= :authenticity_token
|
||||
|
||||
# Controls whether request forgergy protection is turned on or not. Turned off by default only in test mode.
|
||||
config.allow_forgery_protection ||= true
|
||||
|
@ -1,3 +1,5 @@
|
||||
require 'active_support/json'
|
||||
|
||||
module ActionController #:nodoc:
|
||||
# Responder is responsible for exposing a resource to different mime requests,
|
||||
# usually depending on the HTTP verb. The responder is triggered when
|
||||
|
@ -1,16 +1,17 @@
|
||||
require "rails"
|
||||
require "action_controller"
|
||||
require "action_dispatch/railtie"
|
||||
require "action_view/railtie"
|
||||
require "active_support/core_ext/class/subclasses"
|
||||
require "active_support/deprecation/proxy_wrappers"
|
||||
require "active_support/deprecation"
|
||||
|
||||
require "action_controller/railties/log_subscriber"
|
||||
require "action_controller/railties/url_helpers"
|
||||
|
||||
module ActionController
|
||||
class Railtie < Rails::Railtie
|
||||
railtie_name :action_controller
|
||||
|
||||
require "action_controller/railties/log_subscriber"
|
||||
require "action_controller/railties/url_helpers"
|
||||
config.action_controller = ActiveSupport::OrderedOptions.new
|
||||
|
||||
ad = config.action_dispatch
|
||||
config.action_controller.singleton_class.send(:define_method, :session) do
|
||||
@ -37,7 +38,7 @@ class Railtie < Rails::Railtie
|
||||
ad.session_store = val
|
||||
end
|
||||
|
||||
log_subscriber ActionController::Railties::LogSubscriber.new
|
||||
log_subscriber :action_controller, ActionController::Railties::LogSubscriber.new
|
||||
|
||||
initializer "action_controller.logger" do
|
||||
ActionController.base_hook { self.logger ||= Rails.logger }
|
||||
@ -52,7 +53,9 @@ class Railtie < Rails::Railtie
|
||||
ac.stylesheets_dir = paths.public.stylesheets.to_a.first
|
||||
ac.secret = app.config.cookie_secret
|
||||
|
||||
ActionController.base_hook { self.config.replace(ac) }
|
||||
ActionController.base_hook do
|
||||
self.config.merge!(ac)
|
||||
end
|
||||
end
|
||||
|
||||
initializer "action_controller.initialize_framework_caches" do
|
||||
@ -67,7 +70,7 @@ class Railtie < Rails::Railtie
|
||||
|
||||
initializer "action_controller.url_helpers" do |app|
|
||||
ActionController.base_hook do
|
||||
extend ::ActionController::Railtie::UrlHelpers.with(app.routes)
|
||||
extend ::ActionController::Railties::UrlHelpers.with(app.routes)
|
||||
end
|
||||
|
||||
message = "ActionController::Routing::Routes is deprecated. " \
|
||||
|
@ -1,5 +1,5 @@
|
||||
module ActionController
|
||||
class Railtie
|
||||
module Railties
|
||||
module UrlHelpers
|
||||
def self.with(router)
|
||||
Module.new do
|
||||
|
@ -1,7 +1,107 @@
|
||||
require 'rack/session/abstract/id'
|
||||
require 'action_view/test_case'
|
||||
|
||||
module ActionController
|
||||
module TemplateAssertions
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
setup :setup_subscriptions
|
||||
teardown :teardown_subscriptions
|
||||
end
|
||||
|
||||
def setup_subscriptions
|
||||
@partials = Hash.new(0)
|
||||
@templates = Hash.new(0)
|
||||
@layouts = Hash.new(0)
|
||||
|
||||
ActiveSupport::Notifications.subscribe("action_view.render_template") do |name, start, finish, id, payload|
|
||||
path = payload[:layout]
|
||||
@layouts[path] += 1
|
||||
end
|
||||
|
||||
ActiveSupport::Notifications.subscribe("action_view.render_template!") do |name, start, finish, id, payload|
|
||||
path = payload[:virtual_path]
|
||||
next unless path
|
||||
partial = path =~ /^.*\/_[^\/]*$/
|
||||
if partial
|
||||
@partials[path] += 1
|
||||
@partials[path.split("/").last] += 1
|
||||
@templates[path] += 1
|
||||
else
|
||||
@templates[path] += 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def teardown_subscriptions
|
||||
ActiveSupport::Notifications.unsubscribe("action_view.render_template!")
|
||||
end
|
||||
|
||||
# Asserts that the request was rendered with the appropriate template file or partials
|
||||
#
|
||||
# ==== Examples
|
||||
#
|
||||
# # assert that the "new" view template was rendered
|
||||
# assert_template "new"
|
||||
#
|
||||
# # assert that the "_customer" partial was rendered twice
|
||||
# assert_template :partial => '_customer', :count => 2
|
||||
#
|
||||
# # assert that no partials were rendered
|
||||
# assert_template :partial => false
|
||||
#
|
||||
def assert_template(options = {}, message = nil)
|
||||
validate_request!
|
||||
|
||||
case options
|
||||
when NilClass, String
|
||||
rendered = @templates
|
||||
msg = build_message(message,
|
||||
"expecting <?> but rendering with <?>",
|
||||
options, rendered.keys.join(', '))
|
||||
assert_block(msg) do
|
||||
if options.nil?
|
||||
@templates.blank?
|
||||
else
|
||||
rendered.any? { |t,num| t.match(options) }
|
||||
end
|
||||
end
|
||||
when Hash
|
||||
if expected_partial = options[:partial]
|
||||
if expected_count = options[:count]
|
||||
actual_count = @partials[expected_partial]
|
||||
# actual_count = found.nil? ? 0 : found[1]
|
||||
msg = build_message(message,
|
||||
"expecting ? to be rendered ? time(s) but rendered ? time(s)",
|
||||
expected_partial, expected_count, actual_count)
|
||||
assert(actual_count == expected_count.to_i, msg)
|
||||
elsif options.key?(:layout)
|
||||
msg = build_message(message,
|
||||
"expecting layout <?> but action rendered <?>",
|
||||
expected_layout, @layouts.keys)
|
||||
|
||||
case layout = options[:layout]
|
||||
when String
|
||||
assert(@layouts.include?(expected_layout), msg)
|
||||
when Regexp
|
||||
assert(@layouts.any? {|l| l =~ layout }, msg)
|
||||
when nil
|
||||
assert(@layouts.empty?, msg)
|
||||
end
|
||||
else
|
||||
msg = build_message(message,
|
||||
"expecting partial <?> but action rendered <?>",
|
||||
options[:partial], @partials.keys)
|
||||
assert(@partials.include?(expected_partial), msg)
|
||||
end
|
||||
else
|
||||
assert @partials.empty?,
|
||||
"Expected no partials to be rendered"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class TestRequest < ActionDispatch::TestRequest #:nodoc:
|
||||
def initialize(env = {})
|
||||
super
|
||||
@ -181,6 +281,7 @@ def initialize(session = {})
|
||||
# assert_redirected_to page_url(:title => 'foo')
|
||||
class TestCase < ActiveSupport::TestCase
|
||||
include ActionDispatch::TestProcess
|
||||
include ActionController::TemplateAssertions
|
||||
|
||||
# Executes a request simulating GET HTTP method and set/volley the response
|
||||
def get(action, parameters = nil, session = nil, flash = nil)
|
||||
|
@ -25,7 +25,6 @@ module Http
|
||||
module FilterParameters
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
mattr_reader :compiled_parameter_filter_for
|
||||
@@compiled_parameter_filter_for = {}
|
||||
|
||||
# Return a hash of parameters with all sensitive data replaced.
|
||||
|
@ -52,12 +52,6 @@ class Type
|
||||
cattr_reader :browser_generated_types
|
||||
attr_reader :symbol
|
||||
|
||||
@@unverifiable_types = Set.new [:text, :json, :csv, :xml, :rss, :atom, :yaml]
|
||||
def self.unverifiable_types
|
||||
ActiveSupport::Deprecation.warn("unverifiable_types is deprecated and has no effect", caller)
|
||||
@@unverifiable_types
|
||||
end
|
||||
|
||||
# A simple helper class used in parsing the accept header
|
||||
class AcceptItem #:nodoc:
|
||||
attr_accessor :order, :name, :q
|
||||
@ -100,7 +94,7 @@ def register_alias(string, symbol, extension_synonyms = [])
|
||||
end
|
||||
|
||||
def register(string, symbol, mime_type_synonyms = [], extension_synonyms = [], skip_lookup = false)
|
||||
Mime.instance_eval { const_set symbol.to_s.upcase, Type.new(string, symbol, mime_type_synonyms) }
|
||||
Mime.const_set(symbol.to_s.upcase, Type.new(string, symbol, mime_type_synonyms))
|
||||
|
||||
SET << Mime.const_get(symbol.to_s.upcase)
|
||||
|
||||
|
@ -84,6 +84,7 @@ def []=(key, options)
|
||||
|
||||
options[:path] ||= "/"
|
||||
@set_cookies[key] = options
|
||||
@delete_cookies.delete(key)
|
||||
value
|
||||
end
|
||||
|
||||
|
@ -5,20 +5,6 @@
|
||||
module ActionDispatch
|
||||
# This middleware rescues any exception returned by the application and renders
|
||||
# nice exception pages if it's being rescued locally.
|
||||
#
|
||||
# Every time an exception is caught, a notification is published, becoming a good API
|
||||
# to deal with exceptions. So, if you want send an e-mail through ActionMailer
|
||||
# everytime this notification is published, you just need to do the following:
|
||||
#
|
||||
# ActiveSupport::Notifications.subscribe "action_dispatch.show_exception" do |name, start, end, instrumentation_id, payload|
|
||||
# ExceptionNotifier.deliver_exception(start, payload)
|
||||
# end
|
||||
#
|
||||
# The payload is a hash which has two pairs:
|
||||
#
|
||||
# * :env - Contains the rack env for the given request;
|
||||
# * :exception - The exception raised;
|
||||
#
|
||||
class ShowExceptions
|
||||
LOCALHOST = ['127.0.0.1', '::1'].freeze
|
||||
|
||||
|
@ -3,9 +3,8 @@
|
||||
|
||||
module ActionDispatch
|
||||
class Railtie < Rails::Railtie
|
||||
railtie_name :action_dispatch
|
||||
|
||||
config.action_dispatch.x_sendfile_header = "X-Sendfile"
|
||||
config.action_dispatch = ActiveSupport::OrderedOptions.new
|
||||
config.action_dispatch.x_sendfile_header = ""
|
||||
config.action_dispatch.ip_spoofing_check = true
|
||||
|
||||
# Prepare dispatcher callbacks and run 'prepare' callbacks
|
||||
|
@ -1,5 +1,6 @@
|
||||
require 'active_support/core_ext/object/to_param'
|
||||
require 'active_support/core_ext/regexp'
|
||||
require 'action_controller/polymorphic_routes'
|
||||
|
||||
module ActionDispatch
|
||||
# == Routing
|
||||
|
@ -32,6 +32,8 @@ def call(env)
|
||||
end
|
||||
|
||||
class Mapping
|
||||
IGNORE_OPTIONS = [:to, :as, :controller, :action, :via, :on, :constraints, :defaults, :only, :except, :anchor]
|
||||
|
||||
def initialize(set, scope, args)
|
||||
@set, @scope = set, scope
|
||||
@path, @options = extract_path_and_options(args)
|
||||
@ -45,18 +47,21 @@ def to_route
|
||||
def extract_path_and_options(args)
|
||||
options = args.extract_options!
|
||||
|
||||
case
|
||||
when using_to_shorthand?(args, options)
|
||||
if using_to_shorthand?(args, options)
|
||||
path, to = options.find { |name, value| name.is_a?(String) }
|
||||
options.merge!(:to => to).delete(path) if path
|
||||
when using_match_shorthand?(args, options)
|
||||
path = args.first
|
||||
options = { :to => path.gsub("/", "#"), :as => path.gsub("/", "_") }
|
||||
else
|
||||
path = args.first
|
||||
end
|
||||
|
||||
[ normalize_path(path), options ]
|
||||
path = normalize_path(path)
|
||||
|
||||
if using_match_shorthand?(path, options)
|
||||
options[:to] ||= path[1..-1].sub(%r{/([^/]*)$}, '#\1')
|
||||
options[:as] ||= path[1..-1].gsub("/", "_")
|
||||
end
|
||||
|
||||
[ path, options ]
|
||||
end
|
||||
|
||||
# match "account" => "account#index"
|
||||
@ -65,14 +70,13 @@ def using_to_shorthand?(args, options)
|
||||
end
|
||||
|
||||
# match "account/overview"
|
||||
def using_match_shorthand?(args, options)
|
||||
args.present? && options.except(:via, :anchor).empty? && !args.first.include?(':')
|
||||
def using_match_shorthand?(path, options)
|
||||
path && options.except(:via, :anchor, :to, :as).empty? && path =~ %r{^/[\w\/]+$}
|
||||
end
|
||||
|
||||
def normalize_path(path)
|
||||
path = "#{@scope[:path]}/#{path}"
|
||||
raise ArgumentError, "path is required" if path.empty?
|
||||
Mapper.normalize_path(path)
|
||||
raise ArgumentError, "path is required" if @scope[:path].blank? && path.blank?
|
||||
Mapper.normalize_path("#{@scope[:path]}/#{path}")
|
||||
end
|
||||
|
||||
def app
|
||||
@ -94,7 +98,15 @@ def requirements
|
||||
end
|
||||
|
||||
def defaults
|
||||
@defaults ||= if to.respond_to?(:call)
|
||||
@defaults ||= (@options[:defaults] || {}).tap do |defaults|
|
||||
defaults.merge!(default_controller_and_action)
|
||||
defaults.reverse_merge!(@scope[:defaults]) if @scope[:defaults]
|
||||
@options.each { |k, v| defaults[k] = v unless v.is_a?(Regexp) || IGNORE_OPTIONS.include?(k.to_sym) }
|
||||
end
|
||||
end
|
||||
|
||||
def default_controller_and_action
|
||||
if to.respond_to?(:call)
|
||||
{ }
|
||||
else
|
||||
defaults = case to
|
||||
@ -144,8 +156,8 @@ def request_method_condition
|
||||
|
||||
def segment_keys
|
||||
@segment_keys ||= Rack::Mount::RegexpWithNamedGroups.new(
|
||||
Rack::Mount::Strexp.compile(@path, requirements, SEPARATORS)
|
||||
).names
|
||||
Rack::Mount::Strexp.compile(@path, requirements, SEPARATORS)
|
||||
).names
|
||||
end
|
||||
|
||||
def to
|
||||
@ -297,11 +309,14 @@ def constraints(constraints = {})
|
||||
scope(:constraints => constraints) { yield }
|
||||
end
|
||||
|
||||
def defaults(defaults = {})
|
||||
scope(:defaults => defaults) { yield }
|
||||
end
|
||||
|
||||
def match(*args)
|
||||
options = args.extract_options!
|
||||
|
||||
options = (@scope[:options] || {}).merge(options)
|
||||
options[:anchor] = true unless options.key?(:anchor)
|
||||
|
||||
if @scope[:name_prefix] && !options[:as].blank?
|
||||
options[:as] = "#{@scope[:name_prefix]}_#{options[:as]}"
|
||||
@ -342,6 +357,10 @@ def merge_constraints_scope(parent, child)
|
||||
merge_options_scope(parent, child)
|
||||
end
|
||||
|
||||
def merge_defaults_scope(parent, child)
|
||||
merge_options_scope(parent, child)
|
||||
end
|
||||
|
||||
def merge_blocks_scope(parent, child)
|
||||
(parent || []) + [child]
|
||||
end
|
||||
@ -362,11 +381,11 @@ def self.default_actions
|
||||
attr_reader :plural, :singular, :options
|
||||
|
||||
def initialize(entities, options = {})
|
||||
entities = entities.to_s
|
||||
@name = entities.to_s
|
||||
@options = options
|
||||
|
||||
@plural = entities.pluralize
|
||||
@singular = entities.singularize
|
||||
@plural = @name.pluralize
|
||||
@singular = @name.singularize
|
||||
end
|
||||
|
||||
def default_actions
|
||||
@ -393,7 +412,7 @@ def action_type(action)
|
||||
end
|
||||
|
||||
def name
|
||||
options[:as] || plural
|
||||
options[:as] || @name
|
||||
end
|
||||
|
||||
def controller
|
||||
@ -438,8 +457,8 @@ def action_type(action)
|
||||
end
|
||||
end
|
||||
|
||||
def name
|
||||
options[:as] || singular
|
||||
def member_name
|
||||
name
|
||||
end
|
||||
end
|
||||
|
||||
@ -460,7 +479,7 @@ def resource(*resources, &block)
|
||||
scope(:path => resource.name.to_s, :controller => resource.controller) do
|
||||
with_scope_level(:resource, resource) do
|
||||
|
||||
scope(:name_prefix => resource.name.to_s) do
|
||||
scope(:name_prefix => resource.name.to_s, :as => "") do
|
||||
yield if block_given?
|
||||
end
|
||||
|
||||
@ -468,8 +487,8 @@ def resource(*resources, &block)
|
||||
post :create if resource.actions.include?(:create)
|
||||
put :update if resource.actions.include?(:update)
|
||||
delete :destroy if resource.actions.include?(:destroy)
|
||||
get :new, :as => resource.singular if resource.actions.include?(:new)
|
||||
get :edit, :as => resource.singular if resource.actions.include?(:edit)
|
||||
get :new, :as => resource.name if resource.actions.include?(:new)
|
||||
get :edit, :as => resource.name if resource.actions.include?(:edit)
|
||||
end
|
||||
end
|
||||
|
||||
@ -563,6 +582,8 @@ def mount(app, options = nil)
|
||||
def match(*args)
|
||||
options = args.extract_options!
|
||||
|
||||
options[:anchor] = true unless options.key?(:anchor)
|
||||
|
||||
if args.length > 1
|
||||
args.each { |path| match(path, options) }
|
||||
return self
|
||||
|
@ -65,7 +65,7 @@ def split_glob_param!(params)
|
||||
# named routes.
|
||||
class NamedRouteCollection #:nodoc:
|
||||
include Enumerable
|
||||
attr_reader :routes, :helpers
|
||||
attr_reader :routes, :helpers, :module
|
||||
|
||||
def initialize
|
||||
clear!
|
||||
@ -179,6 +179,7 @@ def #{selector}(*args)
|
||||
|
||||
url_for(options)
|
||||
end
|
||||
protected :#{selector}
|
||||
END_EVAL
|
||||
helpers << selector
|
||||
end
|
||||
@ -219,14 +220,16 @@ def draw(&block)
|
||||
end
|
||||
|
||||
def finalize!
|
||||
return if @finalized
|
||||
@finalized = true
|
||||
@set.add_route(NotFound)
|
||||
install_helpers
|
||||
@set.freeze
|
||||
end
|
||||
|
||||
def clear!
|
||||
# Clear the controller cache so we may discover new ones
|
||||
@controller_constraints = nil
|
||||
@finalized = false
|
||||
routes.clear
|
||||
named_routes.clear
|
||||
@set = ::Rack::Mount::RouteSet.new(:parameters_key => PARAMETERS_KEY)
|
||||
@ -239,21 +242,30 @@ def install_helpers(destinations = [ActionController::Base, ActionView::Base], r
|
||||
|
||||
def url_helpers
|
||||
@url_helpers ||= begin
|
||||
router = self
|
||||
routes = self
|
||||
|
||||
Module.new do
|
||||
helpers = Module.new do
|
||||
extend ActiveSupport::Concern
|
||||
include UrlFor
|
||||
|
||||
@routes = routes
|
||||
class << self
|
||||
delegate :url_for, :to => '@routes'
|
||||
end
|
||||
extend routes.named_routes.module
|
||||
|
||||
# ROUTES TODO: install_helpers isn't great... can we make a module with the stuff that
|
||||
# we can include?
|
||||
# Yes plz - JP
|
||||
included do
|
||||
router.install_helpers(self)
|
||||
routes.install_helpers(self)
|
||||
singleton_class.send(:define_method, :_router) { routes }
|
||||
end
|
||||
|
||||
define_method(:_router) { router }
|
||||
define_method(:_router) { routes }
|
||||
end
|
||||
|
||||
helpers
|
||||
end
|
||||
end
|
||||
|
||||
@ -406,6 +418,7 @@ def generate(options, recall = {}, extras = false)
|
||||
RESERVED_OPTIONS = [:anchor, :params, :only_path, :host, :protocol, :port, :trailing_slash]
|
||||
|
||||
def url_for(options)
|
||||
finalize!
|
||||
options = default_url_options.merge(options || {})
|
||||
|
||||
handle_positional_args(options)
|
||||
@ -437,6 +450,7 @@ def url_for(options)
|
||||
end
|
||||
|
||||
def call(env)
|
||||
finalize!
|
||||
@set.call(env)
|
||||
end
|
||||
|
||||
|
@ -72,7 +72,7 @@ module Routing
|
||||
# you can do that by including ActionController::UrlFor in your class:
|
||||
#
|
||||
# class User < ActiveRecord::Base
|
||||
# include ActionController::UrlFor
|
||||
# include Rails.application.routes.url_helpers
|
||||
#
|
||||
# def base_uri
|
||||
# user_path(self)
|
||||
|
@ -73,58 +73,6 @@ def assert_redirected_to(options = {}, message=nil)
|
||||
end
|
||||
end
|
||||
|
||||
# Asserts that the request was rendered with the appropriate template file or partials
|
||||
#
|
||||
# ==== Examples
|
||||
#
|
||||
# # assert that the "new" view template was rendered
|
||||
# assert_template "new"
|
||||
#
|
||||
# # assert that the "_customer" partial was rendered twice
|
||||
# assert_template :partial => '_customer', :count => 2
|
||||
#
|
||||
# # assert that no partials were rendered
|
||||
# assert_template :partial => false
|
||||
#
|
||||
def assert_template(options = {}, message = nil)
|
||||
validate_request!
|
||||
|
||||
case options
|
||||
when NilClass, String
|
||||
rendered = (@controller.template.rendered[:template] || []).map { |t| t.identifier }
|
||||
msg = build_message(message,
|
||||
"expecting <?> but rendering with <?>",
|
||||
options, rendered.join(', '))
|
||||
assert_block(msg) do
|
||||
if options.nil?
|
||||
@controller.template.rendered[:template].blank?
|
||||
else
|
||||
rendered.any? { |t| t.match(options) }
|
||||
end
|
||||
end
|
||||
when Hash
|
||||
if expected_partial = options[:partial]
|
||||
partials = @controller.template.rendered[:partials]
|
||||
if expected_count = options[:count]
|
||||
found = partials.detect { |p, _| p.identifier.match(expected_partial) }
|
||||
actual_count = found.nil? ? 0 : found.second
|
||||
msg = build_message(message,
|
||||
"expecting ? to be rendered ? time(s) but rendered ? time(s)",
|
||||
expected_partial, expected_count, actual_count)
|
||||
assert(actual_count == expected_count.to_i, msg)
|
||||
else
|
||||
msg = build_message(message,
|
||||
"expecting partial <?> but action rendered <?>",
|
||||
options[:partial], partials.keys)
|
||||
assert(partials.keys.any? { |p| p.identifier.match(expected_partial) }, msg)
|
||||
end
|
||||
else
|
||||
assert @controller.template.rendered[:partials].empty?,
|
||||
"Expected no partials to be rendered"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
# Proxy to to_param if the object will respond to it.
|
||||
def parameterize(value)
|
||||
|
@ -145,11 +145,25 @@ def with_routing
|
||||
old_routes, @router = @router, ActionDispatch::Routing::RouteSet.new
|
||||
old_controller, @controller = @controller, @controller.clone if @controller
|
||||
_router = @router
|
||||
@controller.singleton_class.send(:send, :include, @router.url_helpers) if @controller
|
||||
|
||||
# Unfortunately, there is currently an abstraction leak between AC::Base
|
||||
# and AV::Base which requires having the URL helpers in both AC and AV.
|
||||
# To do this safely at runtime for tests, we need to bump up the helper serial
|
||||
# to that the old AV subclass isn't cached.
|
||||
#
|
||||
# TODO: Make this unnecessary
|
||||
if @controller
|
||||
@controller.singleton_class.send(:include, _router.url_helpers)
|
||||
@controller.view_context_class = Class.new(@controller.view_context_class) do
|
||||
include _router.url_helpers
|
||||
end
|
||||
end
|
||||
yield @router
|
||||
ensure
|
||||
@router = old_routes
|
||||
@controller = old_controller if @controller
|
||||
if @controller
|
||||
@controller = old_controller
|
||||
end
|
||||
end
|
||||
|
||||
# ROUTES TODO: These assertions should really work in an integration context
|
||||
|
@ -2,6 +2,7 @@
|
||||
require 'uri'
|
||||
require 'active_support/core_ext/object/singleton_class'
|
||||
require 'rack/test'
|
||||
require 'test/unit/assertions'
|
||||
|
||||
module ActionDispatch
|
||||
module Integration #:nodoc:
|
||||
@ -177,14 +178,8 @@ def initialize(app)
|
||||
reset!
|
||||
end
|
||||
|
||||
def url_options
|
||||
opts = super.reverse_merge(
|
||||
:host => host,
|
||||
:protocol => https? ? "https" : "http"
|
||||
)
|
||||
|
||||
opts.merge!(:port => 443) if !opts.key?(:port) && https?
|
||||
opts
|
||||
def default_url_options
|
||||
{ :host => host, :protocol => https? ? "https" : "http" }
|
||||
end
|
||||
|
||||
# Resets the instance. This can be used to reset the state information
|
||||
@ -293,6 +288,8 @@ def process(method, path, parameters = nil, rack_environment = nil)
|
||||
end
|
||||
|
||||
module Runner
|
||||
include ActionDispatch::Assertions
|
||||
|
||||
def app
|
||||
@app
|
||||
end
|
||||
@ -300,7 +297,7 @@ def app
|
||||
# Reset the current session. This is useful for testing multiple sessions
|
||||
# in a single test case.
|
||||
def reset!
|
||||
@integration_session = open_session
|
||||
@integration_session = Integration::Session.new(app)
|
||||
end
|
||||
|
||||
%w(get post put head delete cookies assigns
|
||||
@ -326,30 +323,9 @@ def reset!
|
||||
# can use this method to open multiple sessions that ought to be tested
|
||||
# simultaneously.
|
||||
def open_session(app = nil)
|
||||
session = Integration::Session.new(app || self.app)
|
||||
|
||||
# delegate the fixture accessors back to the test instance
|
||||
extras = Module.new { attr_accessor :delegate, :test_result }
|
||||
if self.class.respond_to?(:fixture_table_names)
|
||||
self.class.fixture_table_names.each do |table_name|
|
||||
name = table_name.tr(".", "_")
|
||||
next unless respond_to?(name)
|
||||
extras.__send__(:define_method, name) { |*args|
|
||||
delegate.send(name, *args)
|
||||
}
|
||||
end
|
||||
dup.tap do |session|
|
||||
yield session if block_given?
|
||||
end
|
||||
|
||||
# delegate add_assertion to the test case
|
||||
extras.__send__(:define_method, :add_assertion) {
|
||||
test_result.add_assertion
|
||||
}
|
||||
session.extend(extras)
|
||||
session.delegate = self
|
||||
session.test_result = @_result
|
||||
|
||||
yield session if block_given?
|
||||
session
|
||||
end
|
||||
|
||||
# Copy the instance variables from the current session instance into the
|
||||
@ -460,6 +436,7 @@ def method_missing(sym, *args, &block)
|
||||
# end
|
||||
class IntegrationTest < ActiveSupport::TestCase
|
||||
include Integration::Runner
|
||||
include ActionController::TemplateAssertions
|
||||
|
||||
@@app = nil
|
||||
|
||||
|
@ -44,13 +44,15 @@ module ActionView
|
||||
|
||||
autoload :Base
|
||||
autoload :LookupContext
|
||||
autoload :MissingTemplate, 'action_view/base'
|
||||
autoload :Resolver, 'action_view/template/resolver'
|
||||
autoload :PathResolver, 'action_view/template/resolver'
|
||||
autoload :FileSystemResolver, 'action_view/template/resolver'
|
||||
autoload :PathSet, 'action_view/paths'
|
||||
|
||||
autoload :MissingTemplate, 'action_view/template/error'
|
||||
autoload :ActionViewError, 'action_view/template/error'
|
||||
autoload :TemplateError, 'action_view/template/error'
|
||||
|
||||
autoload :TemplateHandler, 'action_view/template'
|
||||
autoload :TemplateHandlers, 'action_view/template'
|
||||
end
|
||||
|
@ -1,27 +1,10 @@
|
||||
require 'active_support/core_ext/module/attr_internal'
|
||||
require 'active_support/core_ext/module/delegation'
|
||||
require 'active_support/core_ext/class/attribute'
|
||||
require 'active_support/core_ext/array/wrap'
|
||||
|
||||
module ActionView #:nodoc:
|
||||
class ActionViewError < StandardError #:nodoc:
|
||||
end
|
||||
|
||||
class MissingTemplate < ActionViewError #:nodoc:
|
||||
attr_reader :path
|
||||
|
||||
def initialize(paths, path, details, partial)
|
||||
@path = path
|
||||
display_paths = paths.compact.map{ |p| p.to_s.inspect }.join(", ")
|
||||
template_type = if partial
|
||||
"partial"
|
||||
elsif path =~ /layouts/i
|
||||
'layout'
|
||||
else
|
||||
'template'
|
||||
end
|
||||
|
||||
super("Missing #{template_type} #{path} with #{details.inspect} in view paths #{display_paths}")
|
||||
end
|
||||
class NonConcattingString < ActiveSupport::SafeBuffer
|
||||
end
|
||||
|
||||
# Action View templates can be written in three ways. If the template file has a <tt>.erb</tt> (or <tt>.rhtml</tt>) extension then it uses a mixture of ERb
|
||||
@ -176,14 +159,13 @@ module Subclasses
|
||||
include Helpers, Rendering, Partials, Layouts, ::ERB::Util, Context
|
||||
extend ActiveSupport::Memoizable
|
||||
|
||||
ActionView.run_base_hooks(self)
|
||||
|
||||
# Specify whether RJS responses should be wrapped in a try/catch block
|
||||
# that alert()s the caught exception (and then re-raises it).
|
||||
cattr_accessor :debug_rjs
|
||||
@@debug_rjs = false
|
||||
|
||||
class_attribute :helpers
|
||||
remove_method :helpers
|
||||
attr_reader :helpers
|
||||
|
||||
class << self
|
||||
@ -191,10 +173,12 @@ class << self
|
||||
delegate :logger, :to => 'ActionController::Base', :allow_nil => true
|
||||
end
|
||||
|
||||
attr_accessor :base_path, :assigns, :template_extension, :lookup_context
|
||||
attr_internal :captures, :request, :layout, :controller, :template, :config
|
||||
ActionView.run_base_hooks(self)
|
||||
|
||||
delegate :find, :exists?, :formats, :formats=, :locale, :locale=,
|
||||
attr_accessor :base_path, :assigns, :template_extension, :lookup_context
|
||||
attr_internal :captures, :request, :controller, :template, :config
|
||||
|
||||
delegate :find_template, :template_exists?, :formats, :formats=, :locale, :locale=,
|
||||
:view_paths, :view_paths=, :with_fallbacks, :update_details, :to => :lookup_context
|
||||
|
||||
delegate :request_forgery_protection_token, :template, :params, :session, :cookies, :response, :headers,
|
||||
@ -202,42 +186,17 @@ class << self
|
||||
|
||||
delegate :logger, :to => :controller, :allow_nil => true
|
||||
|
||||
# TODO: HACK FOR RJS
|
||||
def view_context
|
||||
self
|
||||
end
|
||||
|
||||
def self.xss_safe? #:nodoc:
|
||||
true
|
||||
end
|
||||
|
||||
def self.process_view_paths(value)
|
||||
ActionView::PathSet.new(Array(value))
|
||||
end
|
||||
|
||||
def self.for_controller(controller)
|
||||
@views ||= {}
|
||||
|
||||
# TODO: Decouple this so helpers are a separate concern in AV just like
|
||||
# they are in AC.
|
||||
if controller.class.respond_to?(:_helper_serial)
|
||||
klass = @views[controller.class._helper_serial] ||= Class.new(self) do
|
||||
# Try to make stack traces clearer
|
||||
class_eval <<-ruby_eval, __FILE__, __LINE__ + 1
|
||||
def self.name
|
||||
"ActionView for #{controller.class}"
|
||||
end
|
||||
|
||||
def inspect
|
||||
"#<#{self.class.name}>"
|
||||
end
|
||||
ruby_eval
|
||||
|
||||
if controller.respond_to?(:_helpers)
|
||||
include controller._helpers
|
||||
self.helpers = controller._helpers
|
||||
end
|
||||
end
|
||||
else
|
||||
klass = self
|
||||
end
|
||||
|
||||
klass.new(controller.lookup_context, {}, controller)
|
||||
ActionView::PathSet.new(Array.wrap(value))
|
||||
end
|
||||
|
||||
def initialize(lookup_context = nil, assigns_for_first_render = {}, controller = nil, formats = nil) #:nodoc:
|
||||
@ -246,7 +205,7 @@ def initialize(lookup_context = nil, assigns_for_first_render = {}, controller =
|
||||
@helpers = self.class.helpers || Module.new
|
||||
|
||||
@_controller = controller
|
||||
@_config = ActiveSupport::InheritableOptions.new(controller.config) if controller
|
||||
@_config = ActiveSupport::InheritableOptions.new(controller.config) if controller && controller.respond_to?(:config)
|
||||
@_content_for = Hash.new { |h,k| h[k] = ActiveSupport::SafeBuffer.new }
|
||||
@_virtual_path = nil
|
||||
|
||||
@ -264,14 +223,5 @@ def punctuate_body!(part)
|
||||
response.body_parts << part
|
||||
nil
|
||||
end
|
||||
|
||||
# Evaluates the local assigns and controller ivars, pushes them to the view.
|
||||
def _evaluate_assigns_and_ivars #:nodoc:
|
||||
if controller
|
||||
variables = controller.instance_variable_names
|
||||
variables -= controller.protected_instance_variables if controller.respond_to?(:protected_instance_variables)
|
||||
variables.each { |name| instance_variable_set(name, controller.instance_variable_get(name)) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -10,8 +10,6 @@ module CompiledTemplates #:nodoc:
|
||||
# In order to work with ActionController, a Context
|
||||
# must implement:
|
||||
#
|
||||
# Context.for_controller[controller] Create a new ActionView instance for a
|
||||
# controller
|
||||
# Context#render_partial[options]
|
||||
# - responsible for setting options[:_template]
|
||||
# - Returns String with the rendered partial
|
||||
|
@ -2,30 +2,32 @@
|
||||
|
||||
module ActionView #:nodoc:
|
||||
module Helpers #:nodoc:
|
||||
autoload :ActiveModelHelper, 'action_view/helpers/active_model_helper'
|
||||
autoload :AssetTagHelper, 'action_view/helpers/asset_tag_helper'
|
||||
autoload :AtomFeedHelper, 'action_view/helpers/atom_feed_helper'
|
||||
autoload :CacheHelper, 'action_view/helpers/cache_helper'
|
||||
autoload :CaptureHelper, 'action_view/helpers/capture_helper'
|
||||
autoload :CsrfHelper, 'action_view/helpers/csrf_helper'
|
||||
autoload :DateHelper, 'action_view/helpers/date_helper'
|
||||
autoload :DebugHelper, 'action_view/helpers/debug_helper'
|
||||
autoload :DeprecatedBlockHelpers, 'action_view/helpers/deprecated_block_helpers'
|
||||
autoload :FormHelper, 'action_view/helpers/form_helper'
|
||||
autoload :FormOptionsHelper, 'action_view/helpers/form_options_helper'
|
||||
autoload :FormTagHelper, 'action_view/helpers/form_tag_helper'
|
||||
autoload :JavaScriptHelper, 'action_view/helpers/javascript_helper'
|
||||
autoload :NumberHelper, 'action_view/helpers/number_helper'
|
||||
autoload :PrototypeHelper, 'action_view/helpers/prototype_helper'
|
||||
autoload :RawOutputHelper, 'action_view/helpers/raw_output_helper'
|
||||
autoload :RecordIdentificationHelper, 'action_view/helpers/record_identification_helper'
|
||||
autoload :RecordTagHelper, 'action_view/helpers/record_tag_helper'
|
||||
autoload :SanitizeHelper, 'action_view/helpers/sanitize_helper'
|
||||
autoload :ScriptaculousHelper, 'action_view/helpers/scriptaculous_helper'
|
||||
autoload :TagHelper, 'action_view/helpers/tag_helper'
|
||||
autoload :TextHelper, 'action_view/helpers/text_helper'
|
||||
autoload :TranslationHelper, 'action_view/helpers/translation_helper'
|
||||
autoload :UrlHelper, 'action_view/helpers/url_helper'
|
||||
extend ActiveSupport::Autoload
|
||||
|
||||
autoload :ActiveModelHelper
|
||||
autoload :AssetTagHelper
|
||||
autoload :AtomFeedHelper
|
||||
autoload :CacheHelper
|
||||
autoload :CaptureHelper
|
||||
autoload :CsrfHelper
|
||||
autoload :DateHelper
|
||||
autoload :DebugHelper
|
||||
autoload :DeprecatedBlockHelpers
|
||||
autoload :FormHelper
|
||||
autoload :FormOptionsHelper
|
||||
autoload :FormTagHelper
|
||||
autoload :JavaScriptHelper, "action_view/helpers/javascript_helper"
|
||||
autoload :NumberHelper
|
||||
autoload :PrototypeHelper
|
||||
autoload :RawOutputHelper
|
||||
autoload :RecordIdentificationHelper
|
||||
autoload :RecordTagHelper
|
||||
autoload :SanitizeHelper
|
||||
autoload :ScriptaculousHelper
|
||||
autoload :TagHelper
|
||||
autoload :TextHelper
|
||||
autoload :TranslationHelper
|
||||
autoload :UrlHelper
|
||||
|
||||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
|
@ -3,6 +3,7 @@
|
||||
require 'active_support/core_ext/class/attribute_accessors'
|
||||
require 'active_support/core_ext/enumerable'
|
||||
require 'active_support/core_ext/kernel/reporting'
|
||||
require 'active_support/core_ext/object/blank'
|
||||
|
||||
module ActionView
|
||||
ActionView.base_hook do
|
||||
@ -127,9 +128,9 @@ def error_message_on(object, method, *args)
|
||||
object = convert_to_model(object)
|
||||
|
||||
if (obj = (object.respond_to?(:errors) ? object : instance_variable_get("@#{object}"))) &&
|
||||
(errors = obj.errors[method])
|
||||
(errors = obj.errors[method]).presence
|
||||
content_tag("div",
|
||||
(options[:prepend_text].html_safe << errors.first).safe_concat(options[:append_text]),
|
||||
"#{options[:prepend_text]}#{ERB::Util.h(errors.first)}#{options[:append_text]}".html_safe,
|
||||
:class => options[:css_class]
|
||||
)
|
||||
else
|
||||
@ -295,6 +296,10 @@ def error_wrapping(html_tag)
|
||||
end
|
||||
end
|
||||
|
||||
def error_message
|
||||
object.errors[@method_name]
|
||||
end
|
||||
|
||||
def column_type
|
||||
object.send(:column_for_attribute, @method_name).type
|
||||
end
|
||||
|
@ -3,6 +3,7 @@
|
||||
require 'action_view/helpers/url_helper'
|
||||
require 'action_view/helpers/tag_helper'
|
||||
require 'active_support/core_ext/file'
|
||||
require 'active_support/core_ext/object/blank'
|
||||
|
||||
module ActionView
|
||||
module Helpers #:nodoc:
|
||||
@ -11,7 +12,7 @@ module Helpers #:nodoc:
|
||||
# the assets exist before linking to them:
|
||||
#
|
||||
# image_tag("rails.png")
|
||||
# # => <img alt="Rails src="/images/rails.png?1230601161" />
|
||||
# # => <img alt="Rails" src="/images/rails.png?1230601161" />
|
||||
# stylesheet_link_tag("application")
|
||||
# # => <link href="/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" />
|
||||
#
|
||||
@ -58,7 +59,7 @@ module Helpers #:nodoc:
|
||||
# +asset_host+ to a proc like this:
|
||||
#
|
||||
# ActionController::Base.asset_host = Proc.new { |source|
|
||||
# "http://assets#{rand(2) + 1}.example.com"
|
||||
# "http://assets#{source.hash % 2 + 1}.example.com"
|
||||
# }
|
||||
# image_tag("rails.png")
|
||||
# # => <img alt="Rails" src="http://assets0.example.com/images/rails.png?1230601161" />
|
||||
@ -66,7 +67,7 @@ module Helpers #:nodoc:
|
||||
# # => <link href="http://assets1.example.com/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" />
|
||||
#
|
||||
# The example above generates "http://assets1.example.com" and
|
||||
# "http://assets2.example.com" randomly. This option is useful for example if
|
||||
# "http://assets2.example.com". This option is useful for example if
|
||||
# you need fewer/more than four hosts, custom host names, etc.
|
||||
#
|
||||
# As you see the proc takes a +source+ parameter. That's a string with the
|
||||
@ -242,12 +243,12 @@ def javascript_path(source)
|
||||
# == Caching multiple javascripts into one
|
||||
#
|
||||
# You can also cache multiple javascripts into one file, which requires less HTTP connections to download and can better be
|
||||
# compressed by gzip (leading to faster transfers). Caching will only happen if ActionController::Base.perform_caching
|
||||
# compressed by gzip (leading to faster transfers). Caching will only happen if config.perform_caching
|
||||
# is set to <tt>true</tt> (which is the case by default for the Rails production environment, but not for the development
|
||||
# environment).
|
||||
#
|
||||
# ==== Examples
|
||||
# javascript_include_tag :all, :cache => true # when ActionController::Base.perform_caching is false =>
|
||||
# javascript_include_tag :all, :cache => true # when config.perform_caching is false =>
|
||||
# <script type="text/javascript" src="/javascripts/prototype.js"></script>
|
||||
# <script type="text/javascript" src="/javascripts/effects.js"></script>
|
||||
# ...
|
||||
@ -255,15 +256,15 @@ def javascript_path(source)
|
||||
# <script type="text/javascript" src="/javascripts/shop.js"></script>
|
||||
# <script type="text/javascript" src="/javascripts/checkout.js"></script>
|
||||
#
|
||||
# javascript_include_tag :all, :cache => true # when ActionController::Base.perform_caching is true =>
|
||||
# javascript_include_tag :all, :cache => true # when config.perform_caching is true =>
|
||||
# <script type="text/javascript" src="/javascripts/all.js"></script>
|
||||
#
|
||||
# javascript_include_tag "prototype", "cart", "checkout", :cache => "shop" # when ActionController::Base.perform_caching is false =>
|
||||
# javascript_include_tag "prototype", "cart", "checkout", :cache => "shop" # when config.perform_caching is false =>
|
||||
# <script type="text/javascript" src="/javascripts/prototype.js"></script>
|
||||
# <script type="text/javascript" src="/javascripts/cart.js"></script>
|
||||
# <script type="text/javascript" src="/javascripts/checkout.js"></script>
|
||||
#
|
||||
# javascript_include_tag "prototype", "cart", "checkout", :cache => "shop" # when ActionController::Base.perform_caching is true =>
|
||||
# javascript_include_tag "prototype", "cart", "checkout", :cache => "shop" # when config.perform_caching is true =>
|
||||
# <script type="text/javascript" src="/javascripts/shop.js"></script>
|
||||
#
|
||||
# The <tt>:recursive</tt> option is also available for caching:
|
||||
@ -275,11 +276,11 @@ def javascript_include_tag(*sources)
|
||||
cache = concat || options.delete("cache")
|
||||
recursive = options.delete("recursive")
|
||||
|
||||
if concat || (ActionController::Base.perform_caching && cache)
|
||||
if concat || (config.perform_caching && cache)
|
||||
joined_javascript_name = (cache == true ? "all" : cache) + ".js"
|
||||
joined_javascript_path = File.join(joined_javascript_name[/^#{File::SEPARATOR}/] ? config.assets_dir : config.javascripts_dir, joined_javascript_name)
|
||||
|
||||
unless ActionController::Base.perform_caching && File.exists?(joined_javascript_path)
|
||||
unless config.perform_caching && File.exists?(joined_javascript_path)
|
||||
write_asset_file_contents(joined_javascript_path, compute_javascript_paths(sources, recursive))
|
||||
end
|
||||
javascript_src_tag(joined_javascript_name, options)
|
||||
@ -390,25 +391,25 @@ def stylesheet_path(source)
|
||||
# == Caching multiple stylesheets into one
|
||||
#
|
||||
# You can also cache multiple stylesheets into one file, which requires less HTTP connections and can better be
|
||||
# compressed by gzip (leading to faster transfers). Caching will only happen if ActionController::Base.perform_caching
|
||||
# compressed by gzip (leading to faster transfers). Caching will only happen if config.perform_caching
|
||||
# is set to true (which is the case by default for the Rails production environment, but not for the development
|
||||
# environment). Examples:
|
||||
#
|
||||
# ==== Examples
|
||||
# stylesheet_link_tag :all, :cache => true # when ActionController::Base.perform_caching is false =>
|
||||
# stylesheet_link_tag :all, :cache => true # when config.perform_caching is false =>
|
||||
# <link href="/stylesheets/style1.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
# <link href="/stylesheets/styleB.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
# <link href="/stylesheets/styleX2.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
#
|
||||
# stylesheet_link_tag :all, :cache => true # when ActionController::Base.perform_caching is true =>
|
||||
# stylesheet_link_tag :all, :cache => true # when config.perform_caching is true =>
|
||||
# <link href="/stylesheets/all.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
#
|
||||
# stylesheet_link_tag "shop", "cart", "checkout", :cache => "payment" # when ActionController::Base.perform_caching is false =>
|
||||
# stylesheet_link_tag "shop", "cart", "checkout", :cache => "payment" # when config.perform_caching is false =>
|
||||
# <link href="/stylesheets/shop.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
# <link href="/stylesheets/cart.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
# <link href="/stylesheets/checkout.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
#
|
||||
# stylesheet_link_tag "shop", "cart", "checkout", :cache => "payment" # when ActionController::Base.perform_caching is true =>
|
||||
# stylesheet_link_tag "shop", "cart", "checkout", :cache => "payment" # when config.perform_caching is true =>
|
||||
# <link href="/stylesheets/payment.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
#
|
||||
# The <tt>:recursive</tt> option is also available for caching:
|
||||
@ -426,11 +427,11 @@ def stylesheet_link_tag(*sources)
|
||||
cache = concat || options.delete("cache")
|
||||
recursive = options.delete("recursive")
|
||||
|
||||
if concat || (ActionController::Base.perform_caching && cache)
|
||||
if concat || (config.perform_caching && cache)
|
||||
joined_stylesheet_name = (cache == true ? "all" : cache) + ".css"
|
||||
joined_stylesheet_path = File.join(joined_stylesheet_name[/^#{File::SEPARATOR}/] ? config.assets_dir : config.stylesheets_dir, joined_stylesheet_name)
|
||||
|
||||
unless ActionController::Base.perform_caching && File.exists?(joined_stylesheet_path)
|
||||
unless config.perform_caching && File.exists?(joined_stylesheet_path)
|
||||
write_asset_file_contents(joined_stylesheet_path, compute_stylesheet_paths(sources, recursive))
|
||||
end
|
||||
stylesheet_tag(joined_stylesheet_name, options)
|
||||
@ -523,7 +524,7 @@ def image_tag(source, options = {})
|
||||
options.symbolize_keys!
|
||||
|
||||
src = options[:src] = path_to_image(source)
|
||||
options[:alt] ||= File.basename(src, '.*').split('.').first.to_s.capitalize
|
||||
options[:alt] ||= File.basename(src, '.*').capitalize
|
||||
|
||||
if size = options.delete(:size)
|
||||
options[:width], options[:height] = size.split("x") if size =~ %r{^\d+x\d+$}
|
||||
@ -623,41 +624,37 @@ def self.cache_asset_timestamps=(value)
|
||||
@@cache_asset_timestamps = true
|
||||
|
||||
private
|
||||
def rewrite_extension?(source, dir, ext)
|
||||
source_ext = File.extname(source)[1..-1]
|
||||
ext && (source_ext.blank? || (ext != source_ext && File.exist?(File.join(config.assets_dir, dir, "#{source}.#{ext}"))))
|
||||
end
|
||||
|
||||
def rewrite_host_and_protocol(source, has_request)
|
||||
host = compute_asset_host(source)
|
||||
if has_request && host.present? && !is_uri?(host)
|
||||
host = "#{controller.request.protocol}#{host}"
|
||||
end
|
||||
"#{host}#{source}"
|
||||
end
|
||||
|
||||
# Add the the extension +ext+ if not present. Return full URLs otherwise untouched.
|
||||
# Prefix with <tt>/dir/</tt> if lacking a leading +/+. Account for relative URL
|
||||
# roots. Rewrite the asset path for cache-busting asset ids. Include
|
||||
# asset host, if configured, with the correct request protocol.
|
||||
def compute_public_path(source, dir, ext = nil, include_host = true)
|
||||
return source if is_uri?(source)
|
||||
|
||||
source += ".#{ext}" if rewrite_extension?(source, dir, ext)
|
||||
source = "/#{dir}/#{source}" unless source[0] == ?/
|
||||
source = rewrite_asset_path(source)
|
||||
|
||||
has_request = controller.respond_to?(:request)
|
||||
|
||||
source_ext = File.extname(source)[1..-1]
|
||||
if ext && !is_uri?(source) && (source_ext.blank? || (ext != source_ext && File.exist?(File.join(config.assets_dir, dir, "#{source}.#{ext}"))))
|
||||
source += ".#{ext}"
|
||||
if has_request && include_host && source !~ %r{^#{controller.config.relative_url_root}/}
|
||||
source = "#{controller.config.relative_url_root}#{source}"
|
||||
end
|
||||
source = rewrite_host_and_protocol(source, has_request) if include_host
|
||||
|
||||
unless is_uri?(source)
|
||||
source = "/#{dir}/#{source}" unless source[0] == ?/
|
||||
|
||||
source = rewrite_asset_path(source)
|
||||
|
||||
if has_request && include_host
|
||||
unless source =~ %r{^#{controller.config.relative_url_root}/}
|
||||
source = "#{controller.config.relative_url_root}#{source}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if include_host && !is_uri?(source)
|
||||
host = compute_asset_host(source)
|
||||
|
||||
if has_request && !host.blank? && !is_uri?(host)
|
||||
host = "#{controller.request.protocol}#{host}"
|
||||
end
|
||||
|
||||
"#{host}#{source}"
|
||||
else
|
||||
source
|
||||
end
|
||||
source
|
||||
end
|
||||
|
||||
def is_uri?(path)
|
||||
|
@ -32,7 +32,28 @@ module CacheHelper
|
||||
# <i>Topics listed alphabetically</i>
|
||||
# <% end %>
|
||||
def cache(name = {}, options = nil, &block)
|
||||
controller.fragment_for(output_buffer, name, options, &block)
|
||||
safe_concat fragment_for(name, options, &block)
|
||||
nil
|
||||
end
|
||||
|
||||
private
|
||||
# TODO: Create an object that has caching read/write on it
|
||||
def fragment_for(name = {}, options = nil, &block) #:nodoc:
|
||||
if controller.perform_caching
|
||||
if controller.fragment_exist?(name, options)
|
||||
controller.read_fragment(name, options)
|
||||
else
|
||||
# VIEW TODO: Make #capture usable outside of ERB
|
||||
# This dance is needed because Builder can't use capture
|
||||
pos = output_buffer.length
|
||||
yield
|
||||
fragment = output_buffer.slice!(pos..-1)
|
||||
controller.write_fragment(name, fragment, options)
|
||||
end
|
||||
else
|
||||
ret = yield
|
||||
ActiveSupport::SafeBuffer.new(ret) if ret.is_a?(String)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -2,22 +2,22 @@ module ActionView
|
||||
module Helpers
|
||||
# CaptureHelper exposes methods to let you extract generated markup which
|
||||
# can be used in other parts of a template or layout file.
|
||||
# It provides a method to capture blocks into variables through capture and
|
||||
# It provides a method to capture blocks into variables through capture and
|
||||
# a way to capture a block of markup for use in a layout through content_for.
|
||||
module CaptureHelper
|
||||
# The capture method allows you to extract part of a template into a
|
||||
# variable. You can then use this variable anywhere in your templates or layout.
|
||||
#
|
||||
# The capture method allows you to extract part of a template into a
|
||||
# variable. You can then use this variable anywhere in your templates or layout.
|
||||
#
|
||||
# ==== Examples
|
||||
# The capture method can be used in ERb templates...
|
||||
#
|
||||
#
|
||||
# <% @greeting = capture do %>
|
||||
# Welcome to my shiny new web page! The date and time is
|
||||
# <%= Time.now %>
|
||||
# <% end %>
|
||||
#
|
||||
# ...and Builder (RXML) templates.
|
||||
#
|
||||
#
|
||||
# @timestamp = capture do
|
||||
# "The current timestamp is #{Time.now}."
|
||||
# end
|
||||
@ -32,16 +32,18 @@ module CaptureHelper
|
||||
#
|
||||
def capture(*args)
|
||||
value = nil
|
||||
buffer = with_output_buffer { value = yield *args }
|
||||
buffer.presence || value
|
||||
buffer = with_output_buffer { value = yield(*args) }
|
||||
if string = buffer.presence || value and string.is_a?(String)
|
||||
NonConcattingString.new(string)
|
||||
end
|
||||
end
|
||||
|
||||
# Calling content_for stores a block of markup in an identifier for later use.
|
||||
# You can make subsequent calls to the stored content in other templates or the layout
|
||||
# by passing the identifier as an argument to <tt>yield</tt>.
|
||||
#
|
||||
#
|
||||
# ==== Examples
|
||||
#
|
||||
#
|
||||
# <% content_for :not_authorized do %>
|
||||
# alert('You are not authorized to do that!')
|
||||
# <% end %>
|
||||
@ -75,7 +77,7 @@ def capture(*args)
|
||||
#
|
||||
# Then, in another view, you could to do something like this:
|
||||
#
|
||||
# <%= link_to_remote 'Logout', :action => 'logout' %>
|
||||
# <%= link_to 'Logout', :action => 'logout', :remote => true %>
|
||||
#
|
||||
# <% content_for :script do %>
|
||||
# <%= javascript_include_tag :defaults %>
|
||||
@ -92,7 +94,7 @@ def capture(*args)
|
||||
# <% end %>
|
||||
#
|
||||
# <%# Add some other content, or use a different template: %>
|
||||
#
|
||||
#
|
||||
# <% content_for :navigation do %>
|
||||
# <li><%= link_to 'Login', :action => 'login' %></li>
|
||||
# <% end %>
|
||||
@ -109,13 +111,13 @@ def capture(*args)
|
||||
# for elements that will be fragment cached.
|
||||
def content_for(name, content = nil, &block)
|
||||
content = capture(&block) if block_given?
|
||||
return @_content_for[name] << content if content
|
||||
@_content_for[name]
|
||||
@_content_for[name] << content if content
|
||||
@_content_for[name] unless content
|
||||
end
|
||||
|
||||
# content_for? simply checks whether any content has been captured yet using content_for
|
||||
# Useful to render parts of your layout differently based on what is in your views.
|
||||
#
|
||||
#
|
||||
# ==== Examples
|
||||
#
|
||||
# Perhaps you will use different css in you layout if no content_for :right_column
|
||||
@ -140,7 +142,7 @@ def content_for?(name)
|
||||
def with_output_buffer(buf = nil) #:nodoc:
|
||||
unless buf
|
||||
buf = ActionView::OutputBuffer.new
|
||||
buf.force_encoding(output_buffer.encoding) if buf.respond_to?(:force_encoding)
|
||||
buf.force_encoding(output_buffer.encoding) if output_buffer && buf.respond_to?(:force_encoding)
|
||||
end
|
||||
self.output_buffer, old_buffer = buf, output_buffer
|
||||
yield
|
||||
|
@ -1,52 +0,0 @@
|
||||
module ActionView
|
||||
module Helpers
|
||||
module DeprecatedBlockHelpers
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
include ActionView::Helpers::TagHelper
|
||||
include ActionView::Helpers::TextHelper
|
||||
include ActionView::Helpers::JavaScriptHelper
|
||||
include ActionView::Helpers::FormHelper
|
||||
|
||||
def content_tag(*, &block)
|
||||
block_called_from_erb?(block) ? safe_concat(super) : super
|
||||
end
|
||||
|
||||
def javascript_tag(*, &block)
|
||||
block_called_from_erb?(block) ? safe_concat(super) : super
|
||||
end
|
||||
|
||||
def form_for(*, &block)
|
||||
block_called_from_erb?(block) ? safe_concat(super) : super
|
||||
end
|
||||
|
||||
def form_tag(*, &block)
|
||||
block_called_from_erb?(block) ? safe_concat(super) : super
|
||||
end
|
||||
|
||||
def fields_for(*, &block)
|
||||
block_called_from_erb?(block) ? safe_concat(super) : super
|
||||
end
|
||||
|
||||
def field_set_tag(*, &block)
|
||||
block_called_from_erb?(block) ? safe_concat(super) : super
|
||||
end
|
||||
|
||||
BLOCK_CALLED_FROM_ERB = 'defined? __in_erb_template'
|
||||
|
||||
if RUBY_VERSION < '1.9.0'
|
||||
# Check whether we're called from an erb template.
|
||||
# We'd return a string in any other case, but erb <%= ... %>
|
||||
# can't take an <% end %> later on, so we have to use <% ... %>
|
||||
# and implicitly concat.
|
||||
def block_called_from_erb?(block)
|
||||
block && eval(BLOCK_CALLED_FROM_ERB, block)
|
||||
end
|
||||
else
|
||||
def block_called_from_erb?(block)
|
||||
block && eval(BLOCK_CALLED_FROM_ERB, block.binding)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -28,7 +28,7 @@ module Helpers
|
||||
#
|
||||
# # Note: a @person variable will have been created in the controller.
|
||||
# # For example: @person = Person.new
|
||||
# <% form_for :person, @person, :url => { :action => "create" } do |f| %>
|
||||
# <%= form_for :person, @person, :url => { :action => "create" } do |f| %>
|
||||
# <%= f.text_field :first_name %>
|
||||
# <%= f.text_field :last_name %>
|
||||
# <%= submit_tag 'Create' %>
|
||||
@ -44,7 +44,7 @@ module Helpers
|
||||
#
|
||||
# If you are using a partial for your form fields, you can use this shortcut:
|
||||
#
|
||||
# <% form_for :person, @person, :url => { :action => "create" } do |f| %>
|
||||
# <%= form_for :person, @person, :url => { :action => "create" } do |f| %>
|
||||
# <%= render :partial => f %>
|
||||
# <%= submit_tag 'Create' %>
|
||||
# <% end %>
|
||||
@ -102,7 +102,7 @@ module FormHelper
|
||||
# Rails provides succinct resource-oriented form generation with +form_for+
|
||||
# like this:
|
||||
#
|
||||
# <% form_for @offer do |f| %>
|
||||
# <%= form_for @offer do |f| %>
|
||||
# <%= f.label :version, 'Version' %>:
|
||||
# <%= f.text_field :version %><br />
|
||||
# <%= f.label :author, 'Author' %>:
|
||||
@ -119,7 +119,7 @@ module FormHelper
|
||||
# The generic way to call +form_for+ yields a form builder around a
|
||||
# model:
|
||||
#
|
||||
# <% form_for :person, :url => { :action => "update" } do |f| %>
|
||||
# <%= form_for :person, :url => { :action => "update" } do |f| %>
|
||||
# <%= f.error_messages %>
|
||||
# First name: <%= f.text_field :first_name %><br />
|
||||
# Last name : <%= f.text_field :last_name %><br />
|
||||
@ -143,7 +143,7 @@ module FormHelper
|
||||
# If the instance variable is not <tt>@person</tt> you can pass the actual
|
||||
# record as the second argument:
|
||||
#
|
||||
# <% form_for :person, person, :url => { :action => "update" } do |f| %>
|
||||
# <%= form_for :person, person, :url => { :action => "update" } do |f| %>
|
||||
# ...
|
||||
# <% end %>
|
||||
#
|
||||
@ -175,7 +175,7 @@ module FormHelper
|
||||
# possible to use both the stand-alone FormHelper methods and methods
|
||||
# from FormTagHelper. For example:
|
||||
#
|
||||
# <% form_for :person, @person, :url => { :action => "update" } do |f| %>
|
||||
# <%= form_for :person, @person, :url => { :action => "update" } do |f| %>
|
||||
# First name: <%= f.text_field :first_name %>
|
||||
# Last name : <%= f.text_field :last_name %>
|
||||
# Biography : <%= text_area :person, :biography %>
|
||||
@ -195,37 +195,37 @@ module FormHelper
|
||||
#
|
||||
# For example, if <tt>@post</tt> is an existing record you want to edit
|
||||
#
|
||||
# <% form_for @post do |f| %>
|
||||
# <%= form_for @post do |f| %>
|
||||
# ...
|
||||
# <% end %>
|
||||
#
|
||||
# is equivalent to something like:
|
||||
#
|
||||
# <% form_for :post, @post, :url => post_path(@post), :html => { :method => :put, :class => "edit_post", :id => "edit_post_45" } do |f| %>
|
||||
# <%= form_for :post, @post, :url => post_path(@post), :html => { :method => :put, :class => "edit_post", :id => "edit_post_45" } do |f| %>
|
||||
# ...
|
||||
# <% end %>
|
||||
#
|
||||
# And for new records
|
||||
#
|
||||
# <% form_for(Post.new) do |f| %>
|
||||
# <%= form_for(Post.new) do |f| %>
|
||||
# ...
|
||||
# <% end %>
|
||||
#
|
||||
# expands to
|
||||
#
|
||||
# <% form_for :post, Post.new, :url => posts_path, :html => { :class => "new_post", :id => "new_post" } do |f| %>
|
||||
# <%= form_for :post, Post.new, :url => posts_path, :html => { :class => "new_post", :id => "new_post" } do |f| %>
|
||||
# ...
|
||||
# <% end %>
|
||||
#
|
||||
# You can also overwrite the individual conventions, like this:
|
||||
#
|
||||
# <% form_for(@post, :url => super_post_path(@post)) do |f| %>
|
||||
# <%= form_for(@post, :url => super_post_path(@post)) do |f| %>
|
||||
# ...
|
||||
# <% end %>
|
||||
#
|
||||
# And for namespaced routes, like +admin_post_url+:
|
||||
#
|
||||
# <% form_for([:admin, @post]) do |f| %>
|
||||
# <%= form_for([:admin, @post]) do |f| %>
|
||||
# ...
|
||||
# <% end %>
|
||||
#
|
||||
@ -243,7 +243,7 @@ module FormHelper
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# <% form_for(:post, @post, :remote => true, :html => { :id => 'create-post', :method => :put }) do |f| %>
|
||||
# <%= form_for(:post, @post, :remote => true, :html => { :id => 'create-post', :method => :put }) do |f| %>
|
||||
# ...
|
||||
# <% end %>
|
||||
#
|
||||
@ -263,7 +263,7 @@ module FormHelper
|
||||
# custom builder. For example, let's say you made a helper to
|
||||
# automatically add labels to form inputs.
|
||||
#
|
||||
# <% form_for :person, @person, :url => { :action => "update" }, :builder => LabellingFormBuilder do |f| %>
|
||||
# <%= form_for :person, @person, :url => { :action => "update" }, :builder => LabellingFormBuilder do |f| %>
|
||||
# <%= f.text_field :first_name %>
|
||||
# <%= f.text_field :last_name %>
|
||||
# <%= text_area :person, :biography %>
|
||||
@ -340,11 +340,11 @@ def apply_form_for_options!(object_or_array, options) #:nodoc:
|
||||
#
|
||||
# === Generic Examples
|
||||
#
|
||||
# <% form_for @person, :url => { :action => "update" } do |person_form| %>
|
||||
# <%= form_for @person, :url => { :action => "update" } do |person_form| %>
|
||||
# First name: <%= person_form.text_field :first_name %>
|
||||
# Last name : <%= person_form.text_field :last_name %>
|
||||
#
|
||||
# <% fields_for @person.permission do |permission_fields| %>
|
||||
# <%= fields_for @person.permission do |permission_fields| %>
|
||||
# Admin? : <%= permission_fields.check_box :admin %>
|
||||
# <% end %>
|
||||
# <% end %>
|
||||
@ -352,13 +352,13 @@ def apply_form_for_options!(object_or_array, options) #:nodoc:
|
||||
# ...or if you have an object that needs to be represented as a different
|
||||
# parameter, like a Client that acts as a Person:
|
||||
#
|
||||
# <% fields_for :person, @client do |permission_fields| %>
|
||||
# <%= fields_for :person, @client do |permission_fields| %>
|
||||
# Admin?: <%= permission_fields.check_box :admin %>
|
||||
# <% end %>
|
||||
#
|
||||
# ...or if you don't have an object, just a name of the parameter:
|
||||
#
|
||||
# <% fields_for :person do |permission_fields| %>
|
||||
# <%= fields_for :person do |permission_fields| %>
|
||||
# Admin?: <%= permission_fields.check_box :admin %>
|
||||
# <% end %>
|
||||
#
|
||||
@ -402,9 +402,9 @@ def apply_form_for_options!(object_or_array, options) #:nodoc:
|
||||
#
|
||||
# This model can now be used with a nested fields_for, like so:
|
||||
#
|
||||
# <% form_for @person, :url => { :action => "update" } do |person_form| %>
|
||||
# <%= form_for @person, :url => { :action => "update" } do |person_form| %>
|
||||
# ...
|
||||
# <% person_form.fields_for :address do |address_fields| %>
|
||||
# <%= person_form.fields_for :address do |address_fields| %>
|
||||
# Street : <%= address_fields.text_field :street %>
|
||||
# Zip code: <%= address_fields.text_field :zip_code %>
|
||||
# <% end %>
|
||||
@ -427,15 +427,15 @@ def apply_form_for_options!(object_or_array, options) #:nodoc:
|
||||
# accepts_nested_attributes_for :address, :allow_destroy => true
|
||||
# end
|
||||
#
|
||||
# Now, when you use a form element with the <tt>_delete</tt> parameter,
|
||||
# Now, when you use a form element with the <tt>_destroy</tt> parameter,
|
||||
# with a value that evaluates to +true+, you will destroy the associated
|
||||
# model (eg. 1, '1', true, or 'true'):
|
||||
#
|
||||
# <% form_for @person, :url => { :action => "update" } do |person_form| %>
|
||||
# <%= form_for @person, :url => { :action => "update" } do |person_form| %>
|
||||
# ...
|
||||
# <% person_form.fields_for :address do |address_fields| %>
|
||||
# <%= person_form.fields_for :address do |address_fields| %>
|
||||
# ...
|
||||
# Delete: <%= address_fields.check_box :_delete %>
|
||||
# Delete: <%= address_fields.check_box :_destroy %>
|
||||
# <% end %>
|
||||
# <% end %>
|
||||
#
|
||||
@ -459,9 +459,9 @@ def apply_form_for_options!(object_or_array, options) #:nodoc:
|
||||
# the nested fields_for call will be repeated for each instance in the
|
||||
# collection:
|
||||
#
|
||||
# <% form_for @person, :url => { :action => "update" } do |person_form| %>
|
||||
# <%= form_for @person, :url => { :action => "update" } do |person_form| %>
|
||||
# ...
|
||||
# <% person_form.fields_for :projects do |project_fields| %>
|
||||
# <%= person_form.fields_for :projects do |project_fields| %>
|
||||
# <% if project_fields.object.active? %>
|
||||
# Name: <%= project_fields.text_field :name %>
|
||||
# <% end %>
|
||||
@ -470,11 +470,11 @@ def apply_form_for_options!(object_or_array, options) #:nodoc:
|
||||
#
|
||||
# It's also possible to specify the instance to be used:
|
||||
#
|
||||
# <% form_for @person, :url => { :action => "update" } do |person_form| %>
|
||||
# <%= form_for @person, :url => { :action => "update" } do |person_form| %>
|
||||
# ...
|
||||
# <% @person.projects.each do |project| %>
|
||||
# <% if project.active? %>
|
||||
# <% person_form.fields_for :projects, project do |project_fields| %>
|
||||
# <%= person_form.fields_for :projects, project do |project_fields| %>
|
||||
# Name: <%= project_fields.text_field :name %>
|
||||
# <% end %>
|
||||
# <% end %>
|
||||
@ -483,9 +483,9 @@ def apply_form_for_options!(object_or_array, options) #:nodoc:
|
||||
#
|
||||
# Or a collection to be used:
|
||||
#
|
||||
# <% form_for @person, :url => { :action => "update" } do |person_form| %>
|
||||
# <%= form_for @person, :url => { :action => "update" } do |person_form| %>
|
||||
# ...
|
||||
# <% person_form.fields_for :projects, @active_projects do |project_fields| %>
|
||||
# <%= person_form.fields_for :projects, @active_projects do |project_fields| %>
|
||||
# Name: <%= project_fields.text_field :name %>
|
||||
# <% end %>
|
||||
# <% end %>
|
||||
@ -508,14 +508,14 @@ def apply_form_for_options!(object_or_array, options) #:nodoc:
|
||||
# end
|
||||
#
|
||||
# This will allow you to specify which models to destroy in the
|
||||
# attributes hash by adding a form element for the <tt>_delete</tt>
|
||||
# attributes hash by adding a form element for the <tt>_destroy</tt>
|
||||
# parameter with a value that evaluates to +true+
|
||||
# (eg. 1, '1', true, or 'true'):
|
||||
#
|
||||
# <% form_for @person, :url => { :action => "update" } do |person_form| %>
|
||||
# <%= form_for @person, :url => { :action => "update" } do |person_form| %>
|
||||
# ...
|
||||
# <% person_form.fields_for :projects do |project_fields| %>
|
||||
# Delete: <%= project_fields.check_box :_delete %>
|
||||
# <%= person_form.fields_for :projects do |project_fields| %>
|
||||
# Delete: <%= project_fields.check_box :_destroy %>
|
||||
# <% end %>
|
||||
# <% end %>
|
||||
def fields_for(record_or_name_or_array, *args, &block)
|
||||
@ -725,7 +725,7 @@ def text_area(object_name, method, options = {})
|
||||
# Unfortunately that workaround does not work when the check box goes
|
||||
# within an array-like parameter, as in
|
||||
#
|
||||
# <% fields_for "project[invoice_attributes][]", invoice, :index => nil do |form| %>
|
||||
# <%= fields_for "project[invoice_attributes][]", invoice, :index => nil do |form| %>
|
||||
# <%= form.check_box :paid %>
|
||||
# ...
|
||||
# <% end %>
|
||||
@ -1014,7 +1014,7 @@ class InstanceTag
|
||||
class FormBuilder #:nodoc:
|
||||
# The methods which wrap a form helper call.
|
||||
class_inheritable_accessor :field_helpers
|
||||
self.field_helpers = (FormHelper.instance_methods - ['form_for'])
|
||||
self.field_helpers = (FormHelper.instance_method_names - ['form_for'])
|
||||
|
||||
attr_accessor :object_name, :object, :options
|
||||
|
||||
@ -1040,7 +1040,7 @@ def initialize(object_name, object, template, options, proc)
|
||||
end
|
||||
|
||||
(field_helpers - %w(label check_box radio_button fields_for hidden_field)).each do |selector|
|
||||
src = <<-end_src
|
||||
src, file, line = <<-end_src, __FILE__, __LINE__ + 1
|
||||
def #{selector}(method, options = {}) # def text_field(method, options = {})
|
||||
@template.send( # @template.send(
|
||||
#{selector.inspect}, # "text_field",
|
||||
@ -1049,7 +1049,7 @@ def #{selector}(method, options = {}) # def text_field(method, options = {})
|
||||
objectify_options(options)) # objectify_options(options))
|
||||
end # end
|
||||
end_src
|
||||
class_eval src, __FILE__, __LINE__
|
||||
class_eval src, file, line
|
||||
end
|
||||
|
||||
def fields_for(record_or_name_or_array, *args, &block)
|
||||
@ -1115,7 +1115,7 @@ def error_messages(options = {})
|
||||
# Add the submit button for the given form. When no value is given, it checks
|
||||
# if the object is a new resource or not to create the proper label:
|
||||
#
|
||||
# <% form_for @post do |f| %>
|
||||
# <%= form_for @post do |f| %>
|
||||
# <%= f.submit %>
|
||||
# <% end %>
|
||||
#
|
||||
|
@ -151,7 +151,7 @@ def select(object, method, choices, options = {}, html_options = {})
|
||||
# end
|
||||
#
|
||||
# Sample usage (selecting the associated Author for an instance of Post, <tt>@post</tt>):
|
||||
# collection_select(:post, :author_id, Author.all, :id, :name_with_initial, {:prompt => true})
|
||||
# collection_select(:post, :author_id, Author.all, :id, :name_with_initial, :prompt => true)
|
||||
#
|
||||
# If <tt>@post.author_id</tt> is already <tt>1</tt>, this would return:
|
||||
# <select name="post[author_id]">
|
||||
|
@ -37,12 +37,12 @@ module FormTagHelper
|
||||
# form_tag('/upload', :multipart => true)
|
||||
# # => <form action="/upload" method="post" enctype="multipart/form-data">
|
||||
#
|
||||
# <% form_tag('/posts')do -%>
|
||||
# <%= form_tag('/posts')do -%>
|
||||
# <div><%= submit_tag 'Save' %></div>
|
||||
# <% end -%>
|
||||
# # => <form action="/posts" method="post"><div><input type="submit" name="submit" value="Save" /></div></form>
|
||||
#
|
||||
# <% form_tag('/posts', :remote => true) %>
|
||||
# <%= form_tag('/posts', :remote => true) %>
|
||||
# # => <form action="/posts" method="post" data-remote="true">
|
||||
#
|
||||
def form_tag(url_for_options = {}, options = {}, *parameters_for_url, &block)
|
||||
@ -430,17 +430,17 @@ def image_submit_tag(source, options = {})
|
||||
# <tt>options</tt> accept the same values as tag.
|
||||
#
|
||||
# ==== Examples
|
||||
# <% field_set_tag do %>
|
||||
# <%= field_set_tag do %>
|
||||
# <p><%= text_field_tag 'name' %></p>
|
||||
# <% end %>
|
||||
# # => <fieldset><p><input id="name" name="name" type="text" /></p></fieldset>
|
||||
#
|
||||
# <% field_set_tag 'Your details' do %>
|
||||
# <%= field_set_tag 'Your details' do %>
|
||||
# <p><%= text_field_tag 'name' %></p>
|
||||
# <% end %>
|
||||
# # => <fieldset><legend>Your details</legend><p><input id="name" name="name" type="text" /></p></fieldset>
|
||||
#
|
||||
# <% field_set_tag nil, :class => 'format' do %>
|
||||
# <%= field_set_tag nil, :class => 'format' do %>
|
||||
# <p><%= text_field_tag 'name' %></p>
|
||||
# <% end %>
|
||||
# # => <fieldset class="format"><p><input id="name" name="name" type="text" /></p></fieldset>
|
||||
|
@ -1,5 +1,4 @@
|
||||
require 'action_view/helpers/tag_helper'
|
||||
require 'action_view/helpers/prototype_helper'
|
||||
|
||||
module ActionView
|
||||
module Helpers
|
||||
@ -71,7 +70,7 @@ def escape_javascript(javascript)
|
||||
#
|
||||
# Instead of passing the content as an argument, you can also use a block
|
||||
# in which case, you pass your +html_options+ as the first parameter.
|
||||
# <% javascript_tag :defer => 'defer' do -%>
|
||||
# <%= javascript_tag :defer => 'defer' do -%>
|
||||
# alert('All is good')
|
||||
# <% end -%>
|
||||
def javascript_tag(content_or_options_with_block = nil, html_options = {}, &block)
|
||||
@ -89,6 +88,93 @@ def javascript_tag(content_or_options_with_block = nil, html_options = {}, &bloc
|
||||
def javascript_cdata_section(content) #:nodoc:
|
||||
"\n//#{cdata_section("\n#{content}\n//")}\n".html_safe
|
||||
end
|
||||
|
||||
# Returns a button with the given +name+ text that'll trigger a JavaScript +function+ using the
|
||||
# onclick handler.
|
||||
#
|
||||
# The first argument +name+ is used as the button's value or display text.
|
||||
#
|
||||
# The next arguments are optional and may include the javascript function definition and a hash of html_options.
|
||||
#
|
||||
# The +function+ argument can be omitted in favor of an +update_page+
|
||||
# block, which evaluates to a string when the template is rendered
|
||||
# (instead of making an Ajax request first).
|
||||
#
|
||||
# The +html_options+ will accept a hash of html attributes for the link tag. Some examples are :class => "nav_button", :id => "articles_nav_button"
|
||||
#
|
||||
# Note: if you choose to specify the javascript function in a block, but would like to pass html_options, set the +function+ parameter to nil
|
||||
#
|
||||
# Examples:
|
||||
# button_to_function "Greeting", "alert('Hello world!')"
|
||||
# button_to_function "Delete", "if (confirm('Really?')) do_delete()"
|
||||
# button_to_function "Details" do |page|
|
||||
# page[:details].visual_effect :toggle_slide
|
||||
# end
|
||||
# button_to_function "Details", :class => "details_button" do |page|
|
||||
# page[:details].visual_effect :toggle_slide
|
||||
# end
|
||||
def button_to_function(name, *args, &block)
|
||||
html_options = args.extract_options!.symbolize_keys
|
||||
|
||||
function = block_given? ? update_page(&block) : args[0] || ''
|
||||
onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function};"
|
||||
|
||||
tag(:input, html_options.merge(:type => 'button', :value => name, :onclick => onclick))
|
||||
end
|
||||
|
||||
# Returns a link of the given +name+ that will trigger a JavaScript +function+ using the
|
||||
# onclick handler and return false after the fact.
|
||||
#
|
||||
# The first argument +name+ is used as the link text.
|
||||
#
|
||||
# The next arguments are optional and may include the javascript function definition and a hash of html_options.
|
||||
#
|
||||
# The +function+ argument can be omitted in favor of an +update_page+
|
||||
# block, which evaluates to a string when the template is rendered
|
||||
# (instead of making an Ajax request first).
|
||||
#
|
||||
# The +html_options+ will accept a hash of html attributes for the link tag. Some examples are :class => "nav_button", :id => "articles_nav_button"
|
||||
#
|
||||
# Note: if you choose to specify the javascript function in a block, but would like to pass html_options, set the +function+ parameter to nil
|
||||
#
|
||||
#
|
||||
# Examples:
|
||||
# link_to_function "Greeting", "alert('Hello world!')"
|
||||
# Produces:
|
||||
# <a onclick="alert('Hello world!'); return false;" href="#">Greeting</a>
|
||||
#
|
||||
# link_to_function(image_tag("delete"), "if (confirm('Really?')) do_delete()")
|
||||
# Produces:
|
||||
# <a onclick="if (confirm('Really?')) do_delete(); return false;" href="#">
|
||||
# <img src="/images/delete.png?" alt="Delete"/>
|
||||
# </a>
|
||||
#
|
||||
# link_to_function("Show me more", nil, :id => "more_link") do |page|
|
||||
# page[:details].visual_effect :toggle_blind
|
||||
# page[:more_link].replace_html "Show me less"
|
||||
# end
|
||||
# Produces:
|
||||
# <a href="#" id="more_link" onclick="try {
|
||||
# $("details").visualEffect("toggle_blind");
|
||||
# $("more_link").update("Show me less");
|
||||
# }
|
||||
# catch (e) {
|
||||
# alert('RJS error:\n\n' + e.toString());
|
||||
# alert('$(\"details\").visualEffect(\"toggle_blind\");
|
||||
# \n$(\"more_link\").update(\"Show me less\");');
|
||||
# throw e
|
||||
# };
|
||||
# return false;">Show me more</a>
|
||||
#
|
||||
def link_to_function(name, *args, &block)
|
||||
html_options = args.extract_options!.symbolize_keys
|
||||
|
||||
function = block_given? ? update_page(&block) : args[0] || ''
|
||||
onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function}; return false;"
|
||||
href = html_options[:href] || '#'
|
||||
|
||||
content_tag(:a, name, html_options.merge(:href => href, :onclick => onclick))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -3,10 +3,24 @@
|
||||
|
||||
module ActionView
|
||||
module Helpers #:nodoc:
|
||||
|
||||
# Provides methods for converting numbers into formatted strings.
|
||||
# Methods are provided for phone numbers, currency, percentage,
|
||||
# precision, positional notation, and file size.
|
||||
# precision, positional notation, file size and pretty printing.
|
||||
#
|
||||
# Most methods expect a +number+ argument, and will return it
|
||||
# unchanged if can't be converted into a valid number.
|
||||
module NumberHelper
|
||||
|
||||
# Raised when argument +number+ param given to the helpers is invalid and
|
||||
# the option :raise is set to +true+.
|
||||
class InvalidNumberError < StandardError
|
||||
attr_accessor :number
|
||||
def initialize(number)
|
||||
@number = number
|
||||
end
|
||||
end
|
||||
|
||||
# Formats a +number+ into a US phone number (e.g., (555) 123-9876). You can customize the format
|
||||
# in the +options+ hash.
|
||||
#
|
||||
@ -30,6 +44,17 @@ module NumberHelper
|
||||
def number_to_phone(number, options = {})
|
||||
return nil if number.nil?
|
||||
|
||||
begin
|
||||
Float(number)
|
||||
is_number_html_safe = true
|
||||
rescue ArgumentError, TypeError
|
||||
if options[:raise]
|
||||
raise InvalidNumberError, number
|
||||
else
|
||||
is_number_html_safe = number.to_s.html_safe?
|
||||
end
|
||||
end
|
||||
|
||||
number = number.to_s.strip
|
||||
options = options.symbolize_keys
|
||||
area_code = options[:area_code] || nil
|
||||
@ -46,7 +71,7 @@ def number_to_phone(number, options = {})
|
||||
number.starts_with?('-') ? number.slice!(1..-1) : number
|
||||
end
|
||||
str << " x #{extension}" unless extension.blank?
|
||||
str
|
||||
is_number_html_safe ? str.html_safe : str
|
||||
end
|
||||
|
||||
# Formats a +number+ into a currency string (e.g., $13.65). You can customize the format
|
||||
@ -72,38 +97,42 @@ def number_to_phone(number, options = {})
|
||||
# number_to_currency(1234567890.50, :unit => "£", :separator => ",", :delimiter => "", :format => "%n %u")
|
||||
# # => 1234567890,50 £
|
||||
def number_to_currency(number, options = {})
|
||||
return nil if number.nil?
|
||||
|
||||
options.symbolize_keys!
|
||||
|
||||
defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {}
|
||||
currency = I18n.translate(:'number.currency.format', :locale => options[:locale], :raise => true) rescue {}
|
||||
defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {})
|
||||
currency = I18n.translate(:'number.currency.format', :locale => options[:locale], :default => {})
|
||||
defaults = defaults.merge(currency)
|
||||
|
||||
precision = options[:precision] || defaults[:precision]
|
||||
unit = options[:unit] || defaults[:unit]
|
||||
separator = options[:separator] || defaults[:separator]
|
||||
delimiter = options[:delimiter] || defaults[:delimiter]
|
||||
format = options[:format] || defaults[:format]
|
||||
separator = '' if precision == 0
|
||||
options = options.reverse_merge(defaults)
|
||||
|
||||
value = number_with_precision(number,
|
||||
:precision => precision,
|
||||
:delimiter => delimiter,
|
||||
:separator => separator)
|
||||
unit = options.delete(:unit)
|
||||
format = options.delete(:format)
|
||||
|
||||
if value
|
||||
begin
|
||||
value = number_with_precision(number, options.merge(:raise => true))
|
||||
format.gsub(/%n/, value).gsub(/%u/, unit).html_safe
|
||||
else
|
||||
number
|
||||
rescue InvalidNumberError => e
|
||||
if options[:raise]
|
||||
raise
|
||||
else
|
||||
formatted_number = format.gsub(/%n/, e.number).gsub(/%u/, unit)
|
||||
e.number.to_s.html_safe? ? formatted_number.html_safe : formatted_number
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# Formats a +number+ as a percentage string (e.g., 65%). You can customize the
|
||||
# format in the +options+ hash.
|
||||
#
|
||||
# ==== Options
|
||||
# * <tt>:precision</tt> - Sets the level of precision (defaults to 3).
|
||||
# * <tt>:separator</tt> - Sets the separator between the units (defaults to ".").
|
||||
# * <tt>:precision</tt> - Sets the precision of the number (defaults to 3).
|
||||
# * <tt>:significant</tt> - If +true+, precision will be the # of significant_digits. If +false+, the # of fractional digits (defaults to +false+)
|
||||
# * <tt>:separator</tt> - Sets the separator between the fractional and integer digits (defaults to ".").
|
||||
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to "").
|
||||
# * <tt>:strip_insignificant_zeros</tt> - If +true+ removes insignificant zeros after the decimal separator (defaults to +false+)
|
||||
#
|
||||
# ==== Examples
|
||||
# number_to_percentage(100) # => 100.000%
|
||||
@ -111,21 +140,25 @@ def number_to_currency(number, options = {})
|
||||
# number_to_percentage(1000, :delimiter => '.', :separator => ',') # => 1.000,000%
|
||||
# number_to_percentage(302.24398923423, :precision => 5) # => 302.24399%
|
||||
def number_to_percentage(number, options = {})
|
||||
return nil if number.nil?
|
||||
|
||||
options.symbolize_keys!
|
||||
|
||||
defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {}
|
||||
percentage = I18n.translate(:'number.percentage.format', :locale => options[:locale], :raise => true) rescue {}
|
||||
defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {})
|
||||
percentage = I18n.translate(:'number.percentage.format', :locale => options[:locale], :default => {})
|
||||
defaults = defaults.merge(percentage)
|
||||
|
||||
precision = options[:precision] || defaults[:precision]
|
||||
separator = options[:separator] || defaults[:separator]
|
||||
delimiter = options[:delimiter] || defaults[:delimiter]
|
||||
options = options.reverse_merge(defaults)
|
||||
|
||||
value = number_with_precision(number,
|
||||
:precision => precision,
|
||||
:separator => separator,
|
||||
:delimiter => delimiter)
|
||||
value ? value + "%" : number
|
||||
begin
|
||||
"#{number_with_precision(number, options.merge(:raise => true))}%".html_safe
|
||||
rescue InvalidNumberError => e
|
||||
if options[:raise]
|
||||
raise
|
||||
else
|
||||
e.number.to_s.html_safe? ? "#{e.number}%".html_safe : "#{e.number}%"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Formats a +number+ with grouped thousands using +delimiter+ (e.g., 12,324). You can
|
||||
@ -133,7 +166,7 @@ def number_to_percentage(number, options = {})
|
||||
#
|
||||
# ==== Options
|
||||
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to ",").
|
||||
# * <tt>:separator</tt> - Sets the separator between the units (defaults to ".").
|
||||
# * <tt>:separator</tt> - Sets the separator between the fractional and integer digits (defaults to ".").
|
||||
#
|
||||
# ==== Examples
|
||||
# number_with_delimiter(12345678) # => 12,345,678
|
||||
@ -146,148 +179,186 @@ def number_to_percentage(number, options = {})
|
||||
# You can still use <tt>number_with_delimiter</tt> with the old API that accepts the
|
||||
# +delimiter+ as its optional second and the +separator+ as its
|
||||
# optional third parameter:
|
||||
# number_with_delimiter(12345678, " ") # => 12 345.678
|
||||
# number_with_delimiter(12345678, " ") # => 12 345 678
|
||||
# number_with_delimiter(12345678.05, ".", ",") # => 12.345.678,05
|
||||
def number_with_delimiter(number, *args)
|
||||
options = args.extract_options!
|
||||
options.symbolize_keys!
|
||||
|
||||
defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {}
|
||||
begin
|
||||
Float(number)
|
||||
rescue ArgumentError, TypeError
|
||||
if options[:raise]
|
||||
raise InvalidNumberError, number
|
||||
else
|
||||
return number
|
||||
end
|
||||
end
|
||||
|
||||
defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {})
|
||||
|
||||
unless args.empty?
|
||||
ActiveSupport::Deprecation.warn('number_with_delimiter takes an option hash ' +
|
||||
'instead of separate delimiter and precision arguments.', caller)
|
||||
delimiter = args[0] || defaults[:delimiter]
|
||||
separator = args[1] || defaults[:separator]
|
||||
options[:delimiter] ||= args[0] if args[0]
|
||||
options[:separator] ||= args[1] if args[1]
|
||||
end
|
||||
|
||||
delimiter ||= (options[:delimiter] || defaults[:delimiter])
|
||||
separator ||= (options[:separator] || defaults[:separator])
|
||||
options = options.reverse_merge(defaults)
|
||||
|
||||
parts = number.to_s.split('.')
|
||||
if parts[0]
|
||||
parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{delimiter}")
|
||||
parts.join(separator)
|
||||
else
|
||||
number
|
||||
end
|
||||
parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}")
|
||||
parts.join(options[:separator]).html_safe
|
||||
|
||||
end
|
||||
|
||||
# Formats a +number+ with the specified level of <tt>:precision</tt> (e.g., 112.32 has a precision of 2).
|
||||
# Formats a +number+ with the specified level of <tt>:precision</tt> (e.g., 112.32 has a precision
|
||||
# of 2 if +:significant+ is +false+, and 5 if +:significant+ is +true+).
|
||||
# You can customize the format in the +options+ hash.
|
||||
#
|
||||
# ==== Options
|
||||
# * <tt>:precision</tt> - Sets the level of precision (defaults to 3).
|
||||
# * <tt>:separator</tt> - Sets the separator between the units (defaults to ".").
|
||||
# * <tt>:precision</tt> - Sets the precision of the number (defaults to 3).
|
||||
# * <tt>:significant</tt> - If +true+, precision will be the # of significant_digits. If +false+, the # of fractional digits (defaults to +false+)
|
||||
# * <tt>:separator</tt> - Sets the separator between the fractional and integer digits (defaults to ".").
|
||||
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to "").
|
||||
# * <tt>:strip_insignificant_zeros</tt> - If +true+ removes insignificant zeros after the decimal separator (defaults to +false+)
|
||||
#
|
||||
# ==== Examples
|
||||
# number_with_precision(111.2345) # => 111.235
|
||||
# number_with_precision(111.2345, :precision => 2) # => 111.23
|
||||
# number_with_precision(13, :precision => 5) # => 13.00000
|
||||
# number_with_precision(389.32314, :precision => 0) # => 389
|
||||
# number_with_precision(111.2345) # => 111.235
|
||||
# number_with_precision(111.2345, :precision => 2) # => 111.23
|
||||
# number_with_precision(13, :precision => 5) # => 13.00000
|
||||
# number_with_precision(389.32314, :precision => 0) # => 389
|
||||
# number_with_precision(111.2345, :significant => true) # => 111
|
||||
# number_with_precision(111.2345, :precision => 1, :significant => true) # => 100
|
||||
# number_with_precision(13, :precision => 5, :significant => true) # => 13.000
|
||||
# number_with_precision(13, :precision => 5, :significant => true, strip_insignificant_zeros => true)
|
||||
# # => 13
|
||||
# number_with_precision(389.32314, :precision => 4, :significant => true) # => 389.3
|
||||
# number_with_precision(1111.2345, :precision => 2, :separator => ',', :delimiter => '.')
|
||||
# # => 1.111,23
|
||||
#
|
||||
# You can still use <tt>number_with_precision</tt> with the old API that accepts the
|
||||
# +precision+ as its optional second parameter:
|
||||
# number_with_precision(number_with_precision(111.2345, 2) # => 111.23
|
||||
# number_with_precision(111.2345, 2) # => 111.23
|
||||
def number_with_precision(number, *args)
|
||||
|
||||
options = args.extract_options!
|
||||
options.symbolize_keys!
|
||||
|
||||
defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {}
|
||||
precision_defaults = I18n.translate(:'number.precision.format', :locale => options[:locale],
|
||||
:raise => true) rescue {}
|
||||
number = begin
|
||||
Float(number)
|
||||
rescue ArgumentError, TypeError
|
||||
if options[:raise]
|
||||
raise InvalidNumberError, number
|
||||
else
|
||||
return number
|
||||
end
|
||||
end
|
||||
|
||||
defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {})
|
||||
precision_defaults = I18n.translate(:'number.precision.format', :locale => options[:locale], :default => {})
|
||||
defaults = defaults.merge(precision_defaults)
|
||||
|
||||
#Backwards compatibility
|
||||
unless args.empty?
|
||||
ActiveSupport::Deprecation.warn('number_with_precision takes an option hash ' +
|
||||
'instead of a separate precision argument.', caller)
|
||||
precision = args[0] || defaults[:precision]
|
||||
options[:precision] ||= args[0] if args[0]
|
||||
end
|
||||
|
||||
precision ||= (options[:precision] || defaults[:precision])
|
||||
separator ||= (options[:separator] || defaults[:separator])
|
||||
delimiter ||= (options[:delimiter] || defaults[:delimiter])
|
||||
options = options.reverse_merge(defaults) # Allow the user to unset default values: Eg.: :significant => false
|
||||
precision = options.delete :precision
|
||||
significant = options.delete :significant
|
||||
strip_insignificant_zeros = options.delete :strip_insignificant_zeros
|
||||
|
||||
begin
|
||||
value = Float(number)
|
||||
rescue ArgumentError, TypeError
|
||||
value = nil
|
||||
end
|
||||
|
||||
if value
|
||||
rounded_number = BigDecimal.new((Float(number) * (10 ** precision)).to_s).round.to_f / 10 ** precision
|
||||
number_with_delimiter("%01.#{precision}f" % rounded_number,
|
||||
:separator => separator,
|
||||
:delimiter => delimiter)
|
||||
if significant and precision > 0
|
||||
digits = (Math.log10(number) + 1).floor
|
||||
rounded_number = BigDecimal.new((number / 10 ** (digits - precision)).to_s).round.to_f * 10 ** (digits - precision)
|
||||
precision = precision - digits
|
||||
precision = precision > 0 ? precision : 0 #don't let it be negative
|
||||
else
|
||||
number
|
||||
rounded_number = BigDecimal.new((number * (10 ** precision)).to_s).round.to_f / 10 ** precision
|
||||
end
|
||||
formatted_number = number_with_delimiter("%01.#{precision}f" % rounded_number, options)
|
||||
if strip_insignificant_zeros
|
||||
escaped_separator = Regexp.escape(options[:separator])
|
||||
formatted_number.sub(/(#{escaped_separator})(\d*[1-9])?0+\z/, '\1\2').sub(/#{escaped_separator}\z/, '').html_safe
|
||||
else
|
||||
formatted_number
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb].freeze
|
||||
|
||||
# Formats the bytes in +size+ into a more understandable representation
|
||||
# Formats the bytes in +number+ into a more understandable representation
|
||||
# (e.g., giving it 1500 yields 1.5 KB). This method is useful for
|
||||
# reporting file sizes to users. This method returns nil if
|
||||
# +size+ cannot be converted into a number. You can customize the
|
||||
# reporting file sizes to users. You can customize the
|
||||
# format in the +options+ hash.
|
||||
#
|
||||
# ==== Options
|
||||
# * <tt>:precision</tt> - Sets the level of precision (defaults to 1).
|
||||
# * <tt>:separator</tt> - Sets the separator between the units (defaults to ".").
|
||||
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to "").
|
||||
# See <tt>number_to_human</tt> if you want to pretty-print a generic number.
|
||||
#
|
||||
# ==== Options
|
||||
# * <tt>:precision</tt> - Sets the precision of the number (defaults to 3).
|
||||
# * <tt>:significant</tt> - If +true+, precision will be the # of significant_digits. If +false+, the # of fractional digits (defaults to +true+)
|
||||
# * <tt>:separator</tt> - Sets the separator between the fractional and integer digits (defaults to ".").
|
||||
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to "").
|
||||
# * <tt>:strip_insignificant_zeros</tt> - If +true+ removes insignificant zeros after the decimal separator (defaults to +true+)
|
||||
# ==== Examples
|
||||
# number_to_human_size(123) # => 123 Bytes
|
||||
# number_to_human_size(1234) # => 1.2 KB
|
||||
# number_to_human_size(1234) # => 1.21 KB
|
||||
# number_to_human_size(12345) # => 12.1 KB
|
||||
# number_to_human_size(1234567) # => 1.2 MB
|
||||
# number_to_human_size(1234567890) # => 1.1 GB
|
||||
# number_to_human_size(1234567890123) # => 1.1 TB
|
||||
# number_to_human_size(1234567, :precision => 2) # => 1.18 MB
|
||||
# number_to_human_size(483989, :precision => 0) # => 473 KB
|
||||
# number_to_human_size(1234567, :precision => 2, :separator => ',') # => 1,18 MB
|
||||
# number_to_human_size(1234567) # => 1.18 MB
|
||||
# number_to_human_size(1234567890) # => 1.15 GB
|
||||
# number_to_human_size(1234567890123) # => 1.12 TB
|
||||
# number_to_human_size(1234567, :precision => 2) # => 1.2 MB
|
||||
# number_to_human_size(483989, :precision => 2) # => 470 KB
|
||||
# number_to_human_size(1234567, :precision => 2, :separator => ',') # => 1,2 MB
|
||||
#
|
||||
# Zeros after the decimal point are always stripped out, regardless of the
|
||||
# specified precision:
|
||||
# helper.number_to_human_size(1234567890123, :precision => 5) # => "1.12283 TB"
|
||||
# helper.number_to_human_size(524288000, :precision=>5) # => "500 MB"
|
||||
# Unsignificant zeros after the fractional separator are stripped out by default (set
|
||||
# <tt>:strip_insignificant_zeros</tt> to +false+ to change that):
|
||||
# number_to_human_size(1234567890123, :precision => 5) # => "1.1229 TB"
|
||||
# number_to_human_size(524288000, :precision=>5) # => "500 MB"
|
||||
#
|
||||
# You can still use <tt>number_to_human_size</tt> with the old API that accepts the
|
||||
# +precision+ as its optional second parameter:
|
||||
# number_to_human_size(1234567, 2) # => 1.18 MB
|
||||
# number_to_human_size(483989, 0) # => 473 KB
|
||||
# number_to_human_size(1234567, 1) # => 1 MB
|
||||
# number_to_human_size(483989, 2) # => 470 KB
|
||||
def number_to_human_size(number, *args)
|
||||
return nil if number.nil?
|
||||
|
||||
options = args.extract_options!
|
||||
options.symbolize_keys!
|
||||
|
||||
defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {}
|
||||
human = I18n.translate(:'number.human.format', :locale => options[:locale], :raise => true) rescue {}
|
||||
number = begin
|
||||
Float(number)
|
||||
rescue ArgumentError, TypeError
|
||||
if options[:raise]
|
||||
raise InvalidNumberError, number
|
||||
else
|
||||
return number
|
||||
end
|
||||
end
|
||||
|
||||
defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {})
|
||||
human = I18n.translate(:'number.human.format', :locale => options[:locale], :default => {})
|
||||
defaults = defaults.merge(human)
|
||||
|
||||
unless args.empty?
|
||||
ActiveSupport::Deprecation.warn('number_to_human_size takes an option hash ' +
|
||||
'instead of a separate precision argument.', caller)
|
||||
precision = args[0] || defaults[:precision]
|
||||
options[:precision] ||= args[0] if args[0]
|
||||
end
|
||||
|
||||
precision ||= (options[:precision] || defaults[:precision])
|
||||
separator ||= (options[:separator] || defaults[:separator])
|
||||
delimiter ||= (options[:delimiter] || defaults[:delimiter])
|
||||
options = options.reverse_merge(defaults)
|
||||
#for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
|
||||
options[:strip_insignificant_zeros] = true if not options.key?(:strip_insignificant_zeros)
|
||||
|
||||
storage_units_format = I18n.translate(:'number.human.storage_units.format', :locale => options[:locale], :raise => true)
|
||||
|
||||
if number.to_i < 1024
|
||||
unit = I18n.translate(:'number.human.storage_units.units.byte', :locale => options[:locale], :count => number.to_i, :raise => true)
|
||||
storage_units_format.gsub(/%n/, number.to_i.to_s).gsub(/%u/, unit)
|
||||
storage_units_format.gsub(/%n/, number.to_i.to_s).gsub(/%u/, unit).html_safe
|
||||
else
|
||||
max_exp = STORAGE_UNITS.size - 1
|
||||
number = Float(number)
|
||||
exponent = (Math.log(number) / Math.log(1024)).to_i # Convert to base 1024
|
||||
exponent = max_exp if exponent > max_exp # we need this to avoid overflow for the highest unit
|
||||
number /= 1024 ** exponent
|
||||
@ -295,15 +366,138 @@ def number_to_human_size(number, *args)
|
||||
unit_key = STORAGE_UNITS[exponent]
|
||||
unit = I18n.translate(:"number.human.storage_units.units.#{unit_key}", :locale => options[:locale], :count => number, :raise => true)
|
||||
|
||||
escaped_separator = Regexp.escape(separator)
|
||||
formatted_number = number_with_precision(number,
|
||||
:precision => precision,
|
||||
:separator => separator,
|
||||
:delimiter => delimiter
|
||||
).sub(/(#{escaped_separator})(\d*[1-9])?0+\z/, '\1\2').sub(/#{escaped_separator}\z/, '')
|
||||
storage_units_format.gsub(/%n/, formatted_number).gsub(/%u/, unit)
|
||||
formatted_number = number_with_precision(number, options)
|
||||
storage_units_format.gsub(/%n/, formatted_number).gsub(/%u/, unit).html_safe
|
||||
end
|
||||
end
|
||||
|
||||
DECIMAL_UNITS = {0 => :unit, 1 => :ten, 2 => :hundred, 3 => :thousand, 6 => :million, 9 => :billion, 12 => :trillion, 15 => :quadrillion,
|
||||
-1 => :deci, -2 => :centi, -3 => :mili, -6 => :micro, -9 => :nano, -12 => :pico, -15 => :femto}.freeze
|
||||
|
||||
# Pretty prints (formats and approximates) a number in a way it is more readable by humans
|
||||
# (eg.: 1200000000 becomes "1.2 Billion"). This is useful for numbers that
|
||||
# can get very large (and too hard to read).
|
||||
#
|
||||
# See <tt>number_to_human_size</tt> if you want to print a file size.
|
||||
#
|
||||
# You can also define you own unit-quantifier names if you want to use other decimal units
|
||||
# (eg.: 1500 becomes "1.5 kilometers", 0.150 becomes "150 mililiters", etc). You may define
|
||||
# a wide range of unit quantifiers, even fractional ones (centi, deci, mili, etc).
|
||||
#
|
||||
# ==== Options
|
||||
# * <tt>:precision</tt> - Sets the precision of the number (defaults to 3).
|
||||
# * <tt>:significant</tt> - If +true+, precision will be the # of significant_digits. If +false+, the # of fractional digits (defaults to +true+)
|
||||
# * <tt>:separator</tt> - Sets the separator between the fractional and integer digits (defaults to ".").
|
||||
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to "").
|
||||
# * <tt>:strip_insignificant_zeros</tt> - If +true+ removes insignificant zeros after the decimal separator (defaults to +true+)
|
||||
# * <tt>:units</tt> - A Hash of unit quantifier names. Or a string containing an i18n scope where to find this hash. It might have the following keys:
|
||||
# * *integers*: <tt>:unit</tt>, <tt>:ten</tt>, <tt>:hundred</tt>, <tt>:thousand</tt>, <tt>:million</tt>, <tt>:billion</tt>, <tt>:trillion</tt>, <tt>:quadrillion</tt>
|
||||
# * *fractionals*: <tt>:deci</tt>, <tt>:centi</tt>, <tt>:mili</tt>, <tt>:micro</tt>, <tt>:nano</tt>, <tt>:pico</tt>, <tt>:femto</tt>
|
||||
# * <tt>:format</tt> - Sets the format of the output string (defaults to "%n %u"). The field types are:
|
||||
#
|
||||
# %u The quantifier (ex.: 'thousand')
|
||||
# %n The number
|
||||
#
|
||||
# ==== Examples
|
||||
# number_to_human(123) # => "123"
|
||||
# number_to_human(1234) # => "1.23 Thousand"
|
||||
# number_to_human(12345) # => "12.3 Thousand"
|
||||
# number_to_human(1234567) # => "1.23 Million"
|
||||
# number_to_human(1234567890) # => "1.23 Billion"
|
||||
# number_to_human(1234567890123) # => "1.23 Trillion"
|
||||
# number_to_human(1234567890123456) # => "1.23 Quadrillion"
|
||||
# number_to_human(1234567890123456789) # => "1230 Quadrillion"
|
||||
# number_to_human(489939, :precision => 2) # => "490 Thousand"
|
||||
# number_to_human(489939, :precision => 4) # => "489.9 Thousand"
|
||||
# number_to_human(1234567, :precision => 4,
|
||||
# :significant => false) # => "1.2346 Million"
|
||||
# number_to_human(1234567, :precision => 1,
|
||||
# :separator => ',',
|
||||
# :significant => false) # => "1,2 Million"
|
||||
#
|
||||
# Unsignificant zeros after the decimal separator are stripped out by default (set
|
||||
# <tt>:strip_insignificant_zeros</tt> to +false+ to change that):
|
||||
# number_to_human(12345012345, :significant_digits => 6) # => "12.345 Billion"
|
||||
# number_to_human(500000000, :precision=>5) # => "500 Million"
|
||||
#
|
||||
# ==== Custom Unit Quantifiers
|
||||
#
|
||||
# You can also use your own custom unit quantifiers:
|
||||
# number_to_human(500000, :units => {:unit => "ml", :thousand => "lt"}) # => "500 lt"
|
||||
#
|
||||
# If in your I18n locale you have:
|
||||
# distance:
|
||||
# centi:
|
||||
# one: "centimeter"
|
||||
# other: "centimeters"
|
||||
# unit:
|
||||
# one: "meter"
|
||||
# other: "meters"
|
||||
# thousand:
|
||||
# one: "kilometer"
|
||||
# other: "kilometers"
|
||||
# billion: "gazilion-distance"
|
||||
#
|
||||
# Then you could do:
|
||||
#
|
||||
# number_to_human(543934, :units => :distance) # => "544 kilometers"
|
||||
# number_to_human(54393498, :units => :distance) # => "54400 kilometers"
|
||||
# number_to_human(54393498000, :units => :distance) # => "54.4 gazilion-distance"
|
||||
# number_to_human(343, :units => :distance, :precision => 1) # => "300 meters"
|
||||
# number_to_human(1, :units => :distance) # => "1 meter"
|
||||
# number_to_human(0.34, :units => :distance) # => "34 centimeters"
|
||||
#
|
||||
def number_to_human(number, options = {})
|
||||
options.symbolize_keys!
|
||||
|
||||
number = begin
|
||||
Float(number)
|
||||
rescue ArgumentError, TypeError
|
||||
if options[:raise]
|
||||
raise InvalidNumberError, number
|
||||
else
|
||||
return number
|
||||
end
|
||||
end
|
||||
|
||||
defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {})
|
||||
human = I18n.translate(:'number.human.format', :locale => options[:locale], :default => {})
|
||||
defaults = defaults.merge(human)
|
||||
|
||||
options = options.reverse_merge(defaults)
|
||||
#for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
|
||||
options[:strip_insignificant_zeros] = true if not options.key?(:strip_insignificant_zeros)
|
||||
|
||||
units = options.delete :units
|
||||
unit_exponents = case units
|
||||
when Hash
|
||||
units
|
||||
when String, Symbol
|
||||
I18n.translate(:"#{units}", :locale => options[:locale], :raise => true)
|
||||
when nil
|
||||
I18n.translate(:"number.human.decimal_units.units", :locale => options[:locale], :raise => true)
|
||||
else
|
||||
raise ArgumentError, ":units must be a Hash or String translation scope."
|
||||
end.keys.map{|e_name| DECIMAL_UNITS.invert[e_name] }.sort_by{|e| -e}
|
||||
|
||||
number_exponent = Math.log10(number).floor
|
||||
display_exponent = unit_exponents.find{|e| number_exponent >= e }
|
||||
number /= 10 ** display_exponent
|
||||
|
||||
unit = case units
|
||||
when Hash
|
||||
units[DECIMAL_UNITS[display_exponent]]
|
||||
when String, Symbol
|
||||
I18n.translate(:"#{units}.#{DECIMAL_UNITS[display_exponent]}", :locale => options[:locale], :count => number.to_i)
|
||||
else
|
||||
I18n.translate(:"number.human.decimal_units.units.#{DECIMAL_UNITS[display_exponent]}", :locale => options[:locale], :count => number.to_i)
|
||||
end
|
||||
|
||||
decimal_format = options[:format] || I18n.translate(:'number.human.decimal_units.format', :locale => options[:locale], :default => "%n %u")
|
||||
formatted_number = number_with_precision(number, options)
|
||||
decimal_format.gsub(/%n/, formatted_number).gsub(/%u/, unit).strip.html_safe
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -35,7 +35,7 @@ module Helpers
|
||||
#
|
||||
# ...through a form...
|
||||
#
|
||||
# <% form_remote_tag :url => '/shipping' do -%>
|
||||
# <%= form_remote_tag :url => '/shipping' do -%>
|
||||
# <div><%= submit_tag 'Recalculate Shipping' %></div>
|
||||
# <% end -%>
|
||||
#
|
||||
@ -102,39 +102,6 @@ module PrototypeHelper
|
||||
:form, :with, :update, :script, :type ]).merge(CALLBACKS)
|
||||
end
|
||||
|
||||
# Returns a button with the given +name+ text that'll trigger a JavaScript +function+ using the
|
||||
# onclick handler.
|
||||
#
|
||||
# The first argument +name+ is used as the button's value or display text.
|
||||
#
|
||||
# The next arguments are optional and may include the javascript function definition and a hash of html_options.
|
||||
#
|
||||
# The +function+ argument can be omitted in favor of an +update_page+
|
||||
# block, which evaluates to a string when the template is rendered
|
||||
# (instead of making an Ajax request first).
|
||||
#
|
||||
# The +html_options+ will accept a hash of html attributes for the link tag. Some examples are :class => "nav_button", :id => "articles_nav_button"
|
||||
#
|
||||
# Note: if you choose to specify the javascript function in a block, but would like to pass html_options, set the +function+ parameter to nil
|
||||
#
|
||||
# Examples:
|
||||
# button_to_function "Greeting", "alert('Hello world!')"
|
||||
# button_to_function "Delete", "if (confirm('Really?')) do_delete()"
|
||||
# button_to_function "Details" do |page|
|
||||
# page[:details].visual_effect :toggle_slide
|
||||
# end
|
||||
# button_to_function "Details", :class => "details_button" do |page|
|
||||
# page[:details].visual_effect :toggle_slide
|
||||
# end
|
||||
def button_to_function(name, *args, &block)
|
||||
html_options = args.extract_options!.symbolize_keys
|
||||
|
||||
function = block_given? ? update_page(&block) : args[0] || ''
|
||||
onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function};"
|
||||
|
||||
tag(:input, html_options.merge(:type => 'button', :value => name, :onclick => onclick))
|
||||
end
|
||||
|
||||
# Returns the JavaScript needed for a remote function.
|
||||
# Takes the same arguments as link_to_remote.
|
||||
#
|
||||
@ -180,13 +147,10 @@ def remote_function(options)
|
||||
# #include_helpers_from_context has nothing to overwrite.
|
||||
class JavaScriptGenerator #:nodoc:
|
||||
def initialize(context, &block) #:nodoc:
|
||||
context._evaluate_assigns_and_ivars
|
||||
@context, @lines = context, []
|
||||
@context.update_details(:formats => [:js, :html]) do
|
||||
include_helpers_from_context
|
||||
@context.with_output_buffer(@lines) do
|
||||
@context.instance_exec(self, &block)
|
||||
end
|
||||
include_helpers_from_context
|
||||
@context.with_output_buffer(@lines) do
|
||||
@context.instance_exec(self, &block)
|
||||
end
|
||||
end
|
||||
|
||||
@ -616,7 +580,7 @@ def method_missing(method, *arguments)
|
||||
# page.hide 'spinner'
|
||||
# end
|
||||
def update_page(&block)
|
||||
JavaScriptGenerator.new(@template, &block).to_s.html_safe
|
||||
JavaScriptGenerator.new(view_context, &block).to_s.html_safe
|
||||
end
|
||||
|
||||
# Works like update_page but wraps the generated JavaScript in a <script>
|
||||
@ -690,6 +654,10 @@ def initialize(generator, root = nil)
|
||||
@generator << root if root
|
||||
end
|
||||
|
||||
def is_a?(klass)
|
||||
klass == JavaScriptProxy
|
||||
end
|
||||
|
||||
private
|
||||
def method_missing(method, *arguments, &block)
|
||||
if method.to_s =~ /(.*)=$/
|
||||
@ -883,5 +851,3 @@ def initialize(generator, pattern)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
require 'action_view/helpers/javascript_helper'
|
||||
|
@ -4,7 +4,7 @@ module RecordTagHelper
|
||||
# Produces a wrapper DIV element with id and class parameters that
|
||||
# relate to the specified Active Record object. Usage example:
|
||||
#
|
||||
# <% div_for(@person, :class => "foo") do %>
|
||||
# <%= div_for(@person, :class => "foo") do %>
|
||||
# <%=h @person.name %>
|
||||
# <% end %>
|
||||
#
|
||||
@ -19,7 +19,7 @@ def div_for(record, *args, &block)
|
||||
# content_tag_for creates an HTML element with id and class parameters
|
||||
# that relate to the specified Active Record object. For example:
|
||||
#
|
||||
# <% content_tag_for(:tr, @person) do %>
|
||||
# <%= content_tag_for(:tr, @person) do %>
|
||||
# <td><%=h @person.first_name %></td>
|
||||
# <td><%=h @person.last_name %></td>
|
||||
# <% end %>
|
||||
@ -31,7 +31,7 @@ def div_for(record, *args, &block)
|
||||
#
|
||||
# If you require the HTML id attribute to have a prefix, you can specify it:
|
||||
#
|
||||
# <% content_tag_for(:tr, @person, :foo) do %> ...
|
||||
# <%= content_tag_for(:tr, @person, :foo) do %> ...
|
||||
#
|
||||
# produces:
|
||||
#
|
||||
@ -41,7 +41,7 @@ def div_for(record, *args, &block)
|
||||
# additional HTML attributes. If you specify a <tt>:class</tt> value, it will be combined
|
||||
# with the default class name for your object. For example:
|
||||
#
|
||||
# <% content_tag_for(:li, @person, :class => "bar") %>...
|
||||
# <%= content_tag_for(:li, @person, :class => "bar") %>...
|
||||
#
|
||||
# produces:
|
||||
#
|
||||
|
@ -65,7 +65,7 @@ def tag(name, options = nil, open = false, escape = true)
|
||||
# content_tag("select", options, :multiple => true)
|
||||
# # => <select multiple="multiple">...options...</select>
|
||||
#
|
||||
# <% content_tag :div, :class => "strong" do -%>
|
||||
# <%= content_tag :div, :class => "strong" do -%>
|
||||
# Hello world!
|
||||
# <% end -%>
|
||||
# # => <div class="strong">Hello world!</div>
|
||||
@ -109,7 +109,7 @@ def escape_once(html)
|
||||
|
||||
def content_tag_string(name, content, options, escape = true)
|
||||
tag_options = tag_options(options, escape) if options
|
||||
("<#{name}#{tag_options}>".html_safe << content.to_s).safe_concat("</#{name}>")
|
||||
"<#{name}#{tag_options}>#{ERB::Util.h(content)}</#{name}>".html_safe
|
||||
end
|
||||
|
||||
def tag_options(options, escape = true)
|
||||
|
@ -17,7 +17,7 @@ module TextHelper
|
||||
# concat "hello"
|
||||
# # is the equivalent of <%= "hello" %>
|
||||
#
|
||||
# if (logged_in == true):
|
||||
# if logged_in
|
||||
# concat "Logged in!"
|
||||
# else
|
||||
# concat link_to('login', :action => login)
|
||||
@ -29,7 +29,7 @@ def concat(string)
|
||||
end
|
||||
|
||||
def safe_concat(string)
|
||||
output_buffer.safe_concat(string)
|
||||
output_buffer.respond_to?(:safe_concat) ? output_buffer.safe_concat(string) : concat(string)
|
||||
end
|
||||
|
||||
# Truncates a given +text+ after a given <tt>:length</tt> if +text+ is longer than <tt>:length</tt>
|
||||
@ -415,7 +415,7 @@ def auto_link(text, *args, &block)#link = :all, html = {}, &block)
|
||||
# {:first => 'Emily', :middle => 'Shannon', :maiden => 'Pike', :last => 'Hicks'},
|
||||
# {:first => 'June', :middle => 'Dae', :last => 'Jones'}]
|
||||
# <% @items.each do |item| %>
|
||||
# <tr class="<%= cycle("even", "odd", :name => "row_class") -%>">
|
||||
# <tr class="<%= cycle("odd", "even", :name => "row_class") -%>">
|
||||
# <td>
|
||||
# <% item.values.each do |value| %>
|
||||
# <%# Create a named cycle "colors" %>
|
||||
@ -576,7 +576,7 @@ def auto_link_urls(text, html_options = {})
|
||||
# each email is yielded and the result is used as the link text.
|
||||
def auto_link_email_addresses(text, html_options = {})
|
||||
body = text.dup
|
||||
text.gsub(/([\w\.!#\$%\-+.]+@[A-Za-z0-9\-]+(\.[A-Za-z0-9\-]+)+)/) do
|
||||
text.gsub(/([\w\.!#\$%\-+]+@[A-Za-z0-9\-]+(\.[A-Za-z0-9\-]+)+)/) do
|
||||
text = $1
|
||||
|
||||
if body.match(/<a\b[^>]*>(.*)(#{Regexp.escape(text)})(.*)<\/a>/)
|
||||
|
@ -13,7 +13,7 @@ module TranslationHelper
|
||||
def translate(key, options = {})
|
||||
options[:raise] = true
|
||||
translation = I18n.translate(scope_key_by_partial(key), options)
|
||||
translation.is_a?(Array) ? translation.map { |entry| entry.html_safe } : translation.html_safe
|
||||
(translation.respond_to?(:join) ? translation.join : translation).html_safe
|
||||
rescue I18n::MissingTranslationData => e
|
||||
keys = I18n.normalize_keys(e.locale, e.key, e.options[:scope])
|
||||
content_tag('span', keys.join(', '), :class => 'translation_missing')
|
||||
@ -29,9 +29,10 @@ def localize(*args)
|
||||
private
|
||||
|
||||
def scope_key_by_partial(key)
|
||||
if (key.respond_to?(:join) ? key.join : key.to_s).first == "."
|
||||
strkey = key.respond_to?(:join) ? key.join : key.to_s
|
||||
if strkey.first == "."
|
||||
if @_virtual_path
|
||||
@_virtual_path.gsub(%r{/_?}, ".") + key.to_s
|
||||
@_virtual_path.gsub(%r{/_?}, ".") + strkey
|
||||
else
|
||||
raise "Cannot use t(#{key.inspect}) shortcut because path is not available"
|
||||
end
|
||||
|
@ -1,6 +1,7 @@
|
||||
require 'action_view/helpers/javascript_helper'
|
||||
require 'active_support/core_ext/array/access'
|
||||
require 'active_support/core_ext/hash/keys'
|
||||
require 'action_dispatch'
|
||||
|
||||
module ActionView
|
||||
module Helpers #:nodoc:
|
||||
@ -9,6 +10,9 @@ module Helpers #:nodoc:
|
||||
# This allows you to use the same format for links in views
|
||||
# and controllers.
|
||||
module UrlHelper
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
include ActionDispatch::Routing::UrlFor
|
||||
include JavaScriptHelper
|
||||
|
||||
# Need to map default url options to controller one.
|
||||
@ -16,6 +20,10 @@ def default_url_options(*args) #:nodoc:
|
||||
controller.send(:default_url_options, *args)
|
||||
end
|
||||
|
||||
def url_options
|
||||
controller.url_options
|
||||
end
|
||||
|
||||
# Returns the URL for the set of +options+ provided. This takes the
|
||||
# same options as +url_for+ in Action Controller (see the
|
||||
# documentation for <tt>ActionController::Base#url_for</tt>). Note that by default
|
||||
@ -162,7 +170,7 @@ def url_for(options = {})
|
||||
#
|
||||
# You can use a block as well if your link target is hard to fit into the name parameter. ERb example:
|
||||
#
|
||||
# <% link_to(@profile) do %>
|
||||
# <%= link_to(@profile) do %>
|
||||
# <strong><%= @profile.name %></strong> -- <span>Check it out!</span>
|
||||
# <% end %>
|
||||
# # => <a href="/profiles/1">
|
||||
@ -206,7 +214,7 @@ def link_to(*args, &block)
|
||||
if block_given?
|
||||
options = args.first || {}
|
||||
html_options = args.second
|
||||
safe_concat(link_to(capture(&block), options, html_options))
|
||||
link_to(capture(&block), options, html_options)
|
||||
else
|
||||
name = args[0]
|
||||
options = args[1] || {}
|
||||
@ -224,7 +232,7 @@ def link_to(*args, &block)
|
||||
end
|
||||
|
||||
href_attr = "href=\"#{url}\"" unless href
|
||||
("<a #{href_attr}#{tag_options}>".html_safe << (name || url)).safe_concat("</a>")
|
||||
"<a #{href_attr}#{tag_options}>#{ERB::Util.h(name || url)}</a>".html_safe
|
||||
end
|
||||
end
|
||||
|
||||
@ -578,8 +586,6 @@ def convert_options_to_data_attributes(options, html_options)
|
||||
add_confirm_to_attributes!(html_options, confirm) if confirm
|
||||
add_method_to_attributes!(html_options, method) if method
|
||||
|
||||
html_options["data-url"] = options[:url] if options.is_a?(Hash) && options[:url]
|
||||
|
||||
html_options
|
||||
end
|
||||
|
||||
|
@ -9,6 +9,11 @@
|
||||
delimiter: ","
|
||||
# Number of decimals, behind the separator (the number 1 with a precision of 2 gives: 1.00)
|
||||
precision: 3
|
||||
# If set to true, precision will mean the number of significant digits instead
|
||||
# of the number of decimal digits (1234 with precision 2 becomes 1200, 1.23543 becomes 1.2)
|
||||
significant: false
|
||||
# If set, the zeros after the decimal separator will always be stripped (eg.: 1.200 will be 1.2)
|
||||
strip_insignificant_zeros: false
|
||||
|
||||
# Used in number_to_currency()
|
||||
currency:
|
||||
@ -16,34 +21,43 @@
|
||||
# Where is the currency sign? %u is the currency unit, %n the number (default: $5.00)
|
||||
format: "%u%n"
|
||||
unit: "$"
|
||||
# These three are to override number.format and are optional
|
||||
# These five are to override number.format and are optional
|
||||
separator: "."
|
||||
delimiter: ","
|
||||
precision: 2
|
||||
significant: false
|
||||
strip_insignificant_zeros: false
|
||||
|
||||
# Used in number_to_percentage()
|
||||
percentage:
|
||||
format:
|
||||
# These three are to override number.format and are optional
|
||||
# These five are to override number.format and are optional
|
||||
# separator:
|
||||
delimiter: ""
|
||||
# precision:
|
||||
# significant: false
|
||||
# strip_insignificant_zeros: false
|
||||
|
||||
# Used in number_to_precision()
|
||||
precision:
|
||||
format:
|
||||
# These three are to override number.format and are optional
|
||||
# These five are to override number.format and are optional
|
||||
# separator:
|
||||
delimiter: ""
|
||||
# precision:
|
||||
# significant: false
|
||||
# strip_insignificant_zeros: false
|
||||
|
||||
# Used in number_to_human_size()
|
||||
# Used in number_to_human_size() and number_to_human()
|
||||
human:
|
||||
format:
|
||||
# These three are to override number.format and are optional
|
||||
# These five are to override number.format and are optional
|
||||
# separator:
|
||||
delimiter: ""
|
||||
precision: 1
|
||||
precision: 3
|
||||
significant: true
|
||||
strip_insignificant_zeros: true
|
||||
# Used in number_to_human_size()
|
||||
storage_units:
|
||||
# Storage units output formatting.
|
||||
# %u is the storage unit, %n is the number (default: 2 MB)
|
||||
@ -56,6 +70,31 @@
|
||||
mb: "MB"
|
||||
gb: "GB"
|
||||
tb: "TB"
|
||||
# Used in number_to_human()
|
||||
decimal_units:
|
||||
format: "%n %u"
|
||||
# Decimal units output formatting
|
||||
# By default we will only quantify some of the exponents
|
||||
# but the commented ones might be defined or overridden
|
||||
# by the user.
|
||||
units:
|
||||
# femto: Quadrillionth
|
||||
# pico: Trillionth
|
||||
# nano: Billionth
|
||||
# micro: Millionth
|
||||
# mili: Thousandth
|
||||
# centi: Hundredth
|
||||
# deci: Tenth
|
||||
unit: ""
|
||||
# ten:
|
||||
# one: Ten
|
||||
# other: Tens
|
||||
# hundred: Hundred
|
||||
thousand: Thousand
|
||||
million: Million
|
||||
billion: Billion
|
||||
trillion: Trillion
|
||||
quadrillion: Quadrillion
|
||||
|
||||
# Used in distance_of_time_in_words(), distance_of_time_in_words_to_now(), time_ago_in_words()
|
||||
datetime:
|
||||
|
@ -1,4 +1,4 @@
|
||||
require 'active_support/core_ext/object/try'
|
||||
require 'active_support/core_ext/array/wrap'
|
||||
require 'active_support/core_ext/object/blank'
|
||||
|
||||
module ActionView
|
||||
@ -11,38 +11,56 @@ class LookupContext #:nodoc:
|
||||
@@fallbacks = [FileSystemResolver.new(""), FileSystemResolver.new("/")]
|
||||
|
||||
mattr_accessor :registered_details
|
||||
self.registered_details = {}
|
||||
self.registered_details = []
|
||||
|
||||
def self.register_detail(name, options = {})
|
||||
registered_details[name] = lambda do |value|
|
||||
value = Array(value.presence || yield)
|
||||
value |= [nil] unless options[:allow_nil] == false
|
||||
value
|
||||
end
|
||||
def self.register_detail(name, options = {}, &block)
|
||||
self.registered_details << name
|
||||
Accessors.send :define_method, :"_#{name}_defaults", &block
|
||||
Accessors.module_eval <<-METHOD, __FILE__, __LINE__ + 1
|
||||
def #{name}
|
||||
@details[:#{name}]
|
||||
end
|
||||
|
||||
def #{name}=(value)
|
||||
value = Array.wrap(value.presence || _#{name}_defaults)
|
||||
|
||||
if value != @details[:#{name}]
|
||||
@details_key = nil
|
||||
@details = @details.dup if @details.frozen?
|
||||
@details[:#{name}] = value.freeze
|
||||
end
|
||||
end
|
||||
METHOD
|
||||
end
|
||||
|
||||
# Holds accessors for the registered details.
|
||||
module Accessors #:nodoc:
|
||||
end
|
||||
|
||||
register_detail(:formats) { Mime::SET.symbols }
|
||||
register_detail(:locale) { [I18n.locale] }
|
||||
|
||||
class DetailsKey #:nodoc:
|
||||
attr_reader :details
|
||||
alias :eql? :equal?
|
||||
alias :object_hash :hash
|
||||
|
||||
attr_reader :hash
|
||||
@details_keys = Hash.new
|
||||
|
||||
def self.get(details)
|
||||
@details_keys[details] ||= new(details)
|
||||
@details_keys[details.freeze] ||= new
|
||||
end
|
||||
|
||||
def initialize(details)
|
||||
@details, @hash = details, details.hash
|
||||
def initialize
|
||||
@hash = object_hash
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(view_paths, details = {})
|
||||
@details_key = nil
|
||||
@details, @details_key = { :handlers => default_handlers }, nil
|
||||
@frozen_formats = false
|
||||
self.view_paths = view_paths
|
||||
self.details = details
|
||||
self.update_details(details, true)
|
||||
end
|
||||
|
||||
module ViewPaths
|
||||
@ -55,18 +73,20 @@ def view_paths=(paths)
|
||||
end
|
||||
|
||||
def find(name, prefix = nil, partial = false)
|
||||
@view_paths.find(name, prefix, partial || false, details, details_key)
|
||||
@view_paths.find(*args_for_lookup(name, prefix, partial))
|
||||
end
|
||||
alias :find_template :find
|
||||
|
||||
def find_all(name, prefix = nil, partial = false)
|
||||
@view_paths.find_all(name, prefix, partial || false, details, details_key)
|
||||
@view_paths.find_all(*args_for_lookup(name, prefix, partial))
|
||||
end
|
||||
|
||||
def exists?(name, prefix = nil, partial = false)
|
||||
@view_paths.exists?(name, prefix, partial || false, details, details_key)
|
||||
@view_paths.exists?(*args_for_lookup(name, prefix, partial))
|
||||
end
|
||||
alias :template_exists? :exists?
|
||||
|
||||
# Add fallbacks to the view paths. Useful in cases you are rendering a file.
|
||||
# Add fallbacks to the view paths. Useful in cases you are rendering a :file.
|
||||
def with_fallbacks
|
||||
added_resolvers = 0
|
||||
self.class.fallbacks.each do |resolver|
|
||||
@ -78,73 +98,92 @@ def with_fallbacks
|
||||
ensure
|
||||
added_resolvers.times { view_paths.pop }
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def args_for_lookup(name, prefix, partial) #:nodoc:
|
||||
name, prefix = normalize_name(name, prefix)
|
||||
[name, prefix, partial || false, @details, details_key]
|
||||
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:
|
||||
name = name.to_s.gsub(handlers_regexp, '')
|
||||
parts = name.split('/')
|
||||
return parts.pop, [prefix, *parts].compact.join("/")
|
||||
end
|
||||
|
||||
def default_handlers #:nodoc:
|
||||
@default_handlers ||= Template::Handlers.extensions
|
||||
end
|
||||
|
||||
def handlers_regexp #:nodoc:
|
||||
@handlers_regexp ||= /\.(?:#{default_handlers.join('|')})$/
|
||||
end
|
||||
end
|
||||
|
||||
module Details
|
||||
attr_reader :details
|
||||
|
||||
def details=(details)
|
||||
@details = normalize_details(details)
|
||||
@details_key = nil if @details_key && @details_key.details != @details
|
||||
end
|
||||
|
||||
def details_key
|
||||
# Calculate the details key. Remove the handlers from calculation to improve performance
|
||||
# since the user cannot modify it explicitly.
|
||||
def details_key #:nodoc:
|
||||
@details_key ||= DetailsKey.get(@details)
|
||||
end
|
||||
|
||||
# Shortcut to read formats from details.
|
||||
def formats
|
||||
@details[:formats].compact
|
||||
# Freeze the current formats in the lookup context. By freezing them, you are guaranteeing
|
||||
# that next template lookups are not going to modify the formats. The controller can also
|
||||
# use this, to ensure that formats won't be further modified (as it does in respond_to blocks).
|
||||
def freeze_formats(formats, unless_frozen=false) #:nodoc:
|
||||
return if unless_frozen && @frozen_formats
|
||||
self.formats = formats
|
||||
@frozen_formats = true
|
||||
end
|
||||
|
||||
# Shortcut to set formats in details.
|
||||
# Overload formats= to reject [:"*/*"] values.
|
||||
def formats=(value)
|
||||
self.details = @details.merge(:formats => value)
|
||||
value = nil if value == [:"*/*"]
|
||||
value << :html if value == [:js]
|
||||
super(value)
|
||||
end
|
||||
|
||||
# Shortcut to read locale.
|
||||
# Overload locale to return a symbol instead of array
|
||||
def locale
|
||||
I18n.locale
|
||||
@details[:locale].first
|
||||
end
|
||||
|
||||
# Shortcut to set locale in details and I18n.
|
||||
# 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
|
||||
# acting as proxy, which we need to skip.
|
||||
def locale=(value)
|
||||
I18n.locale = value
|
||||
|
||||
unless I18n.config.respond_to?(:lookup_context)
|
||||
self.details = @details.merge(:locale => value)
|
||||
if value
|
||||
config = I18n.config.respond_to?(:i18n_config) ? I18n.config.i18n_config : I18n.config
|
||||
config.locale = value
|
||||
end
|
||||
super(I18n.locale)
|
||||
end
|
||||
|
||||
# Update the details keys by merging the given hash into the current
|
||||
# details hash. If a block is given, the details are modified just during
|
||||
# the execution of the block and reverted to the previous value after.
|
||||
def update_details(new_details)
|
||||
old_details = @details
|
||||
self.details = old_details.merge(new_details)
|
||||
def update_details(new_details, force=false)
|
||||
old_details = @details.dup
|
||||
|
||||
registered_details.each do |key|
|
||||
send(:"#{key}=", new_details[key]) if force || new_details.key?(key)
|
||||
end
|
||||
|
||||
if block_given?
|
||||
begin
|
||||
yield
|
||||
ensure
|
||||
self.details = old_details
|
||||
@details = old_details
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def normalize_details(details)
|
||||
details = details.dup
|
||||
# TODO: Refactor this concern out of the resolver
|
||||
details.delete(:formats) if details[:formats] == [:"*/*"]
|
||||
self.class.registered_details.each do |k, v|
|
||||
details[k] = v.call(details[k])
|
||||
end
|
||||
details.freeze
|
||||
end
|
||||
end
|
||||
|
||||
include Accessors
|
||||
include Details
|
||||
include ViewPaths
|
||||
end
|
||||
|
@ -3,10 +3,10 @@
|
||||
|
||||
module ActionView
|
||||
class Railtie < Rails::Railtie
|
||||
railtie_name :action_view
|
||||
config.action_view = ActiveSupport::OrderedOptions.new
|
||||
|
||||
require "action_view/railties/log_subscriber"
|
||||
log_subscriber ActionView::Railties::LogSubscriber.new
|
||||
log_subscriber :action_view, ActionView::Railties::LogSubscriber.new
|
||||
|
||||
initializer "action_view.cache_asset_timestamps" do |app|
|
||||
unless app.config.cache_classes
|
||||
@ -15,5 +15,13 @@ class Railtie < Rails::Railtie
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
initializer "action_view.set_configs" do |app|
|
||||
ActionView.base_hook do
|
||||
app.config.action_view.each do |k,v|
|
||||
send "#{k}=", v
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -1,8 +1,5 @@
|
||||
require 'active_support/core_ext/object/try'
|
||||
|
||||
module ActionView
|
||||
module Layouts
|
||||
|
||||
# You can think of a layout as a method that is called with a block. _layout_for
|
||||
# returns the contents that are yielded to the layout. If the user calls yield
|
||||
# :some_name, the block, by default, returns content_for(:some_name). If the user
|
||||
@ -13,7 +10,7 @@ module Layouts
|
||||
# ==== Example
|
||||
#
|
||||
# # The template
|
||||
# <% render :layout => "my_layout" do %>Content<% end %>
|
||||
# <%= render :layout => "my_layout" do %>Content<% end %>
|
||||
#
|
||||
# # The layout
|
||||
# <html><% yield %></html>
|
||||
@ -27,7 +24,7 @@ module Layouts
|
||||
# ==== Example
|
||||
#
|
||||
# # The template
|
||||
# <% render :layout => "my_layout" do |customer| %>Hello <%= customer.name %><% end %>
|
||||
# <%= render :layout => "my_layout" do |customer| %>Hello <%= customer.name %><% end %>
|
||||
#
|
||||
# # The layout
|
||||
# <html><% yield Struct.new(:name).new("David") %></html>
|
||||
@ -46,17 +43,28 @@ def _layout_for(name = nil, &block) #:nodoc:
|
||||
# This is the method which actually finds the layout using details in the lookup
|
||||
# context object. If no layout is found, it checkes if at least a layout with
|
||||
# the given name exists across all details before raising the error.
|
||||
def _find_layout(layout) #:nodoc:
|
||||
#
|
||||
# If self.formats contains several formats, just the first one is considered in
|
||||
# the layout lookup.
|
||||
def find_layout(layout)
|
||||
begin
|
||||
layout =~ /^\// ?
|
||||
with_fallbacks { find(layout) } : find(layout)
|
||||
if formats.size == 1
|
||||
_find_layout(layout)
|
||||
else
|
||||
update_details(:formats => self.formats.first){ _find_layout(layout) }
|
||||
end
|
||||
rescue ActionView::MissingTemplate => e
|
||||
update_details(:formats => nil) do
|
||||
raise unless exists?(layout)
|
||||
raise unless template_exists?(layout)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def _find_layout(layout) #:nodoc:
|
||||
layout =~ /^\// ?
|
||||
with_fallbacks { find_template(layout) } : find_template(layout)
|
||||
end
|
||||
|
||||
# Contains the logic that actually renders the layout.
|
||||
def _render_layout(layout, locals, &block) #:nodoc:
|
||||
layout.render(self, locals){ |*name| _layout_for(*name, &block) }
|
||||
|
@ -123,7 +123,7 @@ module ActionView
|
||||
# You can also apply a layout to a block within any template:
|
||||
#
|
||||
# <%# app/views/users/_chief.html.erb &>
|
||||
# <% render(:layout => "administrator", :locals => { :user => chief }) do %>
|
||||
# <%= render(:layout => "administrator", :locals => { :user => chief }) do %>
|
||||
# Title: <%= chief.title %>
|
||||
# <% end %>
|
||||
#
|
||||
@ -146,7 +146,7 @@ module ActionView
|
||||
# </div>
|
||||
#
|
||||
# <%# app/views/users/index.html.erb &>
|
||||
# <% render :layout => @users do |user| %>
|
||||
# <%= render :layout => @users do |user| %>
|
||||
# Title: <%= user.title %>
|
||||
# <% end %>
|
||||
#
|
||||
@ -162,7 +162,7 @@ module ActionView
|
||||
# </div>
|
||||
#
|
||||
# <%# app/views/users/index.html.erb &>
|
||||
# <% render :layout => @users do |user, section| %>
|
||||
# <%= render :layout => @users do |user, section| %>
|
||||
# <%- case section when :header -%>
|
||||
# Title: <%= user.title %>
|
||||
# <%- when :footer -%>
|
||||
@ -294,7 +294,7 @@ def collection
|
||||
def find_template(path=@path)
|
||||
return path unless path.is_a?(String)
|
||||
prefix = @view.controller_path unless path.include?(?/)
|
||||
@view.find(path, prefix, true)
|
||||
@view.find_template(path, prefix, true)
|
||||
end
|
||||
|
||||
def partial_path(object = @object)
|
||||
|
@ -12,14 +12,17 @@ module Rendering
|
||||
#
|
||||
# If no options hash is passed or :update specified, the default is to render a partial and use the second parameter
|
||||
# as the locals hash.
|
||||
def render(options = {}, locals = {}, &block) #:nodoc:
|
||||
def render(options = {}, locals = {}, &block)
|
||||
case options
|
||||
when Hash
|
||||
if block_given?
|
||||
content = _render_partial(options.merge(:partial => options[:layout]), &block)
|
||||
safe_concat(content)
|
||||
_render_partial(options.merge(:partial => options[:layout]), &block)
|
||||
elsif options.key?(:partial)
|
||||
_render_partial(options)
|
||||
else
|
||||
_render(options)
|
||||
template = _determine_template(options)
|
||||
lookup_context.freeze_formats(template.formats, true)
|
||||
_render_template(template, options[:layout], options)
|
||||
end
|
||||
when :update
|
||||
update_page(&block)
|
||||
@ -28,44 +31,18 @@ def render(options = {}, locals = {}, &block) #:nodoc:
|
||||
end
|
||||
end
|
||||
|
||||
# This is the API to render a ViewContext's template from a controller.
|
||||
def render_template(options, &block)
|
||||
_evaluate_assigns_and_ivars
|
||||
|
||||
# TODO Layout for partials should be handled here, because inside the
|
||||
# partial renderer it looks for the layout as a partial.
|
||||
if options.key?(:partial) && options[:layout]
|
||||
options[:layout] = _find_layout(options[:layout])
|
||||
end
|
||||
|
||||
_render(options, &block)
|
||||
end
|
||||
|
||||
# This method holds the common render logic for both controllers and
|
||||
# views rendering stacks.
|
||||
def _render(options) #:nodoc:
|
||||
if options.key?(:partial)
|
||||
_render_partial(options)
|
||||
else
|
||||
template = _determine_template(options)
|
||||
yield template if block_given?
|
||||
_render_template(template, options[:layout], options)
|
||||
end
|
||||
end
|
||||
|
||||
# Determine the template to be rendered using the given options.
|
||||
def _determine_template(options) #:nodoc:
|
||||
if options.key?(:inline)
|
||||
handler = Template.handler_class_for_extension(options[:type] || "erb")
|
||||
Template.new(options[:inline], "inline template", handler, {})
|
||||
elsif options.key?(:text)
|
||||
Template::Text.new(options[:text], self.formats.try(:first))
|
||||
elsif options.key?(:_template)
|
||||
options[:_template]
|
||||
Template::Text.new(options[:text], formats.try(:first))
|
||||
elsif options.key?(:file)
|
||||
with_fallbacks { find(options[:file], options[:prefix]) }
|
||||
with_fallbacks { find_template(options[:file], options[:prefix]) }
|
||||
elsif options.key?(:template)
|
||||
find(options[:template], options[:prefix])
|
||||
options[:template].respond_to?(:render) ?
|
||||
options[:template] : find_template(options[:template], options[:prefix])
|
||||
end
|
||||
end
|
||||
|
||||
@ -73,22 +50,17 @@ def _determine_template(options) #:nodoc:
|
||||
# supplied as well.
|
||||
def _render_template(template, layout = nil, options = {}) #:nodoc:
|
||||
locals = options[:locals] || {}
|
||||
layout = _find_layout(layout) if layout
|
||||
layout = find_layout(layout) if layout
|
||||
|
||||
ActiveSupport::Notifications.instrument("action_view.render_template",
|
||||
:identifier => template.identifier, :layout => layout.try(:identifier)) do
|
||||
:identifier => template.identifier, :layout => layout.try(:virtual_path)) do
|
||||
|
||||
content = template.render(self, locals) { |*name| _layout_for(*name) }
|
||||
@_content_for[:layout] = content
|
||||
|
||||
if layout
|
||||
@_layout = layout.identifier
|
||||
content = _render_layout(layout, locals)
|
||||
end
|
||||
|
||||
content = _render_layout(layout, locals) if layout
|
||||
content
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
@ -1,8 +1,7 @@
|
||||
# encoding: utf-8
|
||||
# This is so that templates compiled in this file are UTF-8
|
||||
|
||||
require 'set'
|
||||
require "action_view/template/resolver"
|
||||
require 'active_support/core_ext/array/wrap'
|
||||
require 'active_support/core_ext/object/blank'
|
||||
|
||||
module ActionView
|
||||
class Template
|
||||
@ -24,19 +23,20 @@ def initialize(source, identifier, handler, details)
|
||||
@identifier = identifier
|
||||
@handler = handler
|
||||
|
||||
@partial = details[:partial]
|
||||
@virtual_path = details[:virtual_path]
|
||||
@method_names = {}
|
||||
|
||||
format = details[:format]
|
||||
format ||= handler.default_format.to_sym if handler.respond_to?(:default_format)
|
||||
format ||= :html
|
||||
@formats = [format.to_sym]
|
||||
format = details[:format] || :html
|
||||
@formats = Array.wrap(format).map(&:to_sym)
|
||||
end
|
||||
|
||||
def render(view, locals, &block)
|
||||
method_name = compile(locals, view)
|
||||
view.send(method_name, locals, &block)
|
||||
# Notice that we use a bang in this instrumentation because you don't want to
|
||||
# consume this in production. This is only slow if it's being listened to.
|
||||
ActiveSupport::Notifications.instrument("action_view.render_template!", :virtual_path => @virtual_path) do
|
||||
method_name = compile(locals, view)
|
||||
view.send(method_name, locals, &block)
|
||||
end
|
||||
rescue Exception => e
|
||||
if e.is_a?(Template::Error)
|
||||
e.sub_template_of(self)
|
||||
@ -58,10 +58,6 @@ def counter_name
|
||||
@counter_name ||= "#{variable_name}_counter".to_sym
|
||||
end
|
||||
|
||||
def partial?
|
||||
@partial
|
||||
end
|
||||
|
||||
def inspect
|
||||
if defined?(Rails.root)
|
||||
identifier.sub("#{Rails.root}/", '')
|
||||
|
@ -1,6 +1,26 @@
|
||||
require "active_support/core_ext/enumerable"
|
||||
|
||||
module ActionView
|
||||
class ActionViewError < StandardError #:nodoc:
|
||||
end
|
||||
|
||||
class MissingTemplate < ActionViewError #:nodoc:
|
||||
attr_reader :path
|
||||
|
||||
def initialize(paths, path, details, partial)
|
||||
@path = path
|
||||
display_paths = paths.compact.map{ |p| p.to_s.inspect }.join(", ")
|
||||
template_type = if partial
|
||||
"partial"
|
||||
elsif path =~ /layouts/i
|
||||
'layout'
|
||||
else
|
||||
'template'
|
||||
end
|
||||
|
||||
super("Missing #{template_type} #{path} with #{details.inspect} in view paths #{display_paths}")
|
||||
end
|
||||
end
|
||||
class Template
|
||||
# The Template::Error exception is raised when the compilation of the template fails. This exception then gathers a
|
||||
# bunch of intimate details and uses it to report a very precise exception message.
|
||||
@ -73,11 +93,11 @@ def line_number
|
||||
end
|
||||
|
||||
def to_s
|
||||
"\n#{self.class} (#{message}) #{source_location}:\n" +
|
||||
"\n#{self.class} (#{message}) #{source_location}:\n" +
|
||||
"#{source_extract}\n #{clean_backtrace.join("\n ")}\n\n"
|
||||
end
|
||||
|
||||
# don't do anything nontrivial here. Any raised exception from here becomes fatal
|
||||
# don't do anything nontrivial here. Any raised exception from here becomes fatal
|
||||
# (and can't be rescued).
|
||||
def backtrace
|
||||
@backtrace
|
||||
|
@ -3,46 +3,16 @@
|
||||
require 'erubis'
|
||||
|
||||
module ActionView
|
||||
class OutputBuffer
|
||||
def initialize
|
||||
@buffer = ActiveSupport::SafeBuffer.new
|
||||
end
|
||||
|
||||
def safe_concat(value)
|
||||
@buffer.safe_concat(value)
|
||||
end
|
||||
|
||||
class OutputBuffer < ActiveSupport::SafeBuffer
|
||||
def <<(value)
|
||||
@buffer << value.to_s
|
||||
super(value.to_s)
|
||||
end
|
||||
alias :append= :<<
|
||||
|
||||
def length
|
||||
@buffer.length
|
||||
end
|
||||
|
||||
def [](*args)
|
||||
@buffer[*args]
|
||||
end
|
||||
|
||||
def to_s
|
||||
@buffer.to_s
|
||||
end
|
||||
|
||||
def to_str
|
||||
@buffer.to_str
|
||||
end
|
||||
|
||||
def empty?
|
||||
@buffer.empty?
|
||||
end
|
||||
|
||||
def html_safe?
|
||||
@buffer.html_safe?
|
||||
end
|
||||
|
||||
if "".respond_to?(:force_encoding)
|
||||
def force_encoding(encoding)
|
||||
@buffer.force_encoding(encoding)
|
||||
def append_if_string=(value)
|
||||
if value.is_a?(String) && !value.is_a?(NonConcattingString)
|
||||
ActiveSupport::Deprecation.warn("<% %> style block helpers are deprecated. Please use <%= %>", caller)
|
||||
self << value
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -58,16 +28,26 @@ def add_text(src, text)
|
||||
src << "@output_buffer.safe_concat('" << escape_text(text) << "');"
|
||||
end
|
||||
|
||||
BLOCK_EXPR = /(do|\{)(\s*\|[^|]*\|)?\s*\Z/
|
||||
|
||||
def add_expr_literal(src, code)
|
||||
if code =~ /(do|\{)(\s*\|[^|]*\|)?\s*\Z/
|
||||
src << '@output_buffer << ' << code
|
||||
if code =~ BLOCK_EXPR
|
||||
src << '@output_buffer.append= ' << code
|
||||
else
|
||||
src << '@output_buffer << (' << code << ');'
|
||||
src << '@output_buffer.append= (' << code << ');'
|
||||
end
|
||||
end
|
||||
|
||||
def add_stmt(src, code)
|
||||
if code =~ BLOCK_EXPR
|
||||
src << '@output_buffer.append_if_string= ' << code
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def add_expr_escaped(src, code)
|
||||
src << '@output_buffer << ' << escaped_expr(code) << ';'
|
||||
src << '@output_buffer.append= ' << escaped_expr(code) << ';'
|
||||
end
|
||||
|
||||
def add_postamble(src)
|
||||
|
@ -6,8 +6,7 @@ class RJS < Template::Handler
|
||||
self.default_format = Mime::JS
|
||||
|
||||
def compile(template)
|
||||
"controller.response.content_type ||= Mime::JS;" +
|
||||
"update_page do |page|;#{template.source}\nend"
|
||||
"update_page do |page|;#{template.source}\nend"
|
||||
end
|
||||
|
||||
def default_format
|
||||
|
@ -1,6 +1,5 @@
|
||||
require "pathname"
|
||||
require "active_support/core_ext/class"
|
||||
require "active_support/core_ext/array/wrap"
|
||||
require "action_view/template"
|
||||
|
||||
module ActionView
|
||||
@ -14,15 +13,8 @@ def clear_cache
|
||||
@cached.clear
|
||||
end
|
||||
|
||||
def find(*args)
|
||||
find_all(*args).first
|
||||
end
|
||||
|
||||
# Normalizes the arguments and passes it on to find_template.
|
||||
def find_all(name, prefix=nil, partial=false, details={}, key=nil)
|
||||
name, prefix = normalize_name(name, prefix)
|
||||
details = details.merge(:handlers => default_handlers)
|
||||
|
||||
cached(key, prefix, name, partial) do
|
||||
find_templates(name, prefix, partial, details)
|
||||
end
|
||||
@ -34,10 +26,6 @@ def caching?
|
||||
@caching ||= !defined?(Rails.application) || Rails.application.config.cache_classes
|
||||
end
|
||||
|
||||
def default_handlers
|
||||
Template::Handlers.extensions + [nil]
|
||||
end
|
||||
|
||||
# This is what child classes implement. No defaults are needed
|
||||
# because Resolver guarantees that the arguments are present and
|
||||
# normalized.
|
||||
@ -45,17 +33,6 @@ def find_templates(name, prefix, partial, details)
|
||||
raise NotImplementedError
|
||||
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)
|
||||
handlers = Template::Handlers.extensions.join('|')
|
||||
name = name.to_s.gsub(/\.(?:#{handlers})$/, '')
|
||||
|
||||
parts = name.split('/')
|
||||
return parts.pop, [prefix, *parts].compact.join("/")
|
||||
end
|
||||
|
||||
def cached(key, prefix, name, partial)
|
||||
return yield unless key && caching?
|
||||
scope = @cached[key][prefix][name]
|
||||
@ -79,7 +56,7 @@ def to_s
|
||||
|
||||
def find_templates(name, prefix, partial, details)
|
||||
path = build_path(name, prefix, partial, details)
|
||||
query(partial, path, EXTENSION_ORDER.map { |ext| details[ext] })
|
||||
query(path, EXTENSION_ORDER.map { |ext| details[ext] }, details[:formats])
|
||||
end
|
||||
|
||||
def build_path(name, prefix, partial, details)
|
||||
@ -89,26 +66,32 @@ def build_path(name, prefix, partial, details)
|
||||
path
|
||||
end
|
||||
|
||||
def query(partial, path, exts)
|
||||
def query(path, exts, formats)
|
||||
query = File.join(@path, path)
|
||||
|
||||
exts.each do |ext|
|
||||
query << '{' << ext.map {|e| e && ".#{e}" }.join(',') << '}'
|
||||
query << '{' << ext.map {|e| e && ".#{e}" }.join(',') << ',}'
|
||||
end
|
||||
|
||||
Dir[query].reject { |p| File.directory?(p) }.map do |p|
|
||||
handler, format = extract_handler_and_format(p)
|
||||
handler, format = extract_handler_and_format(p, formats)
|
||||
Template.new(File.read(p), File.expand_path(p), handler,
|
||||
:partial => partial, :virtual_path => path, :format => format)
|
||||
:virtual_path => path, :format => format)
|
||||
end
|
||||
end
|
||||
|
||||
def extract_handler_and_format(path)
|
||||
# Extract handler and formats from path. If a format cannot be a found neither
|
||||
# from the path, or the handler, we should return the array of formats given
|
||||
# to the resolver.
|
||||
def extract_handler_and_format(path, default_formats)
|
||||
pieces = File.basename(path).split(".")
|
||||
pieces.shift
|
||||
|
||||
handler = Template.handler_class_for_extension(pieces.pop)
|
||||
format = pieces.last && Mime[pieces.last] && pieces.pop.to_sym
|
||||
handler = Template.handler_class_for_extension(pieces.pop)
|
||||
format = pieces.last && Mime[pieces.last] && pieces.pop.to_sym
|
||||
format ||= handler.default_format if handler.respond_to?(:default_format)
|
||||
format ||= default_formats
|
||||
|
||||
[handler, format]
|
||||
end
|
||||
end
|
||||
|
@ -1,10 +1,12 @@
|
||||
module ActionView #:nodoc:
|
||||
class Template
|
||||
class Text < String #:nodoc:
|
||||
def initialize(string, content_type = nil)
|
||||
attr_accessor :mime_type
|
||||
|
||||
def initialize(string, mime_type = nil)
|
||||
super(string.to_s)
|
||||
@content_type = Mime[content_type] || content_type if content_type
|
||||
@content_type ||= Mime::TEXT
|
||||
@mime_type = Mime[mime_type] || mime_type if mime_type
|
||||
@mime_type ||= Mime::TEXT
|
||||
end
|
||||
|
||||
def identifier
|
||||
@ -19,12 +21,8 @@ def render(*args)
|
||||
to_s
|
||||
end
|
||||
|
||||
def mime_type
|
||||
@content_type
|
||||
end
|
||||
|
||||
def formats
|
||||
[@content_type.to_sym]
|
||||
[@mime_type.to_sym]
|
||||
end
|
||||
|
||||
def partial?
|
||||
|
@ -2,29 +2,10 @@
|
||||
require 'action_view'
|
||||
|
||||
module ActionView
|
||||
class Base
|
||||
alias_method :initialize_without_template_tracking, :initialize
|
||||
def initialize(*args)
|
||||
@_rendered = { :template => nil, :partials => Hash.new(0) }
|
||||
initialize_without_template_tracking(*args)
|
||||
end
|
||||
|
||||
attr_internal :rendered
|
||||
end
|
||||
|
||||
class Template
|
||||
alias_method :render_without_tracking, :render
|
||||
def render(view, locals, &blk)
|
||||
rendered = view.rendered
|
||||
rendered[:partials][self] += 1 if partial?
|
||||
rendered[:template] ||= []
|
||||
rendered[:template] << self
|
||||
render_without_tracking(view, locals, &blk)
|
||||
end
|
||||
end
|
||||
|
||||
class TestCase < ActiveSupport::TestCase
|
||||
class TestController < ActionController::Base
|
||||
include ActionDispatch::TestProcess
|
||||
|
||||
attr_accessor :request, :response, :params
|
||||
|
||||
def self.controller_path
|
||||
@ -42,6 +23,7 @@ def initialize
|
||||
end
|
||||
|
||||
include ActionDispatch::Assertions, ActionDispatch::TestProcess
|
||||
include ActionController::TemplateAssertions
|
||||
include ActionView::Context
|
||||
|
||||
include ActionController::PolymorphicRoutes
|
||||
@ -64,7 +46,7 @@ def setup_with_controller
|
||||
end
|
||||
|
||||
def config
|
||||
@controller.config
|
||||
@controller.config if @controller.respond_to?(:config)
|
||||
end
|
||||
|
||||
def render(options = {}, local_assigns = {}, &block)
|
||||
@ -124,7 +106,8 @@ def make_test_case_available_to_view!
|
||||
|
||||
def _view
|
||||
view = ActionView::Base.new(ActionController::Base.view_paths, _assigns, @controller)
|
||||
view.class.send :include, _helpers
|
||||
view.singleton_class.send :include, _helpers
|
||||
view.singleton_class.send :include, @controller._router.url_helpers
|
||||
view.output_buffer = self.output_buffer
|
||||
view
|
||||
end
|
||||
|
@ -8,210 +8,158 @@ class Base < AbstractController::Base
|
||||
include AbstractController::Rendering
|
||||
include AbstractController::Layouts
|
||||
|
||||
def _prefix
|
||||
"template"
|
||||
end
|
||||
|
||||
self.view_paths = [ActionView::FixtureResolver.new(
|
||||
"abstract_controller_tests/layouts/with_string_implied_child.erb" =>
|
||||
"With Implied <%= yield %>",
|
||||
"layouts/hello.erb" => "With String <%= yield %>",
|
||||
"layouts/hello_override.erb" => "With Override <%= yield %>",
|
||||
"layouts/overwrite.erb" => "Overwrite <%= yield %>",
|
||||
"layouts/with_false_layout.erb" => "False Layout <%= yield %>"
|
||||
"layouts/with_false_layout.erb" => "False Layout <%= yield %>",
|
||||
"abstract_controller_tests/layouts/with_string_implied_child.erb" =>
|
||||
"With Implied <%= yield %>"
|
||||
)]
|
||||
end
|
||||
|
||||
|
||||
class Blank < Base
|
||||
self.view_paths = ActionView::FixtureResolver.new("template/index.erb" => "Hello blank!")
|
||||
self.view_paths = []
|
||||
|
||||
def index
|
||||
render
|
||||
render :template => ActionView::Template::Text.new("Hello blank!")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class WithString < Base
|
||||
layout "hello"
|
||||
|
||||
append_view_path ActionView::FixtureResolver.new(
|
||||
"template/index.erb" => "Hello string!",
|
||||
"template/overwrite_default.erb" => "Hello string!",
|
||||
"template/overwrite_false.erb" => "Hello string!",
|
||||
"template/overwrite_string.erb" => "Hello string!"
|
||||
)
|
||||
|
||||
def index
|
||||
render
|
||||
render :template => ActionView::Template::Text.new("Hello string!")
|
||||
end
|
||||
|
||||
def overwrite_default
|
||||
render :layout => :default
|
||||
render :template => ActionView::Template::Text.new("Hello string!"), :layout => :default
|
||||
end
|
||||
|
||||
def overwrite_false
|
||||
render :layout => false
|
||||
render :template => ActionView::Template::Text.new("Hello string!"), :layout => false
|
||||
end
|
||||
|
||||
def overwrite_string
|
||||
render :layout => "overwrite"
|
||||
render :template => ActionView::Template::Text.new("Hello string!"), :layout => "overwrite"
|
||||
end
|
||||
|
||||
def overwrite_skip
|
||||
render :text => "Hello text!"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class WithStringChild < WithString
|
||||
end
|
||||
|
||||
|
||||
class WithStringOverriddenChild < WithString
|
||||
layout "hello_override"
|
||||
end
|
||||
|
||||
|
||||
class WithNilChild < WithString
|
||||
layout nil
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class WithStringImpliedChild < WithString
|
||||
end
|
||||
|
||||
|
||||
class WithChildOfImplied < WithStringImpliedChild
|
||||
end
|
||||
|
||||
class WithProc < Base
|
||||
layout proc { |c| "overwrite" }
|
||||
|
||||
append_view_path ActionView::FixtureResolver.new(
|
||||
"template/index.erb" => "Hello proc!"
|
||||
)
|
||||
|
||||
def index
|
||||
render
|
||||
render :template => ActionView::Template::Text.new("Hello proc!")
|
||||
end
|
||||
end
|
||||
|
||||
class WithSymbol < Base
|
||||
layout :hello
|
||||
|
||||
append_view_path ActionView::FixtureResolver.new(
|
||||
"template/index.erb" => "Hello symbol!"
|
||||
)
|
||||
|
||||
def index
|
||||
render
|
||||
render :template => ActionView::Template::Text.new("Hello symbol!")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def hello
|
||||
"overwrite"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class WithSymbolReturningString < Base
|
||||
layout :no_hello
|
||||
|
||||
append_view_path ActionView::FixtureResolver.new(
|
||||
"template/index.erb" => "Hello missing symbol!"
|
||||
)
|
||||
|
||||
def index
|
||||
render
|
||||
render :template => ActionView::Template::Text.new("Hello missing symbol!")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def no_hello
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class WithSymbolReturningNil < Base
|
||||
layout :nilz
|
||||
|
||||
append_view_path ActionView::FixtureResolver.new(
|
||||
"template/index.erb" => "Hello nilz!"
|
||||
)
|
||||
|
||||
def index
|
||||
render
|
||||
end
|
||||
|
||||
def nilz
|
||||
render :template => ActionView::Template::Text.new("Hello nilz!")
|
||||
end
|
||||
|
||||
def nilz() end
|
||||
end
|
||||
|
||||
|
||||
class WithSymbolReturningObj < Base
|
||||
layout :objekt
|
||||
|
||||
append_view_path ActionView::FixtureResolver.new(
|
||||
"template/index.erb" => "Hello object!"
|
||||
)
|
||||
|
||||
def index
|
||||
render
|
||||
render :template => ActionView::Template::Text.new("Hello nilz!")
|
||||
end
|
||||
|
||||
|
||||
def objekt
|
||||
Object.new
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class WithSymbolAndNoMethod < Base
|
||||
layout :no_method
|
||||
|
||||
append_view_path ActionView::FixtureResolver.new(
|
||||
"template/index.erb" => "Hello boom!"
|
||||
)
|
||||
|
||||
def index
|
||||
render
|
||||
render :template => ActionView::Template::Text.new("Hello boom!")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class WithMissingLayout < Base
|
||||
layout "missing"
|
||||
|
||||
append_view_path ActionView::FixtureResolver.new(
|
||||
"template/index.erb" => "Hello missing!"
|
||||
)
|
||||
|
||||
def index
|
||||
render
|
||||
render :template => ActionView::Template::Text.new("Hello missing!")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class WithFalseLayout < Base
|
||||
layout false
|
||||
|
||||
append_view_path ActionView::FixtureResolver.new(
|
||||
"template/index.erb" => "Hello false!"
|
||||
)
|
||||
|
||||
def index
|
||||
render
|
||||
render :template => ActionView::Template::Text.new("Hello false!")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class WithNilLayout < Base
|
||||
layout nil
|
||||
|
||||
append_view_path ActionView::FixtureResolver.new(
|
||||
"template/index.erb" => "Hello nil!"
|
||||
)
|
||||
|
||||
def index
|
||||
render
|
||||
render :template => ActionView::Template::Text.new("Hello nil!")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class TestBase < ActiveSupport::TestCase
|
||||
test "when no layout is specified, and no default is available, render without a layout" do
|
||||
controller = Blank.new
|
||||
controller.process(:index)
|
||||
assert_equal "Hello blank!", controller.response_body
|
||||
end
|
||||
|
||||
|
||||
test "when layout is specified as a string, render with that layout" do
|
||||
controller = WithString.new
|
||||
controller.process(:index)
|
||||
@ -245,13 +193,13 @@ class TestBase < ActiveSupport::TestCase
|
||||
test "when layout is specified as a string, but the layout is missing, raise an exception" do
|
||||
assert_raises(ActionView::MissingTemplate) { WithMissingLayout.new.process(:index) }
|
||||
end
|
||||
|
||||
|
||||
test "when layout is specified as false, do not use a layout" do
|
||||
controller = WithFalseLayout.new
|
||||
controller.process(:index)
|
||||
assert_equal "Hello false!", controller.response_body
|
||||
end
|
||||
|
||||
|
||||
test "when layout is specified as nil, do not use a layout" do
|
||||
controller = WithNilLayout.new
|
||||
controller.process(:index)
|
||||
@ -263,58 +211,58 @@ class TestBase < ActiveSupport::TestCase
|
||||
controller.process(:index)
|
||||
assert_equal "Overwrite Hello proc!", controller.response_body
|
||||
end
|
||||
|
||||
|
||||
test "when layout is specified as a symbol, call the requested method and use the layout returned" do
|
||||
controller = WithSymbol.new
|
||||
controller.process(:index)
|
||||
assert_equal "Overwrite Hello symbol!", controller.response_body
|
||||
end
|
||||
|
||||
|
||||
test "when layout is specified as a symbol and the method returns nil, don't use a layout" do
|
||||
controller = WithSymbolReturningNil.new
|
||||
controller.process(:index)
|
||||
assert_equal "Hello nilz!", controller.response_body
|
||||
end
|
||||
|
||||
|
||||
test "when the layout is specified as a symbol and the method doesn't exist, raise an exception" do
|
||||
assert_raises(NoMethodError) { WithSymbolAndNoMethod.new.process(:index) }
|
||||
end
|
||||
|
||||
|
||||
test "when the layout is specified as a symbol and the method returns something besides a string/false/nil, raise an exception" do
|
||||
assert_raises(ArgumentError) { WithSymbolReturningObj.new.process(:index) }
|
||||
end
|
||||
|
||||
|
||||
test "when a child controller does not have a layout, use the parent controller layout" do
|
||||
controller = WithStringChild.new
|
||||
controller.process(:index)
|
||||
assert_equal "With String Hello string!", controller.response_body
|
||||
end
|
||||
|
||||
|
||||
test "when a child controller has specified a layout, use that layout and not the parent controller layout" do
|
||||
controller = WithStringOverriddenChild.new
|
||||
controller.process(:index)
|
||||
assert_equal "With Override Hello string!", controller.response_body
|
||||
end
|
||||
|
||||
|
||||
test "when a child controller has an implied layout, use that layout and not the parent controller layout" do
|
||||
controller = WithStringImpliedChild.new
|
||||
controller.process(:index)
|
||||
assert_equal "With Implied Hello string!", controller.response_body
|
||||
end
|
||||
|
||||
|
||||
test "when a child controller specifies layout nil, do not use the parent layout" do
|
||||
controller = WithNilChild.new
|
||||
controller.process(:index)
|
||||
assert_equal "Hello string!", controller.response_body
|
||||
end
|
||||
|
||||
|
||||
test "when a grandchild has no layout specified, the child has an implied layout, and the " \
|
||||
"parent has specified a layout, use the child controller layout" do
|
||||
controller = WithChildOfImplied.new
|
||||
controller.process(:index)
|
||||
assert_equal "With Implied Hello string!", controller.response_body
|
||||
end
|
||||
|
||||
|
||||
test "raises an exception when specifying layout true" do
|
||||
assert_raises ArgumentError do
|
||||
Object.class_eval do
|
||||
@ -326,4 +274,4 @@ class ::BadFailLayout < AbstractControllerTests::Layouts::Base
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -44,6 +44,19 @@
|
||||
FIXTURE_LOAD_PATH = File.join(File.dirname(__FILE__), 'fixtures')
|
||||
FIXTURES = Pathname.new(FIXTURE_LOAD_PATH)
|
||||
|
||||
module RackTestUtils
|
||||
def body_to_string(body)
|
||||
if body.respond_to?(:each)
|
||||
str = ""
|
||||
body.each {|s| str << s }
|
||||
str
|
||||
else
|
||||
body
|
||||
end
|
||||
end
|
||||
extend self
|
||||
end
|
||||
|
||||
module SetupOnce
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
@ -239,47 +252,6 @@ class TestCase
|
||||
setup do
|
||||
@router = SharedTestRoutes
|
||||
end
|
||||
|
||||
def assert_template(options = {}, message = nil)
|
||||
validate_request!
|
||||
|
||||
hax = @controller.view_context.instance_variable_get(:@_rendered)
|
||||
|
||||
case options
|
||||
when NilClass, String
|
||||
rendered = (hax[:template] || []).map { |t| t.identifier }
|
||||
msg = build_message(message,
|
||||
"expecting <?> but rendering with <?>",
|
||||
options, rendered.join(', '))
|
||||
assert_block(msg) do
|
||||
if options.nil?
|
||||
hax[:template].blank?
|
||||
else
|
||||
rendered.any? { |t| t.match(options) }
|
||||
end
|
||||
end
|
||||
when Hash
|
||||
if expected_partial = options[:partial]
|
||||
partials = hax[:partials]
|
||||
if expected_count = options[:count]
|
||||
found = partials.detect { |p, _| p.identifier.match(expected_partial) }
|
||||
actual_count = found.nil? ? 0 : found[1]
|
||||
msg = build_message(message,
|
||||
"expecting ? to be rendered ? time(s) but rendered ? time(s)",
|
||||
expected_partial, expected_count, actual_count)
|
||||
assert(actual_count == expected_count.to_i, msg)
|
||||
else
|
||||
msg = build_message(message,
|
||||
"expecting partial <?> but action rendered <?>",
|
||||
options[:partial], partials.keys)
|
||||
assert(partials.keys.any? { |p| p.identifier.match(expected_partial) }, msg)
|
||||
end
|
||||
else
|
||||
assert hax[:partials].empty?,
|
||||
"Expected no partials to be rendered"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -181,6 +181,8 @@ def redirect_to_top_level_named_route
|
||||
end
|
||||
end
|
||||
|
||||
# require "action_dispatch/test_process"
|
||||
|
||||
# a test case to exercise the new capabilities TestRequest & TestResponse
|
||||
class ActionPackAssertionsControllerTest < ActionController::TestCase
|
||||
# -- assertion-based testing ------------------------------------------------
|
||||
@ -303,14 +305,14 @@ def test_assert_redirected_to_top_level_named_route_with_same_controller_name_in
|
||||
# make sure that the template objects exist
|
||||
def test_template_objects_alive
|
||||
process :assign_this
|
||||
assert !@controller.template.instance_variable_get(:"@hi")
|
||||
assert @controller.template.instance_variable_get(:"@howdy")
|
||||
assert !@controller.instance_variable_get(:"@hi")
|
||||
assert @controller.instance_variable_get(:"@howdy")
|
||||
end
|
||||
|
||||
# make sure we don't have template objects when we shouldn't
|
||||
def test_template_object_missing
|
||||
process :nothing
|
||||
assert_nil @controller.template.assigns['howdy']
|
||||
assert_nil @controller.instance_variable_get(:@howdy)
|
||||
end
|
||||
|
||||
# check the empty flashing
|
||||
@ -365,11 +367,10 @@ def test_assert_template_with_symbol
|
||||
# check if we were rendered by a file-based template?
|
||||
def test_rendered_action
|
||||
process :nothing
|
||||
assert_nil @controller.template.rendered[:template]
|
||||
assert_template nil
|
||||
|
||||
process :hello_world
|
||||
assert @controller.template.rendered[:template]
|
||||
assert 'hello_world', @controller.template.rendered[:template].to_s
|
||||
assert_template 'hello_world'
|
||||
end
|
||||
|
||||
# check the redirection location
|
||||
|
@ -56,6 +56,16 @@ def method_missing(selector)
|
||||
end
|
||||
end
|
||||
|
||||
class AnotherMethodMissingController < ActionController::Base
|
||||
cattr_accessor :_exception
|
||||
rescue_from Exception, :with => :_exception=
|
||||
|
||||
protected
|
||||
def method_missing(*attrs, &block)
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
class DefaultUrlOptionsController < ActionController::Base
|
||||
def from_view
|
||||
render :inline => "<%= #{params[:route]} %>"
|
||||
@ -124,11 +134,11 @@ def setup
|
||||
|
||||
def test_action_methods
|
||||
@empty_controllers.each do |c|
|
||||
assert_equal Set.new, c.class.__send__(:action_methods), "#{c.controller_path} should be empty!"
|
||||
assert_equal Set.new, c.class.action_methods, "#{c.controller_path} should be empty!"
|
||||
end
|
||||
|
||||
@non_empty_controllers.each do |c|
|
||||
assert_equal Set.new(%w(public_action)), c.class.__send__(:action_methods), "#{c.controller_path} should not be empty!"
|
||||
assert_equal Set.new(%w(public_action)), c.class.action_methods, "#{c.controller_path} should not be empty!"
|
||||
end
|
||||
end
|
||||
|
||||
@ -173,6 +183,12 @@ def test_method_missing_is_not_an_action_name
|
||||
assert_equal 'method_missing', @response.body
|
||||
end
|
||||
|
||||
def test_method_missing_should_recieve_symbol
|
||||
use_controller AnotherMethodMissingController
|
||||
get :some_action
|
||||
assert_kind_of NameError, @controller._exception
|
||||
end
|
||||
|
||||
def test_get_on_hidden_should_fail
|
||||
use_controller NonEmptyController
|
||||
assert_raise(ActionController::UnknownAction) { get :hidden_action }
|
||||
@ -191,7 +207,7 @@ def setup
|
||||
|
||||
def test_url_options_override
|
||||
with_routing do |set|
|
||||
set.draw do |map|
|
||||
set.draw do
|
||||
match 'from_view', :to => 'url_options#from_view', :as => :from_view
|
||||
match ':controller/:action'
|
||||
end
|
||||
@ -202,7 +218,18 @@ def test_url_options_override
|
||||
assert_equal 'http://www.override.com/from_view?locale=en', @controller.send(:from_view_url)
|
||||
assert_equal 'http://www.override.com/default_url_options/new?locale=en', @controller.url_for(:controller => 'default_url_options')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_url_helpers_does_not_become_actions
|
||||
with_routing do |set|
|
||||
set.draw do
|
||||
match "account/overview"
|
||||
end
|
||||
|
||||
@controller.class.send(:include, set.url_helpers)
|
||||
assert !@controller.class.action_methods.include?("account_overview_path")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class DefaultUrlOptionsTest < ActionController::TestCase
|
||||
@ -216,7 +243,7 @@ def setup
|
||||
|
||||
def test_default_url_options_override
|
||||
with_routing do |set|
|
||||
set.draw do |map|
|
||||
set.draw do
|
||||
match 'from_view', :to => 'default_url_options#from_view', :as => :from_view
|
||||
match ':controller/:action'
|
||||
end
|
||||
@ -231,7 +258,7 @@ def test_default_url_options_override
|
||||
|
||||
def test_default_url_options_are_used_in_non_positional_parameters
|
||||
with_routing do |set|
|
||||
set.draw do |map|
|
||||
set.draw do
|
||||
scope("/:locale") do
|
||||
resources :descriptions
|
||||
end
|
||||
|
@ -285,6 +285,8 @@ def test_simple_action_not_cached
|
||||
assert_not_equal cached_time, @response.body
|
||||
end
|
||||
|
||||
include RackTestUtils
|
||||
|
||||
def test_action_cache_with_layout
|
||||
get :with_layout
|
||||
cached_time = content_to_cache
|
||||
@ -294,8 +296,8 @@ def test_action_cache_with_layout
|
||||
|
||||
get :with_layout
|
||||
assert_not_equal cached_time, @response.body
|
||||
|
||||
assert_equal @response.body, read_fragment('hostname.com/action_caching_test/with_layout')
|
||||
body = body_to_string(read_fragment('hostname.com/action_caching_test/with_layout'))
|
||||
assert_equal @response.body, body
|
||||
end
|
||||
|
||||
def test_action_cache_with_layout_and_layout_cache_false
|
||||
@ -308,7 +310,8 @@ def test_action_cache_with_layout_and_layout_cache_false
|
||||
get :layout_false
|
||||
assert_not_equal cached_time, @response.body
|
||||
|
||||
assert_equal cached_time, read_fragment('hostname.com/action_caching_test/layout_false')
|
||||
body = body_to_string(read_fragment('hostname.com/action_caching_test/layout_false'))
|
||||
assert_equal cached_time, body
|
||||
end
|
||||
|
||||
def test_action_cache_conditional_options
|
||||
@ -616,8 +619,10 @@ def test_fragment_for_with_disabled_caching
|
||||
@store.write('views/expensive', 'fragment content')
|
||||
fragment_computed = false
|
||||
|
||||
view_context = @controller.view_context
|
||||
|
||||
buffer = 'generated till now -> '.html_safe
|
||||
@controller.fragment_for(buffer, 'expensive') { fragment_computed = true }
|
||||
buffer << view_context.send(:fragment_for, 'expensive') { fragment_computed = true }
|
||||
|
||||
assert fragment_computed
|
||||
assert_equal 'generated till now -> ', buffer
|
||||
@ -627,13 +632,28 @@ def test_fragment_for
|
||||
@store.write('views/expensive', 'fragment content')
|
||||
fragment_computed = false
|
||||
|
||||
view_context = @controller.view_context
|
||||
|
||||
buffer = 'generated till now -> '.html_safe
|
||||
@controller.fragment_for(buffer, 'expensive') { fragment_computed = true }
|
||||
buffer << view_context.send(:fragment_for, 'expensive') { fragment_computed = true }
|
||||
|
||||
assert !fragment_computed
|
||||
assert_equal 'generated till now -> fragment content', buffer
|
||||
end
|
||||
|
||||
def test_html_safety
|
||||
assert_nil @store.read('views/name')
|
||||
content = 'value'.html_safe
|
||||
assert_equal content, @controller.write_fragment('name', content)
|
||||
|
||||
cached = @store.read('views/name')
|
||||
assert_equal content, cached
|
||||
assert_equal String, cached.class
|
||||
|
||||
html_safe = @controller.read_fragment('name')
|
||||
assert_equal content, html_safe
|
||||
assert html_safe.html_safe?
|
||||
end
|
||||
end
|
||||
|
||||
class FunctionalCachingController < CachingController
|
||||
@ -691,8 +711,8 @@ def test_fragment_caching
|
||||
def test_fragment_caching_in_partials
|
||||
get :html_fragment_cached_with_partial
|
||||
assert_response :success
|
||||
assert_match /Fragment caching in a partial/, @response.body
|
||||
assert_match "Fragment caching in a partial", @store.read('views/test.host/functional_caching/html_fragment_cached_with_partial')
|
||||
assert_match /Old fragment caching in a partial/, @response.body
|
||||
assert_match "Old fragment caching in a partial", @store.read('views/test.host/functional_caching/html_fragment_cached_with_partial')
|
||||
end
|
||||
|
||||
def test_render_inline_before_fragment_caching
|
||||
@ -706,14 +726,14 @@ def test_render_inline_before_fragment_caching
|
||||
def test_fragment_caching_in_rjs_partials
|
||||
xhr :get, :js_fragment_cached_with_partial
|
||||
assert_response :success
|
||||
assert_match /Fragment caching in a partial/, @response.body
|
||||
assert_match "Fragment caching in a partial", @store.read('views/test.host/functional_caching/js_fragment_cached_with_partial')
|
||||
assert_match /Old fragment caching in a partial/, @response.body
|
||||
assert_match "Old fragment caching in a partial", @store.read('views/test.host/functional_caching/js_fragment_cached_with_partial')
|
||||
end
|
||||
|
||||
def test_html_formatted_fragment_caching
|
||||
get :formatted_fragment_cached, :format => "html"
|
||||
assert_response :success
|
||||
expected_body = "<body>\n<p>ERB</p>\n</body>"
|
||||
expected_body = "<body>\n<p>ERB</p>\n</body>\n"
|
||||
|
||||
assert_equal expected_body, @response.body
|
||||
|
||||
@ -729,15 +749,4 @@ def test_xml_formatted_fragment_caching
|
||||
|
||||
assert_equal " <p>Builder</p>\n", @store.read('views/test.host/functional_caching/formatted_fragment_cached')
|
||||
end
|
||||
|
||||
def test_js_formatted_fragment_caching
|
||||
get :formatted_fragment_cached, :format => "js"
|
||||
assert_response :success
|
||||
expected_body = %(title = "Hey";\n$("element_1").visualEffect("highlight");\n) +
|
||||
%($("element_2").visualEffect("highlight");\nfooter = "Bye";)
|
||||
assert_equal expected_body, @response.body
|
||||
|
||||
assert_equal ['$("element_1").visualEffect("highlight");', '$("element_2").visualEffect("highlight");'],
|
||||
@store.read('views/test.host/functional_caching/formatted_fragment_cached')
|
||||
end
|
||||
end
|
||||
|
@ -64,6 +64,12 @@ def set_permanent_signed_cookie
|
||||
cookies.permanent.signed[:remember_me] = 100
|
||||
head :ok
|
||||
end
|
||||
|
||||
def delete_and_set_cookie
|
||||
cookies.delete :user_name
|
||||
cookies[:user_name] = { :value => "david", :expires => Time.utc(2005, 10, 10,5) }
|
||||
head :ok
|
||||
end
|
||||
end
|
||||
|
||||
tests TestController
|
||||
@ -152,6 +158,11 @@ def test_permanent_signed_cookie
|
||||
assert_equal 100, @controller.send(:cookies).signed[:remember_me]
|
||||
end
|
||||
|
||||
def test_delete_and_set_cookie
|
||||
get :delete_and_set_cookie
|
||||
assert_cookie_header "user_name=david; path=/; expires=Mon, 10-Oct-2005 05:00:00 GMT"
|
||||
assert_equal({"user_name" => "david"}, @response.cookies)
|
||||
end
|
||||
|
||||
private
|
||||
def assert_cookie_header(expected)
|
||||
|
@ -651,9 +651,9 @@ def test_conditional_skipping_of_filters
|
||||
assert_equal %w( ensure_login find_user ), assigns["ran_filter"]
|
||||
|
||||
test_process(ConditionalSkippingController, "login")
|
||||
assert_nil @controller.template.controller.instance_variable_get("@ran_after_filter")
|
||||
assert_nil @controller.instance_variable_get("@ran_after_filter")
|
||||
test_process(ConditionalSkippingController, "change_password")
|
||||
assert_equal %w( clean_up ), @controller.template.controller.instance_variable_get("@ran_after_filter")
|
||||
assert_equal %w( clean_up ), @controller.instance_variable_get("@ran_after_filter")
|
||||
end
|
||||
|
||||
def test_conditional_skipping_of_filters_when_parent_filter_is_also_conditional
|
||||
|
@ -173,14 +173,12 @@ def setup
|
||||
end
|
||||
|
||||
def test_opens_new_session
|
||||
@test.class.expects(:fixture_table_names).times(2).returns(['foo'])
|
||||
|
||||
session1 = @test.open_session { |sess| }
|
||||
session2 = @test.open_session # implicit session
|
||||
|
||||
assert_kind_of ::ActionController::Integration::Session, session1
|
||||
assert_kind_of ::ActionController::Integration::Session, session2
|
||||
assert_not_equal session1, session2
|
||||
assert session1.respond_to?(:assert_template), "open_session makes assert_template available"
|
||||
assert session2.respond_to?(:assert_template), "open_session makes assert_template available"
|
||||
assert !session1.equal?(session2)
|
||||
end
|
||||
|
||||
# RSpec mixes Matchers (which has a #method_missing) into
|
||||
|
@ -120,19 +120,19 @@ class LayoutSetInResponseTest < ActionController::TestCase
|
||||
def test_layout_set_when_using_default_layout
|
||||
@controller = DefaultLayoutController.new
|
||||
get :hello
|
||||
assert @controller.template.layout.include?('layouts/layout_test')
|
||||
assert_template :layout => "layouts/layout_test"
|
||||
end
|
||||
|
||||
def test_layout_set_when_set_in_controller
|
||||
@controller = HasOwnLayoutController.new
|
||||
get :hello
|
||||
assert @controller.template.layout.include?('layouts/item')
|
||||
assert_template :layout => "layouts/item"
|
||||
end
|
||||
|
||||
def test_layout_only_exception_when_included
|
||||
@controller = OnlyLayoutController.new
|
||||
get :hello
|
||||
assert @controller.template.layout.include?('layouts/item')
|
||||
assert_template :layout => "layouts/item"
|
||||
end
|
||||
|
||||
def test_layout_only_exception_when_excepted
|
||||
@ -144,7 +144,7 @@ def test_layout_only_exception_when_excepted
|
||||
def test_layout_except_exception_when_included
|
||||
@controller = ExceptLayoutController.new
|
||||
get :hello
|
||||
assert @controller.template.layout.include?('layouts/item')
|
||||
assert_template :layout => "layouts/item"
|
||||
end
|
||||
|
||||
def test_layout_except_exception_when_excepted
|
||||
@ -156,19 +156,19 @@ def test_layout_except_exception_when_excepted
|
||||
def test_layout_set_when_using_render
|
||||
@controller = SetsLayoutInRenderController.new
|
||||
get :hello
|
||||
assert @controller.template.layout.include?('layouts/third_party_template_library')
|
||||
assert_template :layout => "layouts/third_party_template_library"
|
||||
end
|
||||
|
||||
def test_layout_is_not_set_when_none_rendered
|
||||
@controller = RendersNoLayoutController.new
|
||||
get :hello
|
||||
assert_nil @controller.template.layout
|
||||
assert_template :layout => nil
|
||||
end
|
||||
|
||||
def test_layout_is_picked_from_the_controller_instances_view_path
|
||||
@controller = PrependsViewPathController.new
|
||||
get :hello
|
||||
assert @controller.template.layout =~ /layouts\/alt\.\w+/
|
||||
assert_template :layout => /layouts\/alt\.\w+/
|
||||
end
|
||||
|
||||
def test_absolute_pathed_layout
|
||||
@ -219,7 +219,7 @@ def test_symlinked_layout_is_rendered
|
||||
@controller = LayoutSymlinkedTest.new
|
||||
get :hello
|
||||
assert_response 200
|
||||
assert @controller.template.layout.include?("layouts/symlinked/symlinked_layout")
|
||||
assert_template :layout => "layouts/symlinked/symlinked_layout"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -765,7 +765,7 @@ def render(params={})
|
||||
Customer.any_instance.stubs(:errors).returns(errors)
|
||||
|
||||
post :using_resource_with_action
|
||||
assert_equal "foo - #{[:html].to_s}", @controller.response_body
|
||||
assert_equal "foo - #{[:html].to_s}", @controller.response.body
|
||||
end
|
||||
|
||||
def test_respond_as_responder_entry_point
|
||||
|
27
actionpack/test/controller/new_base/bare_metal_test.rb
Normal file
27
actionpack/test/controller/new_base/bare_metal_test.rb
Normal file
@ -0,0 +1,27 @@
|
||||
require "abstract_unit"
|
||||
|
||||
module BareMetalTest
|
||||
class BareController < ActionController::Metal
|
||||
def index
|
||||
self.response_body = "Hello world"
|
||||
end
|
||||
end
|
||||
|
||||
class BareTest < ActiveSupport::TestCase
|
||||
test "response body is a Rack-compatible response" do
|
||||
status, headers, body = BareController.action(:index).call(Rack::MockRequest.env_for("/"))
|
||||
assert_equal 200, status
|
||||
string = ""
|
||||
|
||||
body.each do |part|
|
||||
assert part.is_a?(String), "Each part of the body must be a String"
|
||||
string << part
|
||||
end
|
||||
|
||||
assert headers.is_a?(Hash), "Headers must be a Hash"
|
||||
assert headers["Content-Type"], "Content-Type must exist"
|
||||
|
||||
assert_equal "Hello world", string
|
||||
end
|
||||
end
|
||||
end
|
@ -18,6 +18,8 @@ def call(env)
|
||||
end
|
||||
|
||||
class TestMiddleware < ActiveSupport::TestCase
|
||||
include RackTestUtils
|
||||
|
||||
def setup
|
||||
@app = Rack::Builder.new do
|
||||
use MetalTest::MetalMiddleware
|
||||
@ -29,14 +31,14 @@ def setup
|
||||
env = Rack::MockRequest.env_for("/authed")
|
||||
response = @app.call(env)
|
||||
|
||||
assert_equal "Hello World", response[2]
|
||||
assert_equal "Hello World", body_to_string(response[2])
|
||||
end
|
||||
|
||||
test "it can return a response using the normal AC::Metal techniques" do
|
||||
env = Rack::MockRequest.env_for("/")
|
||||
response = @app.call(env)
|
||||
|
||||
assert_equal "Not authed!", response[2]
|
||||
assert_equal "Not authed!", body_to_string(response[2])
|
||||
assert_equal 401, response[0]
|
||||
end
|
||||
end
|
||||
|
@ -44,7 +44,7 @@ module MiddlewareTests
|
||||
|
||||
test "middleware that is 'use'd is called as part of the Rack application" do
|
||||
result = @app.call(env_for("/"))
|
||||
assert_equal "Hello World", result[2]
|
||||
assert_equal "Hello World", RackTestUtils.body_to_string(result[2])
|
||||
assert_equal "Success", result[1]["Middleware-Test"]
|
||||
end
|
||||
|
||||
|
@ -117,7 +117,7 @@ module RenderActionWithApplicationLayout
|
||||
# # ==== Render actions with layouts ====
|
||||
class BasicController < ::ApplicationController
|
||||
# Set the view path to an application view structure with layouts
|
||||
self.view_paths = self.view_paths = [ActionView::FixtureResolver.new(
|
||||
self.view_paths = [ActionView::FixtureResolver.new(
|
||||
"render_action_with_application_layout/basic/hello_world.html.erb" => "Hello World!",
|
||||
"render_action_with_application_layout/basic/hello.html.builder" => "xml.p 'Hello'",
|
||||
"layouts/application.html.erb" => "Hi <%= yield %> OK, Bye",
|
||||
@ -202,7 +202,7 @@ class TestLayout < Rack::TestCase
|
||||
|
||||
module RenderActionWithControllerLayout
|
||||
class BasicController < ActionController::Base
|
||||
self.view_paths = self.view_paths = [ActionView::FixtureResolver.new(
|
||||
self.view_paths = [ActionView::FixtureResolver.new(
|
||||
"render_action_with_controller_layout/basic/hello_world.html.erb" => "Hello World!",
|
||||
"layouts/render_action_with_controller_layout/basic.html.erb" => "With Controller Layout! <%= yield %> Bye"
|
||||
)]
|
||||
|
@ -5,8 +5,10 @@ class BasicController < ActionController::Base
|
||||
self.view_paths = [ActionView::FixtureResolver.new(
|
||||
"render_rjs/basic/index.js.rjs" => "page[:customer].replace_html render(:partial => 'customer')",
|
||||
"render_rjs/basic/index_html.js.rjs" => "page[:customer].replace_html :partial => 'customer'",
|
||||
"render_rjs/basic/index_no_js.js.erb" => "<%= render(:partial => 'developer') %>",
|
||||
"render_rjs/basic/_customer.js.erb" => "JS Partial",
|
||||
"render_rjs/basic/_customer.html.erb" => "HTML Partial",
|
||||
"render_rjs/basic/_developer.html.erb" => "HTML Partial",
|
||||
"render_rjs/basic/index_locale.js.rjs" => "page[:customer].replace_html :partial => 'customer'",
|
||||
"render_rjs/basic/_customer.da.html.erb" => "Danish HTML Partial",
|
||||
"render_rjs/basic/_customer.da.js.erb" => "Danish JS Partial"
|
||||
@ -16,8 +18,14 @@ def index
|
||||
render
|
||||
end
|
||||
|
||||
def index_respond_to
|
||||
respond_to do |format|
|
||||
format.js { render :action => "index_no_js" }
|
||||
end
|
||||
end
|
||||
|
||||
def index_locale
|
||||
old_locale, I18n.locale = I18n.locale, :da
|
||||
self.locale = :da
|
||||
end
|
||||
end
|
||||
|
||||
@ -37,6 +45,16 @@ def teardown
|
||||
assert_response("$(\"customer\").update(\"JS Partial\");")
|
||||
end
|
||||
|
||||
test "rendering a partial in an RJS template should pick the HTML one if no JS is available" do
|
||||
get :index_no_js, "format" => "js"
|
||||
assert_response("HTML Partial")
|
||||
end
|
||||
|
||||
test "rendering a partial in an RJS template should pick the HTML one if no JS is available on respond_to" do
|
||||
get :index_respond_to, "format" => "js"
|
||||
assert_response("HTML Partial")
|
||||
end
|
||||
|
||||
test "replacing an element with a partial in an RJS template should pick the HTML template over the JS one" do
|
||||
get :index_html, "format" => "js"
|
||||
assert_response("$(\"customer\").update(\"HTML Partial\");")
|
||||
|
@ -18,6 +18,13 @@ class LabellingFormBuilder < ActionView::Helpers::FormBuilder
|
||||
|
||||
layout :determine_layout
|
||||
|
||||
def name
|
||||
nil
|
||||
end
|
||||
|
||||
private :name
|
||||
helper_method :name
|
||||
|
||||
def hello_world
|
||||
end
|
||||
|
||||
@ -418,7 +425,6 @@ def render_to_string_with_inline_and_render
|
||||
|
||||
def rendering_with_conflicting_local_vars
|
||||
@name = "David"
|
||||
def @template.name() nil end
|
||||
render :action => "potential_conflicts"
|
||||
end
|
||||
|
||||
@ -507,10 +513,6 @@ def render_alternate_default
|
||||
end
|
||||
end
|
||||
|
||||
def partial_only_with_layout
|
||||
render :partial => "partial_only", :layout => true
|
||||
end
|
||||
|
||||
def render_to_string_with_partial
|
||||
@partial_only = render_to_string :partial => "partial_only"
|
||||
@partial_with_locals = render_to_string :partial => "customer", :locals => { :customer => Customer.new("david") }
|
||||
@ -526,11 +528,11 @@ def partial_with_locals
|
||||
end
|
||||
|
||||
def partial_with_form_builder
|
||||
render :partial => ActionView::Helpers::FormBuilder.new(:post, nil, @template, {}, Proc.new {})
|
||||
render :partial => ActionView::Helpers::FormBuilder.new(:post, nil, view_context, {}, Proc.new {})
|
||||
end
|
||||
|
||||
def partial_with_form_builder_subclass
|
||||
render :partial => LabellingFormBuilder.new(:post, nil, @template, {}, Proc.new {})
|
||||
render :partial => LabellingFormBuilder.new(:post, nil, view_context, {}, Proc.new {})
|
||||
end
|
||||
|
||||
def partial_collection
|
||||
@ -617,6 +619,15 @@ def rescue_action(e)
|
||||
raise
|
||||
end
|
||||
|
||||
before_filter :only => :render_with_filters do
|
||||
request.format = :xml
|
||||
end
|
||||
|
||||
# Ensure that the before filter is executed *before* self.formats is set.
|
||||
def render_with_filters
|
||||
render :action => :formatted_xml_erb
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def determine_layout
|
||||
@ -625,8 +636,7 @@ def determine_layout
|
||||
"rendering_nothing_on_layout", "render_text_hello_world",
|
||||
"render_text_hello_world_with_layout",
|
||||
"hello_world_with_layout_false",
|
||||
"partial_only", "partial_only_with_layout",
|
||||
"accessing_params_in_template",
|
||||
"partial_only", "accessing_params_in_template",
|
||||
"accessing_params_in_template_with_layout",
|
||||
"render_with_explicit_template",
|
||||
"render_with_explicit_string_template",
|
||||
@ -1034,6 +1044,11 @@ def test_render_with_explicit_string_template
|
||||
assert_equal "<html>Hello world!</html>", @response.body
|
||||
end
|
||||
|
||||
def test_render_with_filters
|
||||
get :render_with_filters
|
||||
assert_equal "<test>passed formatted xml erb</test>", @response.body
|
||||
end
|
||||
|
||||
# :ported:
|
||||
def test_double_render
|
||||
assert_raise(ActionController::DoubleRenderError) { get :double_render }
|
||||
@ -1184,11 +1199,6 @@ def test_should_render_html_partial_with_dot
|
||||
assert_equal 'partial html', @response.body
|
||||
end
|
||||
|
||||
def test_partial_only_with_layout
|
||||
get :partial_only_with_layout
|
||||
assert_equal "<html>only partial</html>", @response.body
|
||||
end
|
||||
|
||||
def test_render_to_string_partial
|
||||
get :render_to_string_with_partial
|
||||
assert_equal "only partial", assigns(:partial_only)
|
||||
|
@ -16,6 +16,7 @@ class AccountsController < ResourcesController; end
|
||||
class AdminController < ResourcesController; end
|
||||
class ProductsController < ResourcesController; end
|
||||
class ImagesController < ResourcesController; end
|
||||
class PreferencesController < ResourcesController; end
|
||||
|
||||
module Backoffice
|
||||
class ProductsController < ResourcesController; end
|
||||
@ -1125,6 +1126,12 @@ def test_default_singleton_restful_route_uses_get
|
||||
end
|
||||
end
|
||||
|
||||
def test_singleton_resource_name_is_not_singularized
|
||||
with_singleton_resources(:preferences) do
|
||||
assert_singleton_restful_for :preferences
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
def with_restful_routing(*args)
|
||||
with_routing do |set|
|
||||
|
@ -1,12 +1,12 @@
|
||||
require 'abstract_unit'
|
||||
|
||||
class TestRoutingMount < ActionDispatch::IntegrationTest
|
||||
SprocketsApp = lambda { |env|
|
||||
[200, {"Content-Type" => "text/html"}, ["#{env["SCRIPT_NAME"]} -- #{env["PATH_INFO"]}"]]
|
||||
}
|
||||
|
||||
Router = ActionDispatch::Routing::RouteSet.new
|
||||
Router.draw do
|
||||
SprocketsApp = lambda { |env|
|
||||
[200, {"Content-Type" => "text/html"}, ["#{env["SCRIPT_NAME"]} -- #{env["PATH_INFO"]}"]]
|
||||
}
|
||||
|
||||
mount SprocketsApp, :at => "/sprockets"
|
||||
mount SprocketsApp => "/shorthand"
|
||||
|
||||
|
@ -18,14 +18,14 @@ def self.matches?(request)
|
||||
default_url_options :host => "rubyonrails.org"
|
||||
|
||||
controller :sessions do
|
||||
get 'login' => :new, :as => :login
|
||||
get 'login' => :new
|
||||
post 'login' => :create
|
||||
|
||||
delete 'logout' => :destroy, :as => :logout
|
||||
delete 'logout' => :destroy
|
||||
end
|
||||
|
||||
resource :session do
|
||||
get :create
|
||||
post :reset
|
||||
|
||||
resource :info
|
||||
end
|
||||
@ -34,6 +34,8 @@ def self.matches?(request)
|
||||
match 'account/login', :to => redirect("/login")
|
||||
|
||||
match 'account/overview'
|
||||
match '/account/nested/overview'
|
||||
match 'sign_in' => "sessions#new"
|
||||
|
||||
match 'account/modulo/:name', :to => redirect("/%{name}s")
|
||||
match 'account/proc/:name', :to => redirect {|params| "/#{params[:name].pluralize}" }
|
||||
@ -120,7 +122,15 @@ def self.matches?(request)
|
||||
# misc
|
||||
match 'articles/:year/:month/:day/:title', :to => "articles#show", :as => :article
|
||||
|
||||
# default params
|
||||
match 'inline_pages/(:id)', :to => 'pages#show', :id => 'home'
|
||||
match 'default_pages/(:id)', :to => 'pages#show', :defaults => { :id => 'home' }
|
||||
defaults :id => 'home' do
|
||||
match 'scoped_pages/(:id)', :to => 'pages#show'
|
||||
end
|
||||
|
||||
namespace :account do
|
||||
match 'shorthand'
|
||||
match 'description', :to => "account#description", :as => "description"
|
||||
resource :subscription, :credit, :credit_card
|
||||
|
||||
@ -193,6 +203,7 @@ def test_login
|
||||
assert_equal '/login', url_for(:controller => 'sessions', :action => 'new', :only_path => true)
|
||||
|
||||
assert_equal 'http://rubyonrails.org/login', Routes.url_for(:controller => 'sessions', :action => 'create')
|
||||
assert_equal 'http://rubyonrails.org/login', Routes.url_helpers.login_url
|
||||
end
|
||||
end
|
||||
|
||||
@ -237,6 +248,10 @@ def test_session_singleton_resource
|
||||
get '/session/edit'
|
||||
assert_equal 'sessions#edit', @response.body
|
||||
assert_equal '/session/edit', edit_session_path
|
||||
|
||||
post '/session/reset'
|
||||
assert_equal 'sessions#reset', @response.body
|
||||
assert_equal '/session/reset', reset_session_path
|
||||
end
|
||||
end
|
||||
|
||||
@ -654,6 +669,30 @@ def test_convention_match_with_no_scope
|
||||
end
|
||||
end
|
||||
|
||||
def test_convention_match_inside_namespace
|
||||
with_test_routes do
|
||||
assert_equal '/account/shorthand', account_shorthand_path
|
||||
get '/account/shorthand'
|
||||
assert_equal 'account#shorthand', @response.body
|
||||
end
|
||||
end
|
||||
|
||||
def test_convention_match_nested_and_with_leading_slash
|
||||
with_test_routes do
|
||||
assert_equal '/account/nested/overview', account_nested_overview_path
|
||||
get '/account/nested/overview'
|
||||
assert_equal 'account/nested#overview', @response.body
|
||||
end
|
||||
end
|
||||
|
||||
def test_convention_with_explicit_end
|
||||
with_test_routes do
|
||||
get '/sign_in'
|
||||
assert_equal 'sessions#new', @response.body
|
||||
assert_equal '/sign_in', sign_in_path
|
||||
end
|
||||
end
|
||||
|
||||
def test_redirect_with_complete_url
|
||||
with_test_routes do
|
||||
get '/account/google'
|
||||
@ -742,6 +781,19 @@ def test_nested_optional_scoped_path
|
||||
end
|
||||
end
|
||||
|
||||
def test_default_params
|
||||
with_test_routes do
|
||||
get '/inline_pages'
|
||||
assert_equal 'home', @request.params[:id]
|
||||
|
||||
get '/default_pages'
|
||||
assert_equal 'home', @request.params[:id]
|
||||
|
||||
get '/scoped_pages'
|
||||
assert_equal 'home', @request.params[:id]
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def with_test_routes
|
||||
yield
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user