Rails 2.1

Update to Rails 2.1 final.
This commit is contained in:
Jacques Distler 2008-06-02 01:35:38 -05:00
parent fd554cce90
commit 516d6dfac0
257 changed files with 4058 additions and 1933 deletions

View file

@ -1,4 +1,4 @@
*2.1.0 RC1 (May 11th, 2008)* *2.1.0 (May 31st, 2008)*
* Fixed that a return-path header would be ignored #7572 [joost] * Fixed that a return-path header would be ignored #7572 [joost]

View file

@ -19,8 +19,7 @@ are all set up this way. An example of such a method:
recipients recipient recipients recipient
subject "[Signed up] Welcome #{recipient}" subject "[Signed up] Welcome #{recipient}"
from "system@loudthinking.com" from "system@loudthinking.com"
body :recipient => recipient
body(:recipient => recipient)
end end
The body of the email is created by using an Action View template (regular The body of the email is created by using an Action View template (regular
@ -78,21 +77,26 @@ Example:
end end
end end
This Mailman can be the target for Postfix. In Rails, you would use the runner like this: This Mailman can be the target for Postfix or other MTAs. In Rails, you would use the runner in the
trivial case like this:
./script/runner 'Mailman.receive(STDIN.read)' ./script/runner 'Mailman.receive(STDIN.read)'
However, invoking Rails in the runner for each mail to be received is very resource intensive. A single
instance of Rails should be run within a daemon if it is going to be utilized to process more than just
a limited number of email.
== Configuration == Configuration
The Base class has the full list of configuration options. Here's an example: The Base class has the full list of configuration options. Here's an example:
ActionMailer::Base.smtp_settings = { ActionMailer::Base.smtp_settings = {
:address=>'smtp.yourserver.com', # default: localhost :address => 'smtp.yourserver.com', # default: localhost
:port=>'25', # default: 25 :port => '25', # default: 25
:user_name=>'user', :user_name => 'user',
:password=>'pass', :password => 'pass',
:authentication=>:plain # :plain, :login or :cram_md5 :authentication => :plain # :plain, :login or :cram_md5
} }
== Dependencies == Dependencies

View file

@ -55,7 +55,7 @@ spec = Gem::Specification.new do |s|
s.rubyforge_project = "actionmailer" s.rubyforge_project = "actionmailer"
s.homepage = "http://www.rubyonrails.org" s.homepage = "http://www.rubyonrails.org"
s.add_dependency('actionpack', '= 2.0.991' + PKG_BUILD) s.add_dependency('actionpack', '= 2.1.0' + PKG_BUILD)
s.has_rdoc = true s.has_rdoc = true
s.requirements << 'none' s.requirements << 'none'

View file

@ -5,17 +5,17 @@ require 'action_mailer/utils'
require 'tmail/net' require 'tmail/net'
module ActionMailer #:nodoc: module ActionMailer #:nodoc:
# ActionMailer allows you to send email from your application using a mailer model and views. # Action Mailer allows you to send email from your application using a mailer model and views.
# #
# #
# = Mailer Models # = Mailer Models
# #
# To use ActionMailer, you need to create a mailer model. # To use Action Mailer, you need to create a mailer model.
# #
# $ script/generate mailer Notifier # $ script/generate mailer Notifier
# #
# The generated model inherits from ActionMailer::Base. Emails are defined by creating methods within the model which are then # The generated model inherits from ActionMailer::Base. Emails are defined by creating methods within the model which are then
# used to set variables to be used in the mail template, to change options on the mail, or # used to set variables to be used in the mail template, to change options on the mail, or
# to add attachments. # to add attachments.
# #
# Examples: # Examples:
@ -35,7 +35,8 @@ module ActionMailer #:nodoc:
# * <tt>subject</tt> - The subject of your email. Sets the <tt>Subject:</tt> header. # * <tt>subject</tt> - The subject of your email. Sets the <tt>Subject:</tt> header.
# * <tt>from</tt> - Who the email you are sending is from. Sets the <tt>From:</tt> header. # * <tt>from</tt> - Who the email you are sending is from. Sets the <tt>From:</tt> header.
# * <tt>cc</tt> - Takes one or more email addresses. These addresses will receive a carbon copy of your email. Sets the <tt>Cc:</tt> header. # * <tt>cc</tt> - Takes one or more email addresses. These addresses will receive a carbon copy of your email. Sets the <tt>Cc:</tt> header.
# * <tt>bcc</tt> - Takes one or more email address. These addresses will receive a blind carbon copy of your email. Sets the <tt>Bcc</tt> header. # * <tt>bcc</tt> - Takes one or more email addresses. These addresses will receive a blind carbon copy of your email. Sets the <tt>Bcc:</tt> header.
# * <tt>reply_to</tt> - Takes one or more email addresses. These addresses will be listed as the default recipients when replying to your email. Sets the <tt>Reply-To:</tt> header.
# * <tt>sent_on</tt> - The date on which the message was sent. If not set, the header wil be set by the delivery agent. # * <tt>sent_on</tt> - The date on which the message was sent. If not set, the header wil be set by the delivery agent.
# * <tt>content_type</tt> - Specify the content type of the message. Defaults to <tt>text/plain</tt>. # * <tt>content_type</tt> - Specify the content type of the message. Defaults to <tt>text/plain</tt>.
# * <tt>headers</tt> - Specify additional headers to be set for the message, e.g. <tt>headers 'X-Mail-Count' => 107370</tt>. # * <tt>headers</tt> - Specify additional headers to be set for the message, e.g. <tt>headers 'X-Mail-Count' => 107370</tt>.
@ -48,16 +49,16 @@ module ActionMailer #:nodoc:
# named after each key in the hash containing the value that that key points to. # named after each key in the hash containing the value that that key points to.
# #
# So, for example, <tt>body :account => recipient</tt> would result # So, for example, <tt>body :account => recipient</tt> would result
# in an instance variable <tt>@account</tt> with the value of <tt>recipient</tt> being accessible in the # in an instance variable <tt>@account</tt> with the value of <tt>recipient</tt> being accessible in the
# view. # view.
# #
# #
# = Mailer views # = Mailer views
# #
# Like ActionController, each mailer class has a corresponding view directory # Like Action Controller, each mailer class has a corresponding view directory
# in which each method of the class looks for a template with its name. # in which each method of the class looks for a template with its name.
# To define a template to be used with a mailing, create an <tt>.erb</tt> file with the same name as the method # To define a template to be used with a mailing, create an <tt>.erb</tt> file with the same name as the method
# in your mailer model. For example, in the mailer defined above, the template at # in your mailer model. For example, in the mailer defined above, the template at
# <tt>app/views/notifier/signup_notification.erb</tt> would be used to generate the email. # <tt>app/views/notifier/signup_notification.erb</tt> would be used to generate the email.
# #
# Variables defined in the model are accessible as instance variables in the view. # Variables defined in the model are accessible as instance variables in the view.
@ -71,48 +72,48 @@ module ActionMailer #:nodoc:
# #
# You got a new note! # You got a new note!
# <%= truncate(note.body, 25) %> # <%= truncate(note.body, 25) %>
# #
# #
# = Generating URLs # = Generating URLs
# #
# URLs can be generated in mailer views using <tt>url_for</tt> or named routes. # URLs can be generated in mailer views using <tt>url_for</tt> or named routes.
# Unlike controllers from Action Pack, the mailer instance doesn't have any context about the incoming request, # Unlike controllers from Action Pack, the mailer instance doesn't have any context about the incoming request,
# so you'll need to provide all of the details needed to generate a URL. # so you'll need to provide all of the details needed to generate a URL.
# #
# When using <tt>url_for</tt> you'll need to provide the <tt>:host</tt>, <tt>:controller</tt>, and <tt>:action</tt>: # When using <tt>url_for</tt> you'll need to provide the <tt>:host</tt>, <tt>:controller</tt>, and <tt>:action</tt>:
# #
# <%= url_for(:host => "example.com", :controller => "welcome", :action => "greeting") %> # <%= url_for(:host => "example.com", :controller => "welcome", :action => "greeting") %>
# #
# When using named routes you only need to supply the <tt>:host</tt>: # When using named routes you only need to supply the <tt>:host</tt>:
# #
# <%= users_url(:host => "example.com") %> # <%= users_url(:host => "example.com") %>
# #
# You will want to avoid using the <tt>name_of_route_path</tt> form of named routes because it doesn't make sense to # You will want to avoid using the <tt>name_of_route_path</tt> form of named routes because it doesn't make sense to
# generate relative URLs in email messages. # generate relative URLs in email messages.
# #
# It is also possible to set a default host that will be used in all mailers by setting the <tt>:host</tt> option in # It is also possible to set a default host that will be used in all mailers by setting the <tt>:host</tt> option in
# the <tt>ActionMailer::Base.default_url_options</tt> hash as follows: # the <tt>ActionMailer::Base.default_url_options</tt> hash as follows:
# #
# ActionMailer::Base.default_url_options[:host] = "example.com" # ActionMailer::Base.default_url_options[:host] = "example.com"
# #
# This can also be set as a configuration option in <tt>config/environment.rb</tt>: # This can also be set as a configuration option in <tt>config/environment.rb</tt>:
# #
# config.action_mailer.default_url_options = { :host => "example.com" } # config.action_mailer.default_url_options = { :host => "example.com" }
# #
# If you do decide to set a default <tt>:host</tt> for your mailers you will want to use the # If you do decide to set a default <tt>:host</tt> for your mailers you will want to use the
# <tt>:only_path => false</tt> option when using <tt>url_for</tt>. This will ensure that absolute URLs are generated because # <tt>:only_path => false</tt> option when using <tt>url_for</tt>. This will ensure that absolute URLs are generated because
# the <tt>url_for</tt> view helper will, by default, generate relative URLs when a <tt>:host</tt> option isn't # the <tt>url_for</tt> view helper will, by default, generate relative URLs when a <tt>:host</tt> option isn't
# explicitly provided. # explicitly provided.
# #
# = Sending mail # = Sending mail
# #
# Once a mailer action and template are defined, you can deliver your message or create it and save it # Once a mailer action and template are defined, you can deliver your message or create it and save it
# for delivery later: # for delivery later:
# #
# Notifier.deliver_signup_notification(david) # sends the email # Notifier.deliver_signup_notification(david) # sends the email
# mail = Notifier.create_signup_notification(david) # => a tmail object # mail = Notifier.create_signup_notification(david) # => a tmail object
# Notifier.deliver(mail) # Notifier.deliver(mail)
# #
# You never instantiate your mailer class. Rather, your delivery instance # You never instantiate your mailer class. Rather, your delivery instance
# methods are automatically wrapped in class methods that start with the word # methods are automatically wrapped in class methods that start with the word
# <tt>deliver_</tt> followed by the name of the mailer method that you would # <tt>deliver_</tt> followed by the name of the mailer method that you would
@ -127,13 +128,13 @@ module ActionMailer #:nodoc:
# #
# class MyMailer < ActionMailer::Base # class MyMailer < ActionMailer::Base
# def signup_notification(recipient) # def signup_notification(recipient)
# recipients recipient.email_address_with_name # recipients recipient.email_address_with_name
# subject "New account information" # subject "New account information"
# body "account" => recipient # from "system@example.com"
# from "system@example.com" # body :account => recipient
# content_type "text/html" # Here's where the magic happens # content_type "text/html"
# end # end
# end # end
# #
# #
# = Multipart email # = Multipart email
@ -145,6 +146,7 @@ module ActionMailer #:nodoc:
# recipients recipient.email_address_with_name # recipients recipient.email_address_with_name
# subject "New account information" # subject "New account information"
# from "system@example.com" # from "system@example.com"
# content_type "multipart/alternative"
# #
# part :content_type => "text/html", # part :content_type => "text/html",
# :body => render_message("signup-as-html", :account => recipient) # :body => render_message("signup-as-html", :account => recipient)
@ -155,21 +157,26 @@ module ActionMailer #:nodoc:
# end # end
# end # end
# end # end
# #
# Multipart messages can also be used implicitly because ActionMailer will automatically # Multipart messages can also be used implicitly because Action Mailer will automatically
# detect and use multipart templates, where each template is named after the name of the action, followed # detect and use multipart templates, where each template is named after the name of the action, followed
# by the content type. Each such detected template will be added as separate part to the message. # by the content type. Each such detected template will be added as separate part to the message.
# #
# For example, if the following templates existed: # For example, if the following templates existed:
# * signup_notification.text.plain.erb # * signup_notification.text.plain.erb
# * signup_notification.text.html.erb # * signup_notification.text.html.erb
# * signup_notification.text.xml.builder # * signup_notification.text.xml.builder
# * signup_notification.text.x-yaml.erb # * signup_notification.text.x-yaml.erb
#
# Each would be rendered and added as a separate part to the message,
# with the corresponding content type. The same body hash is passed to
# each template.
# #
# Each would be rendered and added as a separate part to the message,
# with the corresponding content type. The content type for the entire
# message is automatically set to <tt>multipart/alternative</tt>, which indicates
# that the email contains multiple different representations of the same email
# body. The same body hash is passed to each template.
#
# Implicit template rendering is not performed if any attachments or parts have been added to the email.
# This means that you'll have to manually add each part to the email and set the content type of the email
# to <tt>multipart/alternative</tt>.
# #
# = Attachments # = Attachments
# #
@ -191,7 +198,7 @@ module ActionMailer #:nodoc:
# a.body = generate_your_pdf_here() # a.body = generate_your_pdf_here()
# end # end
# end # end
# end # end
# #
# #
# = Configuration options # = Configuration options
@ -210,11 +217,11 @@ module ActionMailer #:nodoc:
# * <tt>:user_name</tt> - If your mail server requires authentication, set the username in this setting. # * <tt>:user_name</tt> - If your mail server requires authentication, set the username in this setting.
# * <tt>:password</tt> - If your mail server requires authentication, set the password in this setting. # * <tt>:password</tt> - If your mail server requires authentication, set the password in this setting.
# * <tt>:authentication</tt> - If your mail server requires authentication, you need to specify the authentication type here. # * <tt>:authentication</tt> - If your mail server requires authentication, you need to specify the authentication type here.
# This is a symbol and one of <tt>:plain</tt>, <tt>:login</tt>, <tt>:cram_md5</tt> # This is a symbol and one of <tt>:plain</tt>, <tt>:login</tt>, <tt>:cram_md5</tt>.
# #
# * <tt>sendmail_settings</tt> - Allows you to override options for the <tt>:sendmail</tt> delivery method # * <tt>sendmail_settings</tt> - Allows you to override options for the <tt>:sendmail</tt> delivery method.
# * <tt>:location</tt> - The location of the sendmail executable, defaults to "/usr/sbin/sendmail" # * <tt>:location</tt> - The location of the sendmail executable. Defaults to <tt>/usr/sbin/sendmail</tt>.
# * <tt>:arguments</tt> - The command line arguments # * <tt>:arguments</tt> - The command line arguments. Defaults to <tt>-i -t</tt>.
# #
# * <tt>raise_delivery_errors</tt> - Whether or not errors should be raised if the email fails to be delivered. # * <tt>raise_delivery_errors</tt> - Whether or not errors should be raised if the email fails to be delivered.
# #
@ -227,16 +234,16 @@ module ActionMailer #:nodoc:
# for unit and functional testing. # for unit and functional testing.
# #
# * <tt>default_charset</tt> - The default charset used for the body and to encode the subject. Defaults to UTF-8. You can also # * <tt>default_charset</tt> - The default charset used for the body and to encode the subject. Defaults to UTF-8. You can also
# pick a different charset from inside a method with <tt>@charset</tt>. # pick a different charset from inside a method with +charset+.
# * <tt>default_content_type</tt> - The default content type used for the main part of the message. Defaults to "text/plain". You # * <tt>default_content_type</tt> - The default content type used for the main part of the message. Defaults to "text/plain". You
# can also pick a different content type from inside a method with <tt>@content_type</tt>. # can also pick a different content type from inside a method with +content_type+.
# * <tt>default_mime_version</tt> - The default mime version used for the message. Defaults to "1.0". You # * <tt>default_mime_version</tt> - The default mime version used for the message. Defaults to <tt>1.0</tt>. You
# can also pick a different value from inside a method with <tt>@mime_version</tt>. # can also pick a different value from inside a method with +mime_version+.
# * <tt>default_implicit_parts_order</tt> - When a message is built implicitly (i.e. multiple parts are assembled from templates # * <tt>default_implicit_parts_order</tt> - When a message is built implicitly (i.e. multiple parts are assembled from templates
# which specify the content type in their filenames) this variable controls how the parts are ordered. Defaults to # which specify the content type in their filenames) this variable controls how the parts are ordered. Defaults to
# ["text/html", "text/enriched", "text/plain"]. Items that appear first in the array have higher priority in the mail client # <tt>["text/html", "text/enriched", "text/plain"]</tt>. Items that appear first in the array have higher priority in the mail client
# and appear last in the mime encoded message. You can also pick a different order from inside a method with # and appear last in the mime encoded message. You can also pick a different order from inside a method with
# <tt>@implicit_parts_order</tt>. # +implicit_parts_order+.
class Base class Base
include AdvAttrAccessor, PartContainer include AdvAttrAccessor, PartContainer
include ActionController::UrlWriter if Object.const_defined?(:ActionController) include ActionController::UrlWriter if Object.const_defined?(:ActionController)
@ -249,16 +256,16 @@ module ActionMailer #:nodoc:
cattr_accessor :template_extensions cattr_accessor :template_extensions
@@template_extensions = ['erb', 'builder', 'rhtml', 'rxml'] @@template_extensions = ['erb', 'builder', 'rhtml', 'rxml']
@@smtp_settings = { @@smtp_settings = {
:address => "localhost", :address => "localhost",
:port => 25, :port => 25,
:domain => 'localhost.localdomain', :domain => 'localhost.localdomain',
:user_name => nil, :user_name => nil,
:password => nil, :password => nil,
:authentication => nil :authentication => nil
} }
cattr_accessor :smtp_settings cattr_accessor :smtp_settings
@@sendmail_settings = { @@sendmail_settings = {
:location => '/usr/sbin/sendmail', :location => '/usr/sbin/sendmail',
:arguments => '-i -t' :arguments => '-i -t'
@ -270,10 +277,10 @@ module ActionMailer #:nodoc:
superclass_delegating_accessor :delivery_method superclass_delegating_accessor :delivery_method
self.delivery_method = :smtp self.delivery_method = :smtp
@@perform_deliveries = true @@perform_deliveries = true
cattr_accessor :perform_deliveries cattr_accessor :perform_deliveries
@@deliveries = [] @@deliveries = []
cattr_accessor :deliveries cattr_accessor :deliveries
@ -282,7 +289,7 @@ module ActionMailer #:nodoc:
@@default_content_type = "text/plain" @@default_content_type = "text/plain"
cattr_accessor :default_content_type cattr_accessor :default_content_type
@@default_mime_version = "1.0" @@default_mime_version = "1.0"
cattr_accessor :default_mime_version cattr_accessor :default_mime_version
@ -291,47 +298,51 @@ module ActionMailer #:nodoc:
# Specify the BCC addresses for the message # Specify the BCC addresses for the message
adv_attr_accessor :bcc adv_attr_accessor :bcc
# Define the body of the message. This is either a Hash (in which case it # Define the body of the message. This is either a Hash (in which case it
# specifies the variables to pass to the template when it is rendered), # specifies the variables to pass to the template when it is rendered),
# or a string, in which case it specifies the actual text of the message. # or a string, in which case it specifies the actual text of the message.
adv_attr_accessor :body adv_attr_accessor :body
# Specify the CC addresses for the message. # Specify the CC addresses for the message.
adv_attr_accessor :cc adv_attr_accessor :cc
# Specify the charset to use for the message. This defaults to the # Specify the charset to use for the message. This defaults to the
# +default_charset+ specified for ActionMailer::Base. # +default_charset+ specified for ActionMailer::Base.
adv_attr_accessor :charset adv_attr_accessor :charset
# Specify the content type for the message. This defaults to <tt>text/plain</tt> # Specify the content type for the message. This defaults to <tt>text/plain</tt>
# in most cases, but can be automatically set in some situations. # in most cases, but can be automatically set in some situations.
adv_attr_accessor :content_type adv_attr_accessor :content_type
# Specify the from address for the message. # Specify the from address for the message.
adv_attr_accessor :from adv_attr_accessor :from
# Specify the address (if different than the "from" address) to direct
# replies to this message.
adv_attr_accessor :reply_to
# Specify additional headers to be added to the message. # Specify additional headers to be added to the message.
adv_attr_accessor :headers adv_attr_accessor :headers
# Specify the order in which parts should be sorted, based on content-type. # Specify the order in which parts should be sorted, based on content-type.
# This defaults to the value for the +default_implicit_parts_order+. # This defaults to the value for the +default_implicit_parts_order+.
adv_attr_accessor :implicit_parts_order adv_attr_accessor :implicit_parts_order
# Defaults to "1.0", but may be explicitly given if needed. # Defaults to "1.0", but may be explicitly given if needed.
adv_attr_accessor :mime_version adv_attr_accessor :mime_version
# The recipient addresses for the message, either as a string (for a single # The recipient addresses for the message, either as a string (for a single
# address) or an array (for multiple addresses). # address) or an array (for multiple addresses).
adv_attr_accessor :recipients adv_attr_accessor :recipients
# The date on which the message was sent. If not set (the default), the # The date on which the message was sent. If not set (the default), the
# header will be set by the delivery agent. # header will be set by the delivery agent.
adv_attr_accessor :sent_on adv_attr_accessor :sent_on
# Specify the subject of the message. # Specify the subject of the message.
adv_attr_accessor :subject adv_attr_accessor :subject
# Specify the template name to use for current message. This is the "base" # Specify the template name to use for current message. This is the "base"
# template name, without the extension or directory, and may be used to # template name, without the extension or directory, and may be used to
# have multiple mailer methods share the same template. # have multiple mailer methods share the same template.
@ -347,7 +358,7 @@ module ActionMailer #:nodoc:
self.class.mailer_name self.class.mailer_name
end end
end end
def mailer_name=(value) def mailer_name=(value)
self.class.mailer_name = value self.class.mailer_name = value
end end
@ -377,8 +388,8 @@ module ActionMailer #:nodoc:
# Receives a raw email, parses it into an email object, decodes it, # Receives a raw email, parses it into an email object, decodes it,
# instantiates a new mailer, and passes the email object to the mailer # instantiates a new mailer, and passes the email object to the mailer
# object's #receive method. If you want your mailer to be able to # object's +receive+ method. If you want your mailer to be able to
# process incoming messages, you'll need to implement a #receive # process incoming messages, you'll need to implement a +receive+
# method that accepts the email object as a parameter: # method that accepts the email object as a parameter:
# #
# class MyMailer < ActionMailer::Base # class MyMailer < ActionMailer::Base
@ -425,7 +436,7 @@ module ActionMailer #:nodoc:
# remain uninitialized (useful when you only need to invoke the "receive" # remain uninitialized (useful when you only need to invoke the "receive"
# method, for instance). # method, for instance).
def initialize(method_name=nil, *parameters) #:nodoc: def initialize(method_name=nil, *parameters) #:nodoc:
create!(method_name, *parameters) if method_name create!(method_name, *parameters) if method_name
end end
# Initialize the mailer via the given +method_name+. The body will be # Initialize the mailer via the given +method_name+. The body will be
@ -484,7 +495,7 @@ module ActionMailer #:nodoc:
end end
# Delivers a TMail::Mail object. By default, it delivers the cached mail # Delivers a TMail::Mail object. By default, it delivers the cached mail
# object (from the #create! method). If no cached mail object exists, and # object (from the <tt>create!</tt> method). If no cached mail object exists, and
# no alternate has been given as the parameter, this will fail. # no alternate has been given as the parameter, this will fail.
def deliver!(mail = @mail) def deliver!(mail = @mail)
raise "no mail object available for delivery!" unless mail raise "no mail object available for delivery!" unless mail
@ -511,7 +522,7 @@ module ActionMailer #:nodoc:
@content_type ||= @@default_content_type.dup @content_type ||= @@default_content_type.dup
@implicit_parts_order ||= @@default_implicit_parts_order.dup @implicit_parts_order ||= @@default_implicit_parts_order.dup
@template ||= method_name @template ||= method_name
@mailer_name ||= Inflector.underscore(self.class.name) @mailer_name ||= self.class.name.underscore
@parts ||= [] @parts ||= []
@headers ||= {} @headers ||= {}
@body ||= {} @body ||= {}
@ -570,13 +581,14 @@ module ActionMailer #:nodoc:
def create_mail def create_mail
m = TMail::Mail.new m = TMail::Mail.new
m.subject, = quote_any_if_necessary(charset, subject) m.subject, = quote_any_if_necessary(charset, subject)
m.to, m.from = quote_any_address_if_necessary(charset, recipients, from) m.to, m.from = quote_any_address_if_necessary(charset, recipients, from)
m.bcc = quote_address_if_necessary(bcc, charset) unless bcc.nil? m.bcc = quote_address_if_necessary(bcc, charset) unless bcc.nil?
m.cc = quote_address_if_necessary(cc, charset) unless cc.nil? m.cc = quote_address_if_necessary(cc, charset) unless cc.nil?
m.reply_to = quote_address_if_necessary(reply_to, charset) unless reply_to.nil?
m.mime_version = mime_version unless mime_version.nil? m.mime_version = mime_version unless mime_version.nil?
m.date = sent_on.to_time rescue sent_on if sent_on m.date = sent_on.to_time rescue sent_on if sent_on
headers.each { |k, v| m[k] = v } headers.each { |k, v| m[k] = v }
real_content_type, ctype_attrs = parse_content_type real_content_type, ctype_attrs = parse_content_type
@ -597,7 +609,7 @@ module ActionMailer #:nodoc:
part = (TMail::Mail === p ? p : p.to_mail(self)) part = (TMail::Mail === p ? p : p.to_mail(self))
m.parts << part m.parts << part
end end
if real_content_type =~ /multipart/ if real_content_type =~ /multipart/
ctype_attrs.delete "charset" ctype_attrs.delete "charset"
m.set_content_type(real_content_type, nil, ctype_attrs) m.set_content_type(real_content_type, nil, ctype_attrs)
@ -612,7 +624,7 @@ module ActionMailer #:nodoc:
mail.ready_to_send mail.ready_to_send
sender = mail['return-path'] || mail.from sender = mail['return-path'] || mail.from
Net::SMTP.start(smtp_settings[:address], smtp_settings[:port], smtp_settings[:domain], Net::SMTP.start(smtp_settings[:address], smtp_settings[:port], smtp_settings[:domain],
smtp_settings[:user_name], smtp_settings[:password], smtp_settings[:authentication]) do |smtp| smtp_settings[:user_name], smtp_settings[:password], smtp_settings[:authentication]) do |smtp|
smtp.sendmail(mail.encoded, sender, destinations) smtp.sendmail(mail.encoded, sender, destinations)
end end

View file

@ -34,7 +34,7 @@ module ActionMailer
# helper FooHelper # helper FooHelper
# includes FooHelper in the template class. # includes FooHelper in the template class.
# helper { def foo() "#{bar} is the very best" end } # helper { def foo() "#{bar} is the very best" end }
# evaluates the block in the template class, adding method #foo. # evaluates the block in the template class, adding method +foo+.
# helper(:three, BlindHelper) { def mice() 'mice' end } # helper(:three, BlindHelper) { def mice() 'mice' end }
# does all three. # does all three.
def helper(*args, &block) def helper(*args, &block)

View file

@ -5,7 +5,7 @@ require 'action_mailer/utils'
module ActionMailer module ActionMailer
# Represents a subpart of an email message. It shares many similar # Represents a subpart of an email message. It shares many similar
# attributes of ActionMailer::Base. Although you can create parts manually # attributes of ActionMailer::Base. Although you can create parts manually
# and add them to the #parts list of the mailer, it is easier # and add them to the +parts+ list of the mailer, it is easier
# to use the helper methods in ActionMailer::PartContainer. # to use the helper methods in ActionMailer::PartContainer.
class Part class Part
include ActionMailer::AdvAttrAccessor include ActionMailer::AdvAttrAccessor
@ -13,7 +13,7 @@ module ActionMailer
# Represents the body of the part, as a string. This should not be a # Represents the body of the part, as a string. This should not be a
# Hash (like ActionMailer::Base), but if you want a template to be rendered # Hash (like ActionMailer::Base), but if you want a template to be rendered
# into the body of a subpart you can do it with the mailer's #render method # into the body of a subpart you can do it with the mailer's +render+ method
# and assign the result here. # and assign the result here.
adv_attr_accessor :body adv_attr_accessor :body

View file

@ -40,7 +40,7 @@ module ActionMailer
# regular email address, or it can be a phrase followed by an address in # regular email address, or it can be a phrase followed by an address in
# brackets. The phrase is the only part that will be quoted, and only if # brackets. The phrase is the only part that will be quoted, and only if
# it needs to be. This allows extended characters to be used in the # it needs to be. This allows extended characters to be used in the
# "to", "from", "cc", and "bcc" headers. # "to", "from", "cc", "bcc" and "reply-to" headers.
def quote_address_if_necessary(address, charset) def quote_address_if_necessary(address, charset)
if Array === address if Array === address
address.map { |a| quote_address_if_necessary(a, charset) } address.map { |a| quote_address_if_necessary(a, charset) }

View file

@ -2,9 +2,9 @@
require 'rubygems' require 'rubygems'
begin begin
gem 'tmail', '~> 1.2.2' gem 'tmail', '~> 1.2.3'
rescue Gem::LoadError rescue Gem::LoadError
$:.unshift "#{File.dirname(__FILE__)}/vendor/tmail-1.2.2" $:.unshift "#{File.dirname(__FILE__)}/vendor/tmail-1.2.3"
end end
begin begin

View file

@ -339,22 +339,36 @@ module TMail
def scanadd( str, force = false ) def scanadd( str, force = false )
types = '' types = ''
strs = [] strs = []
if str.respond_to?(:encoding)
enc = str.encoding
str.force_encoding(Encoding::ASCII_8BIT)
end
until str.empty? until str.empty?
if m = /\A[^\e\t\r\n ]+/.match(str) if m = /\A[^\e\t\r\n ]+/.match(str)
types << (force ? 'j' : 'a') types << (force ? 'j' : 'a')
strs.push m[0] if str.respond_to?(:encoding)
strs.push m[0].force_encoding(enc)
else
strs.push m[0]
end
elsif m = /\A[\t\r\n ]+/.match(str) elsif m = /\A[\t\r\n ]+/.match(str)
types << 's' types << 's'
strs.push m[0] if str.respond_to?(:encoding)
strs.push m[0].force_encoding(enc)
else
strs.push m[0]
end
elsif m = /\A\e../.match(str) elsif m = /\A\e../.match(str)
esc = m[0] esc = m[0]
str = m.post_match str = m.post_match
if esc != "\e(B" and m = /\A[^\e]+/.match(str) if esc != "\e(B" and m = /\A[^\e]+/.match(str)
types << 'j' types << 'j'
strs.push m[0] if str.respond_to?(:encoding)
strs.push m[0].force_encoding(enc)
else
strs.push m[0]
end
end end
else else
@ -453,7 +467,13 @@ module TMail
size = max_bytes(chunksize, str.size) - 6 size = max_bytes(chunksize, str.size) - 6
size = (size % 2 == 0) ? (size) : (size - 1) size = (size % 2 == 0) ? (size) : (size - 1)
return nil if size <= 0 return nil if size <= 0
"\e$B#{str.slice!(0, size)}\e(B" if str.respond_to?(:encoding)
enc = str.encoding
str.force_encoding(Encoding::ASCII_8BIT)
"\e$B#{str.slice!(0, size)}\e(B".force_encoding(enc)
else
"\e$B#{str.slice!(0, size)}\e(B"
end
end end
def extract_A( chunksize, str ) def extract_A( chunksize, str )

View file

@ -591,12 +591,17 @@ module TMail
end end
# Destructively sets the message ID of the mail object instance to the passed in string # Destructively sets the message ID of the mail object instance to the passed in string
#
# Invalid message IDs are ignored (silently, unless configured otherwise) and result in
# a nil message ID. Left and right angle brackets are required.
# #
# Example: # Example:
# #
# mail = TMail::Mail.new # mail = TMail::Mail.new
# mail.message_id = "<348F04F142D69C21-291E56D292BC@xxxx.net>"
# mail.message_id #=> "<348F04F142D69C21-291E56D292BC@xxxx.net>"
# mail.message_id = "this_is_my_badly_formatted_message_id" # mail.message_id = "this_is_my_badly_formatted_message_id"
# mail.message_id #=> "this_is_my_badly_formatted_message_id" # mail.message_id #=> nil
def message_id=( str ) def message_id=( str )
set_string_attr 'Message-Id', str set_string_attr 'Message-Id', str
end end

View file

@ -408,8 +408,8 @@ module TMail
when /\AFrom (\S+)/ when /\AFrom (\S+)/
unixfrom = $1 unixfrom = $1
when /^charset=.*/ when /^charset=.*/
else else
raise SyntaxError, "wrong mail header: '#{line.inspect}'" raise SyntaxError, "wrong mail header: '#{line.inspect}'"
end end

View file

@ -118,7 +118,7 @@ module TMail
ATOM_UNSAFE = /[#{Regexp.quote aspecial}#{control}#{lwsp}]/n ATOM_UNSAFE = /[#{Regexp.quote aspecial}#{control}#{lwsp}]/n
PHRASE_UNSAFE = /[#{Regexp.quote aspecial}#{control}]/n PHRASE_UNSAFE = /[#{Regexp.quote aspecial}#{control}]/n
TOKEN_UNSAFE = /[#{Regexp.quote tspecial}#{control}#{lwsp}]/n TOKEN_UNSAFE = /[#{Regexp.quote tspecial}#{control}#{lwsp}]/n
# Returns true if the string supplied is free from characters not allowed as an ATOM # Returns true if the string supplied is free from characters not allowed as an ATOM
def atom_safe?( str ) def atom_safe?( str )
not ATOM_UNSAFE === str not ATOM_UNSAFE === str

View file

@ -32,7 +32,7 @@ module TMail
module VERSION module VERSION
MAJOR = 1 MAJOR = 1
MINOR = 2 MINOR = 2
TINY = 2 TINY = 3
STRING = [MAJOR, MINOR, TINY].join('.') STRING = [MAJOR, MINOR, TINY].join('.')
end end

View file

@ -1,8 +1,8 @@
module ActionMailer module ActionMailer
module VERSION #:nodoc: module VERSION #:nodoc:
MAJOR = 2 MAJOR = 2
MINOR = 0 MINOR = 1
TINY = 991 TINY = 0
STRING = [MAJOR, MINOR, TINY].join('.') STRING = [MAJOR, MINOR, TINY].join('.')
end end

View file

@ -40,6 +40,15 @@ class TestMailer < ActionMailer::Base
body "Nothing to see here." body "Nothing to see here."
end end
def different_reply_to(recipient)
recipients recipient
subject "testing reply_to"
from "system@loudthinking.com"
sent_on Time.local(2008, 5, 23)
reply_to "atraver@gmail.com"
body "Nothing to see here."
end
def iso_charset(recipient) def iso_charset(recipient)
@recipients = recipient @recipients = recipient
@subject = "testing isø charsets" @subject = "testing isø charsets"
@ -445,6 +454,31 @@ class ActionMailerTest < Test::Unit::TestCase
assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
end end
def test_reply_to
expected = new_mail
expected.to = @recipient
expected.subject = "testing reply_to"
expected.body = "Nothing to see here."
expected.from = "system@loudthinking.com"
expected.reply_to = "atraver@gmail.com"
expected.date = Time.local 2008, 5, 23
created = nil
assert_nothing_raised do
created = TestMailer.create_different_reply_to @recipient
end
assert_not_nil created
assert_equal expected.encoded, created.encoded
assert_nothing_raised do
TestMailer.deliver_different_reply_to @recipient
end
assert_not_nil ActionMailer::Base.deliveries.first
assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
end
def test_iso_charset def test_iso_charset
expected = new_mail( "iso-8859-1" ) expected = new_mail( "iso-8859-1" )
expected.to = @recipient expected.to = @recipient

View file

@ -1,9 +1,12 @@
*2.1.0 RC1 (May 11th, 2008)* *2.1.0 (May 31st, 2008)*
* InstanceTag#default_time_from_options overflows to DateTime [Geoff Buesing]
* Fixed that forgery protection can be used without session tracking (Peter Jones) [#139] * Fixed that forgery protection can be used without session tracking (Peter Jones) [#139]
* Added session(:on) to turn session management back on in a controller subclass if the superclass turned it off (Peter Jones) [#136] * Added session(:on) to turn session management back on in a controller subclass if the superclass turned it off (Peter Jones) [#136]
* Change the request forgery protection to go by Content-Type instead of request.format so that you can't bypass it by POSTing to "#{request.uri}.xml" [rick]
* InstanceTag#default_time_from_options with hash args uses Time.current as default; respects hash settings when time falls in system local spring DST gap [Geoff Buesing] * InstanceTag#default_time_from_options with hash args uses Time.current as default; respects hash settings when time falls in system local spring DST gap [Geoff Buesing]
* select_date defaults to Time.zone.today when config.time_zone is set [Geoff Buesing] * select_date defaults to Time.zone.today when config.time_zone is set [Geoff Buesing]

View file

@ -76,7 +76,7 @@ spec = Gem::Specification.new do |s|
s.has_rdoc = true s.has_rdoc = true
s.requirements << 'none' s.requirements << 'none'
s.add_dependency('activesupport', '= 2.0.991' + PKG_BUILD) s.add_dependency('activesupport', '= 2.1.0' + PKG_BUILD)
s.require_path = 'lib' s.require_path = 'lib'
s.autorequire = 'action_controller' s.autorequire = 'action_controller'

View file

@ -1,7 +1,8 @@
module ActionController module ActionController
module Assertions module Assertions
module ModelAssertions module ModelAssertions
# Ensures that the passed record is valid by ActiveRecord standards and returns any error messages if it is not. # Ensures that the passed record is valid by Active Record standards and
# returns any error messages if it is not.
# #
# ==== Examples # ==== Examples
# #

View file

@ -59,7 +59,7 @@ module ActionController
end end
end end
# Asserts that the provided options can be used to generate the provided path. This is the inverse of #assert_recognizes. # Asserts that the provided options can be used to generate the provided path. This is the inverse of +assert_recognizes+.
# The +extras+ parameter is used to tell the request the names and values of additional request parameters that would be in # The +extras+ parameter is used to tell the request the names and values of additional request parameters that would be in
# a query string. The +message+ parameter allows you to specify a custom error message for assertion failures. # a query string. The +message+ parameter allows you to specify a custom error message for assertion failures.
# #
@ -96,8 +96,8 @@ module ActionController
end end
# Asserts that path and options match both ways; in other words, it verifies that <tt>path</tt> generates # Asserts that path and options match both ways; in other words, it verifies that <tt>path</tt> generates
# <tt>options</tt> and then that <tt>options</tt> generates <tt>path</tt>. This essentially combines #assert_recognizes # <tt>options</tt> and then that <tt>options</tt> generates <tt>path</tt>. This essentially combines +assert_recognizes+
# and #assert_generates into one step. # and +assert_generates+ into one step.
# #
# The +extras+ hash allows you to specify options that would normally be provided as a query string to the action. The # The +extras+ hash allows you to specify options that would normally be provided as a query string to the action. The
# +message+ parameter allows you to specify a custom error message to display upon failure. # +message+ parameter allows you to specify a custom error message to display upon failure.

View file

@ -12,12 +12,12 @@ module ActionController
NO_STRIP = %w{pre script style textarea} NO_STRIP = %w{pre script style textarea}
end end
# Adds the #assert_select method for use in Rails functional # Adds the +assert_select+ method for use in Rails functional
# test cases, which can be used to make assertions on the response HTML of a controller # test cases, which can be used to make assertions on the response HTML of a controller
# action. You can also call #assert_select within another #assert_select to # action. You can also call +assert_select+ within another +assert_select+ to
# make assertions on elements selected by the enclosing assertion. # make assertions on elements selected by the enclosing assertion.
# #
# Use #css_select to select elements without making an assertions, either # Use +css_select+ to select elements without making an assertions, either
# from the response HTML or elements selected by the enclosing assertion. # from the response HTML or elements selected by the enclosing assertion.
# #
# In addition to HTML responses, you can make the following assertions: # In addition to HTML responses, you can make the following assertions:
@ -44,8 +44,8 @@ module ActionController
# base element and any of its children. Returns an empty array if no # base element and any of its children. Returns an empty array if no
# match is found. # match is found.
# #
# The selector may be a CSS selector expression (+String+), an expression # The selector may be a CSS selector expression (String), an expression
# with substitution values (+Array+) or an HTML::Selector object. # with substitution values (Array) or an HTML::Selector object.
# #
# ==== Examples # ==== Examples
# # Selects all div tags # # Selects all div tags
@ -114,8 +114,8 @@ module ActionController
# starting from (and including) that element and all its children in # starting from (and including) that element and all its children in
# depth-first order. # depth-first order.
# #
# If no element if specified, calling #assert_select will select from the # If no element if specified, calling +assert_select+ will select from the
# response HTML. Calling #assert_select inside an #assert_select block will # response HTML. Calling #assert_select inside an +assert_select+ block will
# run the assertion for each element selected by the enclosing assertion. # run the assertion for each element selected by the enclosing assertion.
# #
# ==== Example # ==== Example
@ -130,7 +130,7 @@ module ActionController
# assert_select "li" # assert_select "li"
# end # end
# #
# The selector may be a CSS selector expression (+String+), an expression # The selector may be a CSS selector expression (String), an expression
# with substitution values, or an HTML::Selector object. # with substitution values, or an HTML::Selector object.
# #
# === Equality Tests # === Equality Tests
@ -356,16 +356,16 @@ module ActionController
# #
# === Using blocks # === Using blocks
# #
# Without a block, #assert_select_rjs merely asserts that the response # Without a block, +assert_select_rjs+ merely asserts that the response
# contains one or more RJS statements that replace or update content. # contains one or more RJS statements that replace or update content.
# #
# With a block, #assert_select_rjs also selects all elements used in # With a block, +assert_select_rjs+ also selects all elements used in
# these statements and passes them to the block. Nested assertions are # these statements and passes them to the block. Nested assertions are
# supported. # supported.
# #
# Calling #assert_select_rjs with no arguments and using nested asserts # Calling +assert_select_rjs+ with no arguments and using nested asserts
# asserts that the HTML content is returned by one or more RJS statements. # asserts that the HTML content is returned by one or more RJS statements.
# Using #assert_select directly makes the same assertion on the content, # Using +assert_select+ directly makes the same assertion on the content,
# but without distinguishing whether the content is returned in an HTML # but without distinguishing whether the content is returned in an HTML
# or JavaScript. # or JavaScript.
# #
@ -601,7 +601,7 @@ module ActionController
RJS_PATTERN_UNICODE_ESCAPED_CHAR = /\\u([0-9a-zA-Z]{4})/ RJS_PATTERN_UNICODE_ESCAPED_CHAR = /\\u([0-9a-zA-Z]{4})/
end end
# #assert_select and #css_select call this to obtain the content in the HTML # +assert_select+ and +css_select+ call this to obtain the content in the HTML
# page, or from all the RJS statements, depending on the type of response. # page, or from all the RJS statements, depending on the type of response.
def response_from_page_or_rjs() def response_from_page_or_rjs()
content_type = @response.content_type content_type = @response.content_type

View file

@ -91,7 +91,7 @@ module ActionController
# :descendant => { :tag => "span", # :descendant => { :tag => "span",
# :child => /hello world/ } # :child => /hello world/ }
# #
# <b>Please note</b>: #assert_tag and #assert_no_tag only work # <b>Please note</b>: +assert_tag+ and +assert_no_tag+ only work
# with well-formed XHTML. They recognize a few tags as implicitly self-closing # with well-formed XHTML. They recognize a few tags as implicitly self-closing
# (like br and hr and such) but will not work correctly with tags # (like br and hr and such) but will not work correctly with tags
# that allow optional closing tags (p, li, td). <em>You must explicitly # that allow optional closing tags (p, li, td). <em>You must explicitly
@ -104,8 +104,8 @@ module ActionController
end end
end end
# Identical to #assert_tag, but asserts that a matching tag does _not_ # Identical to +assert_tag+, but asserts that a matching tag does _not_
# exist. (See #assert_tag for a full discussion of the syntax.) # exist. (See +assert_tag+ for a full discussion of the syntax.)
# #
# === Examples # === Examples
# # Assert that there is not a "div" containing a "p" # # Assert that there is not a "div" containing a "p"

View file

@ -104,7 +104,7 @@ module ActionController #:nodoc:
# end # end
# #
# Actions, by default, render a template in the <tt>app/views</tt> directory corresponding to the name of the controller and action # Actions, by default, render a template in the <tt>app/views</tt> directory corresponding to the name of the controller and action
# after executing code in the action. For example, the +index+ action of the +GuestBookController+ would render the # after executing code in the action. For example, the +index+ action of the GuestBookController would render the
# template <tt>app/views/guestbook/index.erb</tt> by default after populating the <tt>@entries</tt> instance variable. # template <tt>app/views/guestbook/index.erb</tt> by default after populating the <tt>@entries</tt> instance variable.
# #
# Unlike index, the sign action will not render a template. After performing its main purpose (creating a # Unlike index, the sign action will not render a template. After performing its main purpose (creating a
@ -118,10 +118,10 @@ module ActionController #:nodoc:
# #
# Requests are processed by the Action Controller framework by extracting the value of the "action" key in the request parameters. # Requests are processed by the Action Controller framework by extracting the value of the "action" key in the request parameters.
# This value should hold the name of the action to be performed. Once the action has been identified, the remaining # This value should hold the name of the action to be performed. Once the action has been identified, the remaining
# request parameters, the session (if one is available), and the full request with all the http headers are made available to # request parameters, the session (if one is available), and the full request with all the HTTP headers are made available to
# the action through instance variables. Then the action is performed. # the action through instance variables. Then the action is performed.
# #
# The full request object is available with the request accessor and is primarily used to query for http headers. These queries # The full request object is available with the request accessor and is primarily used to query for HTTP headers. These queries
# are made by accessing the environment hash, like this: # are made by accessing the environment hash, like this:
# #
# def server_ip # def server_ip
@ -259,12 +259,12 @@ module ActionController #:nodoc:
DEFAULT_RENDER_STATUS_CODE = "200 OK" DEFAULT_RENDER_STATUS_CODE = "200 OK"
include StatusCodes include StatusCodes
# Controller specific instance variables which will not be accessible inside views. # Controller specific instance variables which will not be accessible inside views.
@@protected_view_variables = %w(@assigns @performed_redirect @performed_render @variables_added @request_origin @url @parent_controller @@protected_view_variables = %w(@assigns @performed_redirect @performed_render @variables_added @request_origin @url @parent_controller
@action_name @before_filter_chain_aborted @action_cache_path @_session @_cookies @_headers @_params @action_name @before_filter_chain_aborted @action_cache_path @_session @_cookies @_headers @_params
@_flash @_response) @_flash @_response)
# Prepends all the URL-generating helpers from AssetHelper. This makes it possible to easily move javascripts, stylesheets, # Prepends all the URL-generating helpers from AssetHelper. This makes it possible to easily move javascripts, stylesheets,
# and images to a dedicated asset server away from the main web server. Example: # and images to a dedicated asset server away from the main web server. Example:
# ActionController::Base.asset_host = "http://assets.example.com" # ActionController::Base.asset_host = "http://assets.example.com"
@ -291,10 +291,10 @@ module ActionController #:nodoc:
cattr_accessor :allow_concurrency cattr_accessor :allow_concurrency
# Modern REST web services often need to submit complex data to the web application. # Modern REST web services often need to submit complex data to the web application.
# The param_parsers hash lets you register handlers which will process the http body and add parameters to the # The <tt>@@param_parsers</tt> hash lets you register handlers which will process the HTTP body and add parameters to the
# <tt>params</tt> hash. These handlers are invoked for post and put requests. # <tt>params</tt> hash. These handlers are invoked for POST and PUT requests.
# #
# By default application/xml is enabled. A XmlSimple class with the same param name as the root will be instantiated # By default <tt>application/xml</tt> is enabled. A XmlSimple class with the same param name as the root will be instantiated
# in the <tt>params</tt>. This allows XML requests to mask themselves as regular form submissions, so you can have one # in the <tt>params</tt>. This allows XML requests to mask themselves as regular form submissions, so you can have one
# action serve both regular forms and web service requests. # action serve both regular forms and web service requests.
# #
@ -307,7 +307,7 @@ module ActionController #:nodoc:
# #
# Note: Up until release 1.1 of Rails, Action Controller would default to using XmlSimple configured to discard the # Note: Up until release 1.1 of Rails, Action Controller would default to using XmlSimple configured to discard the
# root node for such requests. The new default is to keep the root, such that "<r><name>David</name></r>" results # root node for such requests. The new default is to keep the root, such that "<r><name>David</name></r>" results
# in params[:r][:name] for "David" instead of params[:name]. To get the old behavior, you can # in <tt>params[:r][:name]</tt> for "David" instead of <tt>params[:name]</tt>. To get the old behavior, you can
# re-register XmlSimple as application/xml handler ike this: # re-register XmlSimple as application/xml handler ike this:
# #
# ActionController::Base.param_parsers[Mime::XML] = # ActionController::Base.param_parsers[Mime::XML] =
@ -325,7 +325,7 @@ module ActionController #:nodoc:
# Controls the default charset for all renders. # Controls the default charset for all renders.
@@default_charset = "utf-8" @@default_charset = "utf-8"
cattr_accessor :default_charset cattr_accessor :default_charset
# The logger is used for generating information on the action run-time (including benchmarking) if available. # The logger is used for generating information on the action run-time (including benchmarking) if available.
# Can be set to nil for no logging. Compatible with both Ruby's own Logger and Log4r loggers. # Can be set to nil for no logging. Compatible with both Ruby's own Logger and Log4r loggers.
cattr_accessor :logger cattr_accessor :logger
@ -333,7 +333,7 @@ module ActionController #:nodoc:
# Controls the resource action separator # Controls the resource action separator
@@resource_action_separator = "/" @@resource_action_separator = "/"
cattr_accessor :resource_action_separator cattr_accessor :resource_action_separator
# Allow to override path names for default resources' actions # Allow to override path names for default resources' actions
@@resources_path_names = { :new => 'new', :edit => 'edit' } @@resources_path_names = { :new => 'new', :edit => 'edit' }
cattr_accessor :resources_path_names cattr_accessor :resources_path_names
@ -433,7 +433,7 @@ module ActionController #:nodoc:
end end
# Adds a view_path to the front of the view_paths array. # Adds a view_path to the front of the view_paths array.
# If the current class has no view paths, copy them from # If the current class has no view paths, copy them from
# the superclass. This change will be visible for all future requests. # the superclass. This change will be visible for all future requests.
# #
# ArticleController.prepend_view_path("views/default") # ArticleController.prepend_view_path("views/default")
@ -444,9 +444,9 @@ module ActionController #:nodoc:
view_paths.unshift(*path) view_paths.unshift(*path)
ActionView::TemplateFinder.process_view_paths(path) ActionView::TemplateFinder.process_view_paths(path)
end end
# Adds a view_path to the end of the view_paths array. # Adds a view_path to the end of the view_paths array.
# If the current class has no view paths, copy them from # If the current class has no view paths, copy them from
# the superclass. This change will be visible for all future requests. # the superclass. This change will be visible for all future requests.
# #
# ArticleController.append_view_path("views/default") # ArticleController.append_view_path("views/default")
@ -457,7 +457,7 @@ module ActionController #:nodoc:
view_paths.push(*path) view_paths.push(*path)
ActionView::TemplateFinder.process_view_paths(path) ActionView::TemplateFinder.process_view_paths(path)
end end
# Replace sensitive parameter data from the request log. # Replace sensitive parameter data from the request log.
# Filters parameters that have any of the arguments as a substring. # Filters parameters that have any of the arguments as a substring.
# Looks in all subhashes of the param hash for keys to filter. # Looks in all subhashes of the param hash for keys to filter.
@ -504,6 +504,7 @@ module ActionController #:nodoc:
filtered_parameters filtered_parameters
end end
protected :filter_parameters
end end
# Don't render layouts for templates with the given extensions. # Don't render layouts for templates with the given extensions.
@ -643,12 +644,12 @@ module ActionController #:nodoc:
end end
self.view_paths = [] self.view_paths = []
# View load paths for controller. # View load paths for controller.
def view_paths def view_paths
@template.finder.view_paths @template.finder.view_paths
end end
def view_paths=(value) def view_paths=(value)
@template.finder.view_paths = value # Mutex needed @template.finder.view_paths = value # Mutex needed
end end
@ -662,7 +663,7 @@ module ActionController #:nodoc:
def prepend_view_path(path) def prepend_view_path(path)
@template.finder.prepend_view_path(path) # Mutex needed @template.finder.prepend_view_path(path) # Mutex needed
end end
# Adds a view_path to the end of the view_paths array. # Adds a view_path to the end of the view_paths array.
# This change affects the current request only. # This change affects the current request only.
# #
@ -874,10 +875,10 @@ module ActionController #:nodoc:
elsif action_name = options[:action] elsif action_name = options[:action]
template = default_template_name(action_name.to_s) template = default_template_name(action_name.to_s)
if options[:layout] && !template_exempt_from_layout?(template) if options[:layout] && !template_exempt_from_layout?(template)
render_with_a_layout(:file => template, :status => options[:status], :use_full_path => true, :layout => true) render_with_a_layout(:file => template, :status => options[:status], :use_full_path => true, :layout => true)
else else
render_with_no_layout(:file => template, :status => options[:status], :use_full_path => true) render_with_no_layout(:file => template, :status => options[:status], :use_full_path => true)
end end
elsif xml = options[:xml] elsif xml = options[:xml]
response.content_type ||= Mime::XML response.content_type ||= Mime::XML
@ -895,12 +896,12 @@ module ActionController #:nodoc:
if collection = options[:collection] if collection = options[:collection]
render_for_text( render_for_text(
@template.send!(:render_partial_collection, partial, collection, @template.send!(:render_partial_collection, partial, collection,
options[:spacer_template], options[:locals]), options[:status] options[:spacer_template], options[:locals]), options[:status]
) )
else else
render_for_text( render_for_text(
@template.send!(:render_partial, partial, @template.send!(:render_partial, partial,
ActionView::Base::ObjectWrapper.new(options[:object]), options[:locals]), options[:status] ActionView::Base::ObjectWrapper.new(options[:object]), options[:locals]), options[:status]
) )
end end
@ -1024,7 +1025,7 @@ module ActionController #:nodoc:
# redirect_to articles_url # redirect_to articles_url
# redirect_to :back # redirect_to :back
# #
# The redirection happens as a "302 Moved" header unless otherwise specified. # The redirection happens as a "302 Moved" header unless otherwise specified.
# #
# Examples: # Examples:
# redirect_to post_url(@post), :status=>:found # redirect_to post_url(@post), :status=>:found
@ -1035,17 +1036,17 @@ module ActionController #:nodoc:
# When using <tt>redirect_to :back</tt>, if there is no referrer, # When using <tt>redirect_to :back</tt>, if there is no referrer,
# RedirectBackError will be raised. You may specify some fallback # RedirectBackError will be raised. You may specify some fallback
# behavior for this case by rescuing RedirectBackError. # behavior for this case by rescuing RedirectBackError.
def redirect_to(options = {}, response_status = {}) #:doc: def redirect_to(options = {}, response_status = {}) #:doc:
raise ActionControllerError.new("Cannot redirect to nil!") if options.nil? raise ActionControllerError.new("Cannot redirect to nil!") if options.nil?
if options.is_a?(Hash) && options[:status] if options.is_a?(Hash) && options[:status]
status = options.delete(:status) status = options.delete(:status)
elsif response_status[:status] elsif response_status[:status]
status = response_status[:status] status = response_status[:status]
else else
status = 302 status = 302
end end
case options case options
when %r{^\w+://.*} when %r{^\w+://.*}
raise DoubleRenderError if performed? raise DoubleRenderError if performed?
@ -1119,7 +1120,7 @@ module ActionController #:nodoc:
response.body = text.is_a?(Proc) ? text : text.to_s response.body = text.is_a?(Proc) ? text : text.to_s
end end
end end
def initialize_template_class(response) def initialize_template_class(response)
response.template = ActionView::Base.new(self.class.view_paths, {}, self) response.template = ActionView::Base.new(self.class.view_paths, {}, self)
response.template.extend self.class.master_helper_module response.template.extend self.class.master_helper_module

View file

@ -9,7 +9,7 @@ module ActionController #:nodoc:
# class ListsController < ApplicationController # class ListsController < ApplicationController
# before_filter :authenticate, :except => :public # before_filter :authenticate, :except => :public
# caches_page :public # caches_page :public
# caches_action :show, :feed # caches_action :index, :show, :feed
# end # end
# #
# In this example, the public action doesn't require authentication, so it's possible to use the faster page caching method. But both the # In this example, the public action doesn't require authentication, so it's possible to use the faster page caching method. But both the
@ -27,15 +27,19 @@ module ActionController #:nodoc:
# You can set modify the default action cache path by passing a :cache_path option. This will be passed directly to ActionCachePath.path_for. This is handy # You can set modify the default action cache path by passing a :cache_path option. This will be passed directly to ActionCachePath.path_for. This is handy
# for actions with multiple possible routes that should be cached differently. If a block is given, it is called with the current controller instance. # for actions with multiple possible routes that should be cached differently. If a block is given, it is called with the current controller instance.
# #
# And you can also use :if to pass a Proc that specifies when the action should be cached.
#
# class ListsController < ApplicationController # class ListsController < ApplicationController
# before_filter :authenticate, :except => :public # before_filter :authenticate, :except => :public
# caches_page :public # caches_page :public
# caches_action :index, :if => Proc.new { |c| !c.request.format.json? } # cache if is not a JSON request
# caches_action :show, :cache_path => { :project => 1 } # caches_action :show, :cache_path => { :project => 1 }
# caches_action :show, :cache_path => Proc.new { |controller| # caches_action :feed, :cache_path => Proc.new { |controller|
# controller.params[:user_id] ? # controller.params[:user_id] ?
# controller.send(:user_list_url, c.params[:user_id], c.params[:id]) : # controller.send(:user_list_url, c.params[:user_id], c.params[:id]) :
# controller.send(:list_url, c.params[:id]) } # controller.send(:list_url, c.params[:id]) }
# end # end
#
module Actions module Actions
def self.included(base) #:nodoc: def self.included(base) #:nodoc:
base.extend(ClassMethods) base.extend(ClassMethods)
@ -49,7 +53,8 @@ module ActionController #:nodoc:
# See ActionController::Caching::Actions for details. # See ActionController::Caching::Actions for details.
def caches_action(*actions) def caches_action(*actions)
return unless cache_configured? return unless cache_configured?
around_filter(ActionCacheFilter.new(*actions)) options = actions.extract_options!
around_filter(ActionCacheFilter.new(:cache_path => options.delete(:cache_path)), {:only => actions}.merge(options))
end end
end end
@ -67,16 +72,12 @@ module ActionController #:nodoc:
end end
class ActionCacheFilter #:nodoc: class ActionCacheFilter #:nodoc:
def initialize(*actions, &block) def initialize(options, &block)
@options = actions.extract_options! @options = options
@actions = Set.new(actions)
end end
def before(controller) def before(controller)
return unless @actions.include?(controller.action_name.intern)
cache_path = ActionCachePath.new(controller, path_options_for(controller, @options)) cache_path = ActionCachePath.new(controller, path_options_for(controller, @options))
if cache = controller.read_fragment(cache_path.path) if cache = controller.read_fragment(cache_path.path)
controller.rendered_action_cache = true controller.rendered_action_cache = true
set_content_type!(controller, cache_path.extension) set_content_type!(controller, cache_path.extension)
@ -88,7 +89,7 @@ module ActionController #:nodoc:
end end
def after(controller) def after(controller)
return if !@actions.include?(controller.action_name.intern) || controller.rendered_action_cache || !caching_allowed(controller) return if controller.rendered_action_cache || !caching_allowed(controller)
controller.write_fragment(controller.action_cache_path.path, controller.response.body) controller.write_fragment(controller.action_cache_path.path, controller.response.body)
end end
@ -105,16 +106,16 @@ module ActionController #:nodoc:
controller.request.get? && controller.response.headers['Status'].to_i == 200 controller.request.get? && controller.response.headers['Status'].to_i == 200
end end
end end
class ActionCachePath class ActionCachePath
attr_reader :path, :extension attr_reader :path, :extension
class << self class << self
def path_for(controller, options) def path_for(controller, options)
new(controller, options).path new(controller, options).path
end end
end end
def initialize(controller, options = {}) def initialize(controller, options = {})
@extension = extract_extension(controller.request.path) @extension = extract_extension(controller.request.path)
path = controller.url_for(options).split('://').last path = controller.url_for(options).split('://').last
@ -122,16 +123,16 @@ module ActionController #:nodoc:
add_extension!(path, @extension) add_extension!(path, @extension)
@path = URI.unescape(path) @path = URI.unescape(path)
end end
private private
def normalize!(path) def normalize!(path)
path << 'index' if path[-1] == ?/ path << 'index' if path[-1] == ?/
end end
def add_extension!(path, extension) def add_extension!(path, extension)
path << ".#{extension}" if extension path << ".#{extension}" if extension
end end
def extract_extension(file_path) def extract_extension(file_path)
# Don't want just what comes after the last '.' to accommodate multi part extensions # Don't want just what comes after the last '.' to accommodate multi part extensions
# such as tar.gz. # such as tar.gz.
@ -140,4 +141,4 @@ module ActionController #:nodoc:
end end
end end
end end
end end

View file

@ -98,6 +98,17 @@ module ActionController #:nodoc:
end end
end end
# Check if a cached fragment from the location signified by <tt>key</tt> exists (see <tt>expire_fragment</tt> for acceptable formats)
def fragment_exist?(key, options = nil)
return unless cache_configured?
key = fragment_cache_key(key)
self.class.benchmark "Cached fragment exists?: #{key}" do
cache_store.exist?(key, options)
end
end
# Name can take one of three forms: # Name can take one of three forms:
# * String: This would normally take the form of a path like "pages/45/notes" # * String: This would normally take the form of a path like "pages/45/notes"
# * Hash: Is treated as an implicit call to url_for, like { :controller => "pages", :action => "notes", :id => 45 } # * Hash: Is treated as an implicit call to url_for, like { :controller => "pages", :action => "notes", :id => 45 }
@ -124,4 +135,4 @@ module ActionController #:nodoc:
end end
end end
end end
end end

View file

@ -28,7 +28,7 @@ module ActionController #:nodoc:
# class ListsController < ApplicationController # class ListsController < ApplicationController
# caches_action :index, :show, :public, :feed # caches_action :index, :show, :public, :feed
# cache_sweeper OpenBar::Sweeper, :only => [ :edit, :destroy, :share ] # cache_sweeper OpenBar::Sweeper, :only => [ :edit, :destroy, :share ]
# end # end
module Sweeping module Sweeping
def self.included(base) #:nodoc: def self.included(base) #:nodoc:
base.extend(ClassMethods) base.extend(ClassMethods)
@ -40,7 +40,7 @@ module ActionController #:nodoc:
sweepers.each do |sweeper| sweepers.each do |sweeper|
ActiveRecord::Base.observers << sweeper if defined?(ActiveRecord) and defined?(ActiveRecord::Base) ActiveRecord::Base.observers << sweeper if defined?(ActiveRecord) and defined?(ActiveRecord::Base)
sweeper_instance = (sweeper.is_a?(Symbol) ? Object.const_get(Inflector.classify(sweeper)) : sweeper).instance sweeper_instance = (sweeper.is_a?(Symbol) ? Object.const_get(sweeper.to_s.classify) : sweeper).instance
if sweeper_instance.is_a?(Sweeper) if sweeper_instance.is_a?(Sweeper)
around_filter(sweeper_instance, :only => configuration[:only]) around_filter(sweeper_instance, :only => configuration[:only])
@ -94,4 +94,4 @@ module ActionController #:nodoc:
end end
end end
end end
end end

View file

@ -6,25 +6,24 @@ class CGI #:nodoc:
attr_accessor :name, :value, :path, :domain, :expires attr_accessor :name, :value, :path, :domain, :expires
attr_reader :secure, :http_only attr_reader :secure, :http_only
# Create a new CGI::Cookie object. # Creates a new CGI::Cookie object.
# #
# The contents of the cookie can be specified as a +name+ and one # The contents of the cookie can be specified as a +name+ and one
# or more +value+ arguments. Alternatively, the contents can # or more +value+ arguments. Alternatively, the contents can
# be specified as a single hash argument. The possible keywords of # be specified as a single hash argument. The possible keywords of
# this hash are as follows: # this hash are as follows:
# #
# name:: the name of the cookie. Required. # * <tt>:name</tt> - The name of the cookie. Required.
# value:: the cookie's value or list of values. # * <tt>:value</tt> - The cookie's value or list of values.
# path:: the path for which this cookie applies. Defaults to the # * <tt>:path</tt> - The path for which this cookie applies. Defaults to the
# base directory of the CGI script. # base directory of the CGI script.
# domain:: the domain for which this cookie applies. # * <tt>:domain</tt> - The domain for which this cookie applies.
# expires:: the time at which this cookie expires, as a +Time+ object. # * <tt>:expires</tt> - The time at which this cookie expires, as a Time object.
# secure:: whether this cookie is a secure cookie or not (default to # * <tt>:secure</tt> - Whether this cookie is a secure cookie or not (defaults to
# false). Secure cookies are only transmitted to HTTPS # +false+). Secure cookies are only transmitted to HTTPS servers.
# servers. # * <tt>:http_only</tt> - Whether this cookie can be accessed by client side scripts (e.g. document.cookie) or only over HTTP.
# http_only:: whether this cookie can be accessed by client side scripts (e.g. document.cookie) or only over HTTP # More details in http://msdn2.microsoft.com/en-us/library/system.web.httpcookie.httponly.aspx. Defaults to +false+.
# More details: http://msdn2.microsoft.com/en-us/library/system.web.httpcookie.httponly.aspx #
# Defaults to false.
# These keywords correspond to attributes of the cookie object. # These keywords correspond to attributes of the cookie object.
def initialize(name = '', *value) def initialize(name = '', *value)
if name.kind_of?(String) if name.kind_of?(String)
@ -37,7 +36,7 @@ class CGI #:nodoc:
@path = nil @path = nil
else else
@name = name['name'] @name = name['name']
@value = Array(name['value']) @value = (name['value'].kind_of?(String) ? [name['value']] : Array(name['value'])).delete_if(&:blank?)
@domain = name['domain'] @domain = name['domain']
@expires = name['expires'] @expires = name['expires']
@secure = name['secure'] || false @secure = name['secure'] || false
@ -56,17 +55,17 @@ class CGI #:nodoc:
super(@value) super(@value)
end end
# Set whether the Cookie is a secure cookie or not. # Sets whether the Cookie is a secure cookie or not.
def secure=(val) def secure=(val)
@secure = val == true @secure = val == true
end end
# Set whether the Cookie is an HTTP only cookie or not. # Sets whether the Cookie is an HTTP only cookie or not.
def http_only=(val) def http_only=(val)
@http_only = val == true @http_only = val == true
end end
# Convert the Cookie to its string representation. # Converts the Cookie to its string representation.
def to_s def to_s
buf = '' buf = ''
buf << @name << '=' buf << @name << '='
@ -79,11 +78,17 @@ class CGI #:nodoc:
buf buf
end end
# Parse a raw cookie string into a hash of cookie-name=>Cookie # FIXME: work around broken 1.8.7 DelegateClass#respond_to?
def respond_to?(method, include_private = false)
return true if super(method)
return __getobj__.respond_to?(method, include_private)
end
# Parses a raw cookie string into a hash of <tt>cookie-name => cookie-object</tt>
# pairs. # pairs.
# #
# cookies = CGI::Cookie::parse("raw_cookie_string") # cookies = CGI::Cookie::parse("raw_cookie_string")
# # { "name1" => cookie1, "name2" => cookie2, ... } # # => { "name1" => cookie1, "name2" => cookie2, ... }
# #
def self.parse(raw_cookie) def self.parse(raw_cookie)
cookies = Hash.new([]) cookies = Hash.new([])

View file

@ -16,6 +16,7 @@ module ActionController
def initialize_with_stdinput(type = nil, stdinput = $stdin) def initialize_with_stdinput(type = nil, stdinput = $stdin)
@stdinput = stdinput @stdinput = stdinput
@stdinput.set_encoding(Encoding::BINARY) if @stdinput.respond_to?(:set_encoding)
initialize_without_stdinput(type || 'query') initialize_without_stdinput(type || 'query')
end end
end end

View file

@ -15,7 +15,7 @@ module ActionController #:nodoc:
# * <tt>:new_session</tt> - if true, force creation of a new session. If not set, a new session is only created if none currently # * <tt>:new_session</tt> - if true, force creation of a new session. If not set, a new session is only created if none currently
# exists. If false, a new session is never created, and if none currently exists and the +session_id+ option is not set, # exists. If false, a new session is never created, and if none currently exists and the +session_id+ option is not set,
# an ArgumentError is raised. # an ArgumentError is raised.
# * <tt>:session_expires</tt> - the time the current session expires, as a +Time+ object. If not set, the session will continue # * <tt>:session_expires</tt> - the time the current session expires, as a Time object. If not set, the session will continue
# indefinitely. # indefinitely.
# * <tt>:session_domain</tt> - the hostname domain for which this session is valid. If not set, defaults to the hostname of the # * <tt>:session_domain</tt> - the hostname domain for which this session is valid. If not set, defaults to the hostname of the
# server. # server.
@ -65,6 +65,7 @@ module ActionController #:nodoc:
# variable is already set, wrap it in a StringIO. # variable is already set, wrap it in a StringIO.
def body def body
if raw_post = env['RAW_POST_DATA'] if raw_post = env['RAW_POST_DATA']
raw_post.force_encoding(Encoding::BINARY) if raw_post.respond_to?(:force_encoding)
StringIO.new(raw_post) StringIO.new(raw_post)
else else
@cgi.stdinput @cgi.stdinput

View file

@ -100,10 +100,10 @@ module ActionController #:nodoc:
# #
# Around filters wrap an action, executing code both before and after. # Around filters wrap an action, executing code both before and after.
# They may be declared as method references, blocks, or objects responding # They may be declared as method references, blocks, or objects responding
# to #filter or to both #before and #after. # to +filter+ or to both +before+ and +after+.
# #
# To use a method as an around_filter, pass a symbol naming the Ruby method. # To use a method as an +around_filter+, pass a symbol naming the Ruby method.
# Yield (or block.call) within the method to run the action. # Yield (or <tt>block.call</tt>) within the method to run the action.
# #
# around_filter :catch_exceptions # around_filter :catch_exceptions
# #
@ -115,9 +115,9 @@ module ActionController #:nodoc:
# raise # raise
# end # end
# #
# To use a block as an around_filter, pass a block taking as args both # To use a block as an +around_filter+, pass a block taking as args both
# the controller and the action block. You can't call yield directly from # the controller and the action block. You can't call yield directly from
# an around_filter block; explicitly call the action block instead: # an +around_filter+ block; explicitly call the action block instead:
# #
# around_filter do |controller, action| # around_filter do |controller, action|
# logger.debug "before #{controller.action_name}" # logger.debug "before #{controller.action_name}"
@ -125,7 +125,7 @@ module ActionController #:nodoc:
# logger.debug "after #{controller.action_name}" # logger.debug "after #{controller.action_name}"
# end # end
# #
# To use a filter object with around_filter, pass an object responding # To use a filter object with +around_filter+, pass an object responding
# to <tt>:filter</tt> or both <tt>:before</tt> and <tt>:after</tt>. With a # to <tt>:filter</tt> or both <tt>:before</tt> and <tt>:after</tt>. With a
# filter method, yield to the block as above: # filter method, yield to the block as above:
# #
@ -137,7 +137,7 @@ module ActionController #:nodoc:
# end # end
# end # end
# #
# With before and after methods: # With +before+ and +after+ methods:
# #
# around_filter Authorizer.new # around_filter Authorizer.new
# #
@ -154,9 +154,9 @@ module ActionController #:nodoc:
# end # end
# end # end
# #
# If the filter has before and after methods, the before method will be # If the filter has +before+ and +after+ methods, the +before+ method will be
# called before the action. If before renders or redirects, the filter chain is # called before the action. If +before+ renders or redirects, the filter chain is
# halted and after will not be run. See Filter Chain Halting below for # halted and +after+ will not be run. See Filter Chain Halting below for
# an example. # an example.
# #
# == Filter chain skipping # == Filter chain skipping
@ -215,7 +215,7 @@ module ActionController #:nodoc:
# #
# <tt>before_filter</tt> and <tt>around_filter</tt> may halt the request # <tt>before_filter</tt> and <tt>around_filter</tt> may halt the request
# before a controller action is run. This is useful, for example, to deny # before a controller action is run. This is useful, for example, to deny
# access to unauthenticated users or to redirect from http to https. # access to unauthenticated users or to redirect from HTTP to HTTPS.
# Simply call render or redirect. After filters will not be executed if the filter # Simply call render or redirect. After filters will not be executed if the filter
# chain is halted. # chain is halted.
# #
@ -241,10 +241,10 @@ module ActionController #:nodoc:
# . / # . /
# #after (actual filter code is run, unless the around filter does not yield) # #after (actual filter code is run, unless the around filter does not yield)
# #
# If #around returns before yielding, #after will still not be run. The #before # If +around+ returns before yielding, +after+ will still not be run. The +before+
# filter and controller action will not be run. If #before renders or redirects, # filter and controller action will not be run. If +before+ renders or redirects,
# the second half of #around and will still run but #after and the # the second half of +around+ and will still run but +after+ and the
# action will not. If #around fails to yield, #after will not be run. # action will not. If +around+ fails to yield, +after+ will not be run.
class FilterChain < ActiveSupport::Callbacks::CallbackChain #:nodoc: class FilterChain < ActiveSupport::Callbacks::CallbackChain #:nodoc:
def append_filter_to_chain(filters, filter_type, &block) def append_filter_to_chain(filters, filter_type, &block)
@ -471,7 +471,7 @@ module ActionController #:nodoc:
# Shorthand for append_after_filter since it's the most common. # Shorthand for append_after_filter since it's the most common.
alias :after_filter :append_after_filter alias :after_filter :append_after_filter
# If you append_around_filter A.new, B.new, the filter chain looks like # If you <tt>append_around_filter A.new, B.new</tt>, the filter chain looks like
# #
# B#before # B#before
# A#before # A#before
@ -479,13 +479,13 @@ module ActionController #:nodoc:
# A#after # A#after
# B#after # B#after
# #
# With around filters which yield to the action block, #before and #after # With around filters which yield to the action block, +before+ and +after+
# are the code before and after the yield. # are the code before and after the yield.
def append_around_filter(*filters, &block) def append_around_filter(*filters, &block)
filter_chain.append_filter_to_chain(filters, :around, &block) filter_chain.append_filter_to_chain(filters, :around, &block)
end end
# If you prepend_around_filter A.new, B.new, the filter chain looks like: # If you <tt>prepend_around_filter A.new, B.new</tt>, the filter chain looks like:
# #
# A#before # A#before
# B#before # B#before
@ -493,13 +493,13 @@ module ActionController #:nodoc:
# B#after # B#after
# A#after # A#after
# #
# With around filters which yield to the action block, #before and #after # With around filters which yield to the action block, +before+ and +after+
# are the code before and after the yield. # are the code before and after the yield.
def prepend_around_filter(*filters, &block) def prepend_around_filter(*filters, &block)
filter_chain.prepend_filter_to_chain(filters, :around, &block) filter_chain.prepend_filter_to_chain(filters, :around, &block)
end end
# Shorthand for append_around_filter since it's the most common. # Shorthand for +append_around_filter+ since it's the most common.
alias :around_filter :append_around_filter alias :around_filter :append_around_filter
# Removes the specified filters from the +before+ filter chain. Note that this only works for skipping method-reference # Removes the specified filters from the +before+ filter chain. Note that this only works for skipping method-reference

View file

@ -20,7 +20,7 @@ module ActionController #:nodoc:
end end
# The Rails framework provides a large number of helpers for working with +assets+, +dates+, +forms+, # The Rails framework provides a large number of helpers for working with +assets+, +dates+, +forms+,
# +numbers+ and +ActiveRecord+ objects, to name a few. These helpers are available to all templates # +numbers+ and Active Record objects, to name a few. These helpers are available to all templates
# by default. # by default.
# #
# In addition to using the standard template helpers provided in the Rails framework, creating custom helpers to # In addition to using the standard template helpers provided in the Rails framework, creating custom helpers to
@ -32,7 +32,7 @@ module ActionController #:nodoc:
# controller which inherits from it. # controller which inherits from it.
# #
# ==== Examples # ==== Examples
# The +to_s+ method from the +Time+ class can be wrapped in a helper method to display a custom message if # The +to_s+ method from the Time class can be wrapped in a helper method to display a custom message if
# the Time object is blank: # the Time object is blank:
# #
# module FormattedTimeHelper # module FormattedTimeHelper
@ -41,7 +41,7 @@ module ActionController #:nodoc:
# end # end
# end # end
# #
# +FormattedTimeHelper+ can now be included in a controller, using the +helper+ class method: # FormattedTimeHelper can now be included in a controller, using the +helper+ class method:
# #
# class EventsController < ActionController::Base # class EventsController < ActionController::Base
# helper FormattedTimeHelper # helper FormattedTimeHelper
@ -74,22 +74,22 @@ module ActionController #:nodoc:
# The +helper+ class method can take a series of helper module names, a block, or both. # The +helper+ class method can take a series of helper module names, a block, or both.
# #
# * <tt>*args</tt>: One or more +Modules+, +Strings+ or +Symbols+, or the special symbol <tt>:all</tt>. # * <tt>*args</tt>: One or more modules, strings or symbols, or the special symbol <tt>:all</tt>.
# * <tt>&block</tt>: A block defining helper methods. # * <tt>&block</tt>: A block defining helper methods.
# #
# ==== Examples # ==== Examples
# When the argument is a +String+ or +Symbol+, the method will provide the "_helper" suffix, require the file # When the argument is a string or symbol, the method will provide the "_helper" suffix, require the file
# and include the module in the template class. The second form illustrates how to include custom helpers # and include the module in the template class. The second form illustrates how to include custom helpers
# when working with namespaced controllers, or other cases where the file containing the helper definition is not # when working with namespaced controllers, or other cases where the file containing the helper definition is not
# in one of Rails' standard load paths: # in one of Rails' standard load paths:
# helper :foo # => requires 'foo_helper' and includes FooHelper # helper :foo # => requires 'foo_helper' and includes FooHelper
# helper 'resources/foo' # => requires 'resources/foo_helper' and includes Resources::FooHelper # helper 'resources/foo' # => requires 'resources/foo_helper' and includes Resources::FooHelper
# #
# When the argument is a +Module+, it will be included directly in the template class. # When the argument is a module it will be included directly in the template class.
# helper FooHelper # => includes FooHelper # helper FooHelper # => includes FooHelper
# #
# When the argument is the symbol <tt>:all</tt>, the controller will include all helpers from # When the argument is the symbol <tt>:all</tt>, the controller will include all helpers from
# <tt>app/helpers/**/*.rb</tt> under +RAILS_ROOT+. # <tt>app/helpers/**/*.rb</tt> under RAILS_ROOT.
# helper :all # helper :all
# #
# Additionally, the +helper+ class method can receive and evaluate a block, making the methods defined available # Additionally, the +helper+ class method can receive and evaluate a block, making the methods defined available

View file

@ -58,7 +58,7 @@ module ActionController
class MultiPartNeededException < Exception class MultiPartNeededException < Exception
end end
# Create and initialize a new +Session+ instance. # Create and initialize a new Session instance.
def initialize def initialize
reset! reset!
end end
@ -136,25 +136,25 @@ module ActionController
end end
# Performs a GET request, following any subsequent redirect. # Performs a GET request, following any subsequent redirect.
# See #request_via_redirect() for more information. # See +request_via_redirect+ for more information.
def get_via_redirect(path, parameters = nil, headers = nil) def get_via_redirect(path, parameters = nil, headers = nil)
request_via_redirect(:get, path, parameters, headers) request_via_redirect(:get, path, parameters, headers)
end end
# Performs a POST request, following any subsequent redirect. # Performs a POST request, following any subsequent redirect.
# See #request_via_redirect() for more information. # See +request_via_redirect+ for more information.
def post_via_redirect(path, parameters = nil, headers = nil) def post_via_redirect(path, parameters = nil, headers = nil)
request_via_redirect(:post, path, parameters, headers) request_via_redirect(:post, path, parameters, headers)
end end
# Performs a PUT request, following any subsequent redirect. # Performs a PUT request, following any subsequent redirect.
# See #request_via_redirect() for more information. # See +request_via_redirect+ for more information.
def put_via_redirect(path, parameters = nil, headers = nil) def put_via_redirect(path, parameters = nil, headers = nil)
request_via_redirect(:put, path, parameters, headers) request_via_redirect(:put, path, parameters, headers)
end end
# Performs a DELETE request, following any subsequent redirect. # Performs a DELETE request, following any subsequent redirect.
# See #request_via_redirect() for more information. # See +request_via_redirect+ for more information.
def delete_via_redirect(path, parameters = nil, headers = nil) def delete_via_redirect(path, parameters = nil, headers = nil)
request_via_redirect(:delete, path, parameters, headers) request_via_redirect(:delete, path, parameters, headers)
end end
@ -166,12 +166,12 @@ module ActionController
# Performs a GET request with the given parameters. The parameters may # Performs a GET request with the given parameters. The parameters may
# be +nil+, a Hash, or a string that is appropriately encoded # be +nil+, a Hash, or a string that is appropriately encoded
# (application/x-www-form-urlencoded or multipart/form-data). The headers # (<tt>application/x-www-form-urlencoded</tt> or <tt>multipart/form-data</tt>).
# should be a hash. The keys will automatically be upcased, with the # The headers should be a hash. The keys will automatically be upcased, with the
# prefix 'HTTP_' added if needed. # prefix 'HTTP_' added if needed.
# #
# You can also perform POST, PUT, DELETE, and HEAD requests with #post, # You can also perform POST, PUT, DELETE, and HEAD requests with +post+,
# #put, #delete, and #head. # +put+, +delete+, and +head+.
def get(path, parameters = nil, headers = nil) def get(path, parameters = nil, headers = nil)
process :get, path, parameters, headers process :get, path, parameters, headers
end end
@ -228,6 +228,8 @@ module ActionController
super super
stdinput.set_encoding(Encoding::BINARY) if stdinput.respond_to?(:set_encoding)
stdinput.force_encoding(Encoding::BINARY) if stdinput.respond_to?(:force_encoding)
@stdinput = stdinput.is_a?(IO) ? stdinput : StringIO.new(stdinput || '') @stdinput = stdinput.is_a?(IO) ? stdinput : StringIO.new(stdinput || '')
end end
end end
@ -382,6 +384,8 @@ module ActionController
multipart_requestify(params).map do |key, value| multipart_requestify(params).map do |key, value|
if value.respond_to?(:original_filename) if value.respond_to?(:original_filename)
File.open(value.path) do |f| File.open(value.path) do |f|
f.set_encoding(Encoding::BINARY) if f.respond_to?(:set_encoding)
<<-EOF <<-EOF
--#{boundary}\r --#{boundary}\r
Content-Disposition: form-data; name="#{key}"; filename="#{CGI.escape(value.original_filename)}"\r Content-Disposition: form-data; name="#{key}"; filename="#{CGI.escape(value.original_filename)}"\r

View file

@ -92,7 +92,7 @@ module ActionController #:nodoc:
# with the remaining data. # with the remaining data.
# #
# Note that you can define your own XML parameter parser which would allow you to describe multiple entities # Note that you can define your own XML parameter parser which would allow you to describe multiple entities
# in a single request (i.e., by wrapping them all in a single root note), but if you just go with the flow # in a single request (i.e., by wrapping them all in a single root node), but if you just go with the flow
# and accept Rails' defaults, life will be much easier. # and accept Rails' defaults, life will be much easier.
# #
# If you need to use a MIME type which isn't supported by default, you can register your own handlers in # If you need to use a MIME type which isn't supported by default, you can register your own handlers in

View file

@ -17,6 +17,10 @@ module Mime
# end # end
# end # end
class Type class Type
@@html_types = Set.new [:html, :all]
@@unverifiable_types = Set.new [:text, :json, :csv, :xml, :rss, :atom, :yaml]
cattr_reader :html_types, :unverifiable_types
# A simple helper class used in parsing the accept header # A simple helper class used in parsing the accept header
class AcceptItem #:nodoc: class AcceptItem #:nodoc:
attr_accessor :order, :name, :q attr_accessor :order, :name, :q
@ -100,7 +104,7 @@ module Mime
list[text_xml].name = Mime::XML.to_s list[text_xml].name = Mime::XML.to_s
end end
# Look for more specific xml-based types and sort them ahead of app/xml # Look for more specific XML-based types and sort them ahead of app/xml
if app_xml if app_xml
idx = app_xml idx = app_xml
@ -153,12 +157,21 @@ module Mime
synonym.to_s == mime_type.to_s || synonym.to_sym == mime_type.to_sym synonym.to_s == mime_type.to_s || synonym.to_sym == mime_type.to_sym
end end
end end
# Returns true if Action Pack should check requests using this Mime Type for possible request forgery. See
# ActionController::RequestForgerProtection.
def verify_request?
!@@unverifiable_types.include?(to_sym)
end
def html?
@@html_types.include?(to_sym) || @string =~ /html/
end
private private
def method_missing(method, *args) def method_missing(method, *args)
if method.to_s =~ /(\w+)\?$/ if method.to_s =~ /(\w+)\?$/
mime_type = $1.downcase.to_sym $1.downcase.to_sym == to_sym
mime_type == @symbol || (mime_type == :html && @symbol == :all)
else else
super super
end end

View file

@ -17,4 +17,4 @@ Mime::Type.register "multipart/form-data", :multipart_form
Mime::Type.register "application/x-www-form-urlencoded", :url_encoded_form Mime::Type.register "application/x-www-form-urlencoded", :url_encoded_form
# http://www.ietf.org/rfc/rfc4627.txt # http://www.ietf.org/rfc/rfc4627.txt
Mime::Type.register "application/json", :json, %w( text/x-json ) Mime::Type.register "application/json", :json, %w( text/x-json )

View file

@ -1,6 +1,6 @@
module ActionController module ActionController
# Polymorphic URL helpers are methods for smart resolution to a named route call when # Polymorphic URL helpers are methods for smart resolution to a named route call when
# given an ActiveRecord model instance. They are to be used in combination with # given an Active Record model instance. They are to be used in combination with
# ActionController::Resources. # ActionController::Resources.
# #
# These methods are useful when you want to generate correct URL or path to a RESTful # These methods are useful when you want to generate correct URL or path to a RESTful
@ -9,7 +9,9 @@ module ActionController
# Nested resources and/or namespaces are also supported, as illustrated in the example: # Nested resources and/or namespaces are also supported, as illustrated in the example:
# #
# polymorphic_url([:admin, @article, @comment]) # polymorphic_url([:admin, @article, @comment])
# #-> results in: #
# results in:
#
# admin_article_comment_url(@article, @comment) # admin_article_comment_url(@article, @comment)
# #
# == Usage within the framework # == Usage within the framework
@ -38,11 +40,8 @@ module ActionController
# #
# Example usage: # Example usage:
# #
# edit_polymorphic_path(@post) # edit_polymorphic_path(@post) # => "/posts/1/edit"
# #=> /posts/1/edit # formatted_polymorphic_path([@post, :pdf]) # => "/posts/1.pdf"
#
# formatted_polymorphic_path([@post, :pdf])
# #=> /posts/1.pdf
module PolymorphicRoutes module PolymorphicRoutes
# Constructs a call to a named RESTful route for the given record and returns the # Constructs a call to a named RESTful route for the given record and returns the
# resulting URL string. For example: # resulting URL string. For example:

View file

@ -466,8 +466,8 @@ EOM
parser.result parser.result
end end
def parse_multipart_form_parameters(body, boundary, content_length, env) def parse_multipart_form_parameters(body, boundary, body_size, env)
parse_request_parameters(read_multipart(body, boundary, content_length, env)) parse_request_parameters(read_multipart(body, boundary, body_size, env))
end end
def extract_multipart_boundary(content_type_with_parameters) def extract_multipart_boundary(content_type_with_parameters)
@ -519,7 +519,7 @@ EOM
EOL = "\015\012" EOL = "\015\012"
def read_multipart(body, boundary, content_length, env) def read_multipart(body, boundary, body_size, env)
params = Hash.new([]) params = Hash.new([])
boundary = "--" + boundary boundary = "--" + boundary
quoted_boundary = Regexp.quote(boundary) quoted_boundary = Regexp.quote(boundary)
@ -529,8 +529,14 @@ EOM
# start multipart/form-data # start multipart/form-data
body.binmode if defined? body.binmode body.binmode if defined? body.binmode
case body
when File
body.set_encoding(Encoding::BINARY) if body.respond_to?(:set_encoding)
when StringIO
body.string.force_encoding(Encoding::BINARY) if body.string.respond_to?(:force_encoding)
end
boundary_size = boundary.size + EOL.size boundary_size = boundary.size + EOL.size
content_length -= boundary_size body_size -= boundary_size
status = body.read(boundary_size) status = body.read(boundary_size)
if nil == status if nil == status
raise EOFError, "no content body" raise EOFError, "no content body"
@ -541,7 +547,7 @@ EOM
loop do loop do
head = nil head = nil
content = content =
if 10240 < content_length if 10240 < body_size
UploadedTempfile.new("CGI") UploadedTempfile.new("CGI")
else else
UploadedStringIO.new UploadedStringIO.new
@ -563,24 +569,24 @@ EOM
buf[0 ... (buf.size - (EOL + boundary + EOL).size)] = "" buf[0 ... (buf.size - (EOL + boundary + EOL).size)] = ""
end end
c = if bufsize < content_length c = if bufsize < body_size
body.read(bufsize) body.read(bufsize)
else else
body.read(content_length) body.read(body_size)
end end
if c.nil? || c.empty? if c.nil? || c.empty?
raise EOFError, "bad content body" raise EOFError, "bad content body"
end end
buf.concat(c) buf.concat(c)
content_length -= c.size body_size -= c.size
end end
buf = buf.sub(/\A((?:.|\n)*?)(?:[\r\n]{1,2})?#{quoted_boundary}([\r\n]{1,2}|--)/n) do buf = buf.sub(/\A((?:.|\n)*?)(?:[\r\n]{1,2})?#{quoted_boundary}([\r\n]{1,2}|--)/n) do
content.print $1 content.print $1
if "--" == $2 if "--" == $2
content_length = -1 body_size = -1
end end
boundary_end = $2.dup boundary_end = $2.dup
"" ""
end end
@ -607,7 +613,7 @@ EOM
else else
params[name] = [content] params[name] = [content]
end end
break if content_length == -1 break if body_size == -1
end end
raise EOFError, "bad boundary end of body part" unless boundary_end=~/--/ raise EOFError, "bad boundary end of body part" unless boundary_end=~/--/

View file

@ -99,7 +99,7 @@ module ActionController #:nodoc:
end end
def verifiable_request_format? def verifiable_request_format?
request.format.html? || request.format.js? request.content_type.nil? || request.content_type.verify_request?
end end
# Sets the token value for the current session. Pass a <tt>:secret</tt> option # Sets the token value for the current session. Pass a <tt>:secret</tt> option

View file

@ -17,13 +17,13 @@ module ActionController
reset! reset!
end end
def benchmark(n) def benchmark(n, profiling = false)
@quiet = true @quiet = true
print ' ' print ' '
result = Benchmark.realtime do result = Benchmark.realtime do
n.times do |i| n.times do |i|
run run(profiling)
print_progress(i) print_progress(i)
end end
end end
@ -43,8 +43,15 @@ module ActionController
script = File.read(script_path) script = File.read(script_path)
source = <<-end_source source = <<-end_source
def run def run(profiling = false)
#{script} if profiling
RubyProf.resume do
#{script}
end
else
#{script}
end
old_request_count = request_count old_request_count = request_count
reset! reset!
self.request_count = old_request_count self.request_count = old_request_count
@ -91,21 +98,22 @@ module ActionController
def profile(sandbox) def profile(sandbox)
load_ruby_prof load_ruby_prof
results = RubyProf.profile { benchmark(sandbox) } benchmark(sandbox, true)
results = RubyProf.stop
show_profile_results results show_profile_results results
results results
end end
def benchmark(sandbox) def benchmark(sandbox, profiling = false)
sandbox.request_count = 0 sandbox.request_count = 0
elapsed = sandbox.benchmark(options[:n]).to_f elapsed = sandbox.benchmark(options[:n], profiling).to_f
count = sandbox.request_count.to_i count = sandbox.request_count.to_i
puts '%.2f sec, %d requests, %d req/sec' % [elapsed, count, count / elapsed] puts '%.2f sec, %d requests, %d req/sec' % [elapsed, count, count / elapsed]
end end
def warmup(sandbox) def warmup(sandbox)
Benchmark.realtime { sandbox.run } Benchmark.realtime { sandbox.run(false) }
end end
def default_options def default_options
@ -136,6 +144,7 @@ module ActionController
protected protected
def load_ruby_prof def load_ruby_prof
begin begin
gem 'ruby-prof', '>= 0.6.1'
require 'ruby-prof' require 'ruby-prof'
if mode = options[:measure] if mode = options[:measure]
RubyProf.measure_mode = RubyProf.const_get(mode.upcase) RubyProf.measure_mode = RubyProf.const_get(mode.upcase)

View file

@ -199,10 +199,8 @@ module ActionController #:nodoc:
private private
def perform_action_with_rescue #:nodoc: def perform_action_with_rescue #:nodoc:
perform_action_without_rescue perform_action_without_rescue
rescue Exception => exception # errors from action performed rescue Exception => exception
return if rescue_action_with_handler(exception) rescue_action_with_handler(exception) || rescue_action(exception)
rescue_action(exception)
end end
def rescues_path(template_name) def rescues_path(template_name)

View file

@ -191,7 +191,7 @@ module ActionController
# end # end
# end # end
# #
# Along with the routes themselves, #resources generates named routes for use in # Along with the routes themselves, +resources+ generates named routes for use in
# controllers and views. <tt>map.resources :messages</tt> produces the following named routes and helpers: # controllers and views. <tt>map.resources :messages</tt> produces the following named routes and helpers:
# #
# Named Route Helpers # Named Route Helpers
@ -208,7 +208,7 @@ module ActionController
# edit_message edit_message_url(id), hash_for_edit_message_url(id), # edit_message edit_message_url(id), hash_for_edit_message_url(id),
# edit_message_path(id), hash_for_edit_message_path(id) # edit_message_path(id), hash_for_edit_message_path(id)
# #
# You can use these helpers instead of #url_for or methods that take #url_for parameters. For example: # You can use these helpers instead of +url_for+ or methods that take +url_for+ parameters. For example:
# #
# redirect_to :controller => 'messages', :action => 'index' # redirect_to :controller => 'messages', :action => 'index'
# # and # # and
@ -406,7 +406,7 @@ module ActionController
# end # end
# end # end
# #
# Along with the routes themselves, #resource generates named routes for # Along with the routes themselves, +resource+ generates named routes for
# use in controllers and views. <tt>map.resource :account</tt> produces # use in controllers and views. <tt>map.resource :account</tt> produces
# these named routes and helpers: # these named routes and helpers:
# #

View file

@ -23,9 +23,9 @@ module ActionController
# Accepts a "route path" (a string defining a route), and returns the array # Accepts a "route path" (a string defining a route), and returns the array
# of segments that corresponds to it. Note that the segment array is only # of segments that corresponds to it. Note that the segment array is only
# partially initialized--the defaults and requirements, for instance, need # partially initialized--the defaults and requirements, for instance, need
# to be set separately, via the #assign_route_options method, and the # to be set separately, via the +assign_route_options+ method, and the
# #optional? method for each segment will not be reliable until after # <tt>optional?</tt> method for each segment will not be reliable until after
# #assign_route_options is called, as well. # +assign_route_options+ is called, as well.
def segments_for_route_path(path) def segments_for_route_path(path)
rest, segments = path, [] rest, segments = path, []

View file

@ -248,7 +248,7 @@ module ActionController
end end
def extract_value def extract_value
"#{local_name} = hash[:#{key}] && hash[:#{key}].collect { |path_component| CGI.escape(path_component) }.to_param #{"|| #{default.inspect}" if default}" "#{local_name} = hash[:#{key}] && hash[:#{key}].collect { |path_component| CGI.escape(path_component.to_param, ActionController::Routing::Segment::UNSAFE_PCHAR) }.to_param #{"|| #{default.inspect}" if default}"
end end
def default def default

View file

@ -34,7 +34,7 @@ require 'openssl' # to generate the HMAC message digest
# such as 'MD5', 'RIPEMD160', 'SHA256', etc. # such as 'MD5', 'RIPEMD160', 'SHA256', etc.
# #
# To generate a secret key for an existing application, run # To generate a secret key for an existing application, run
# `rake secret` and set the key in config/environment.rb. # "rake secret" and set the key in config/environment.rb.
# #
# Note that changing digest or secret invalidates all existing sessions! # Note that changing digest or secret invalidates all existing sessions!
class CGI::Session::CookieStore class CGI::Session::CookieStore
@ -130,17 +130,20 @@ class CGI::Session::CookieStore
# Marshal a session hash into safe cookie data. Include an integrity hash. # Marshal a session hash into safe cookie data. Include an integrity hash.
def marshal(session) def marshal(session)
data = ActiveSupport::Base64.encode64(Marshal.dump(session)).chop data = ActiveSupport::Base64.encode64(Marshal.dump(session)).chop
CGI.escape "#{data}--#{generate_digest(data)}" "#{data}--#{generate_digest(data)}"
end end
# Unmarshal cookie data to a hash and verify its integrity. # Unmarshal cookie data to a hash and verify its integrity.
def unmarshal(cookie) def unmarshal(cookie)
if cookie if cookie
data, digest = CGI.unescape(cookie).split('--') data, digest = cookie.split('--')
unless digest == generate_digest(data)
# Do two checks to transparently support old double-escaped data.
unless digest == generate_digest(data) || digest == generate_digest(data = CGI.unescape(data))
delete delete
raise TamperedWithCookie raise TamperedWithCookie
end end
Marshal.load(ActiveSupport::Base64.decode64(data)) Marshal.load(ActiveSupport::Base64.decode64(data))
end end
end end

View file

@ -80,4 +80,4 @@ module ActionController
@request.remote_addr = '208.77.188.166' # example.com @request.remote_addr = '208.77.188.166' # example.com
end end
end end
end end

View file

@ -3,7 +3,7 @@ require 'action_controller/test_case'
module ActionController #:nodoc: module ActionController #:nodoc:
class Base class Base
# Process a test request called with a +TestRequest+ object. # Process a test request called with a TestRequest object.
def self.process_test(request) def self.process_test(request)
new.process_test(request) new.process_test(request)
end end
@ -49,7 +49,7 @@ module ActionController #:nodoc:
# Either the RAW_POST_DATA environment variable or the URL-encoded request # Either the RAW_POST_DATA environment variable or the URL-encoded request
# parameters. # parameters.
def raw_post def raw_post
env['RAW_POST_DATA'] ||= url_encoded_request_parameters env['RAW_POST_DATA'] ||= returning(url_encoded_request_parameters) { |b| b.force_encoding(Encoding::BINARY) if b.respond_to?(:force_encoding) }
end end
def port=(number) def port=(number)
@ -340,6 +340,7 @@ module ActionController #:nodoc:
@content_type = content_type @content_type = content_type
@original_filename = path.sub(/^.*#{File::SEPARATOR}([^#{File::SEPARATOR}]+)$/) { $1 } @original_filename = path.sub(/^.*#{File::SEPARATOR}([^#{File::SEPARATOR}]+)$/) { $1 }
@tempfile = Tempfile.new(@original_filename) @tempfile = Tempfile.new(@original_filename)
@tempfile.set_encoding(Encoding::BINARY) if @tempfile.respond_to?(:set_encoding)
@tempfile.binmode if binary @tempfile.binmode if binary
FileUtils.copy_file(path, @tempfile.path) FileUtils.copy_file(path, @tempfile.path)
end end
@ -357,7 +358,7 @@ module ActionController #:nodoc:
module TestProcess module TestProcess
def self.included(base) def self.included(base)
# execute the request simulating a specific http method and set/volley the response # execute the request simulating a specific HTTP method and set/volley the response
%w( get post put delete head ).each do |method| %w( get post put delete head ).each do |method|
base.class_eval <<-EOV, __FILE__, __LINE__ base.class_eval <<-EOV, __FILE__, __LINE__
def #{method}(action, parameters = nil, session = nil, flash = nil) def #{method}(action, parameters = nil, session = nil, flash = nil)

View file

@ -1,8 +1,8 @@
module ActionPack #:nodoc: module ActionPack #:nodoc:
module VERSION #:nodoc: module VERSION #:nodoc:
MAJOR = 2 MAJOR = 2
MINOR = 0 MINOR = 1
TINY = 991 TINY = 0
STRING = [MAJOR, MINOR, TINY].join('.') STRING = [MAJOR, MINOR, TINY].join('.')
end end

View file

@ -168,12 +168,12 @@ module ActionView #:nodoc:
# Specify whether file modification times should be checked to see if a template needs recompilation # Specify whether file modification times should be checked to see if a template needs recompilation
@@cache_template_loading = false @@cache_template_loading = false
cattr_accessor :cache_template_loading cattr_accessor :cache_template_loading
# Specify whether file extension lookup should be cached, and whether template base path lookup should be cached.
# Should be +false+ for development environments. Defaults to +true+.
@@cache_template_extensions = true
cattr_accessor :cache_template_extensions
def self.cache_template_extensions=(*args)
ActiveSupport::Deprecation.warn("config.action_view.cache_template_extensions option has been deprecated and has no affect. " <<
"Please remove it from your config files.", caller)
end
# Specify whether RJS responses should be wrapped in a try/catch block # Specify whether RJS responses should be wrapped in a try/catch block
# that alert()s the caught exception (and then re-raises it). # that alert()s the caught exception (and then re-raises it).
@@debug_rjs = false @@debug_rjs = false

View file

@ -1,69 +0,0 @@
module ActionView
# CompiledTemplates modules hold methods that have been compiled.
# Templates are compiled into these methods so that they do not need to be
# read and parsed for each request.
#
# Each template may be compiled into one or more methods. Each method accepts a given
# set of parameters which is used to implement local assigns passing.
#
# To use a compiled template module, create a new instance and include it into the class
# in which you want the template to be rendered.
class CompiledTemplates < Module
attr_reader :method_names
def initialize
@method_names = Hash.new do |hash, key|
hash[key] = "__compiled_method_#{(hash.length + 1)}"
end
@mtimes = {}
end
# Return the full key for the given identifier and argument names
def full_key(identifier, arg_names)
[identifier, arg_names]
end
# Return the selector for this method or nil if it has not been compiled
def selector(identifier, arg_names)
key = full_key(identifier, arg_names)
method_names.key?(key) ? method_names[key] : nil
end
alias :compiled? :selector
# Return the time at which the method for the given identifier and argument names was compiled.
def mtime(identifier, arg_names)
@mtimes[full_key(identifier, arg_names)]
end
# Compile the provided source code for the given argument names and with the given initial line number.
# The identifier should be unique to this source.
#
# The file_name, if provided will appear in backtraces. If not provided, the file_name defaults
# to the identifier.
#
# This method will return the selector for the compiled version of this method.
def compile_source(identifier, arg_names, source, initial_line_number = 0, file_name = nil)
file_name ||= identifier
name = method_names[full_key(identifier, arg_names)]
arg_desc = arg_names.empty? ? '' : "(#{arg_names * ', '})"
fake_file_name = "#{file_name}#{arg_desc}" # Include the arguments for this version (for now)
method_def = wrap_source(name, arg_names, source)
begin
module_eval(method_def, fake_file_name, initial_line_number)
@mtimes[full_key(identifier, arg_names)] = Time.now
rescue Exception => e # errors from compiled source
e.blame_file! identifier
raise
end
name
end
# Wrap the provided source in a def ... end block.
def wrap_source(name, arg_names, source)
"def #{name}(#{arg_names * ', '})\n#{source}\nend"
end
end
end

View file

@ -11,8 +11,8 @@ module ActionView
# === Using asset hosts # === Using asset hosts
# By default, Rails links to these assets on the current host in the public # By default, Rails links to these assets on the current host in the public
# folder, but you can direct Rails to link to assets from a dedicated assets server by # folder, but you can direct Rails to link to assets from a dedicated assets server by
# setting ActionController::Base.asset_host in your environment.rb. For example, # setting ActionController::Base.asset_host in your <tt>config/environment.rb</tt>. For example,
# let's say your asset host is assets.example.com. # let's say your asset host is <tt>assets.example.com</tt>.
# #
# ActionController::Base.asset_host = "assets.example.com" # ActionController::Base.asset_host = "assets.example.com"
# image_tag("rails.png") # image_tag("rails.png")
@ -22,8 +22,8 @@ module ActionView
# #
# This is useful since browsers typically open at most two connections to a single host, # This is useful since browsers typically open at most two connections to a single host,
# which means your assets often wait in single file for their turn to load. You can # which means your assets often wait in single file for their turn to load. You can
# alleviate this by using a %d wildcard in <tt>asset_host</tt> (for example, "assets%d.example.com") # alleviate this by using a <tt>%d</tt> wildcard in <tt>asset_host</tt> (for example, "assets%d.example.com")
# to automatically distribute asset requests among four hosts (e.g., assets0.example.com through assets3.example.com) # to automatically distribute asset requests among four hosts (e.g., "assets0.example.com" through "assets3.example.com")
# so browsers will open eight connections rather than two. # so browsers will open eight connections rather than two.
# #
# image_tag("rails.png") # image_tag("rails.png")
@ -293,9 +293,9 @@ module ActionView
end end
# Computes the path to a stylesheet asset in the public stylesheets directory. # Computes the path to a stylesheet asset in the public stylesheets directory.
# If the +source+ filename has no extension, .css will be appended. # If the +source+ filename has no extension, <tt>.css</tt> will be appended.
# Full paths from the document root will be passed through. # Full paths from the document root will be passed through.
# Used internally by stylesheet_link_tag to build the stylesheet path. # Used internally by +stylesheet_link_tag+ to build the stylesheet path.
# #
# ==== Examples # ==== Examples
# stylesheet_path "style" # => /stylesheets/style.css # stylesheet_path "style" # => /stylesheets/style.css
@ -309,7 +309,7 @@ module ActionView
alias_method :path_to_stylesheet, :stylesheet_path # aliased to avoid conflicts with a stylesheet_path named route alias_method :path_to_stylesheet, :stylesheet_path # aliased to avoid conflicts with a stylesheet_path named route
# Returns a stylesheet link tag for the sources specified as arguments. If # Returns a stylesheet link tag for the sources specified as arguments. If
# you don't specify an extension, .css will be appended automatically. # you don't specify an extension, <tt>.css</tt> will be appended automatically.
# You can modify the link attributes by passing a hash as the last argument. # You can modify the link attributes by passing a hash as the last argument.
# #
# ==== Examples # ==== Examples
@ -379,7 +379,7 @@ module ActionView
# Computes the path to an image asset in the public images directory. # Computes the path to an image asset in the public images directory.
# Full paths from the document root will be passed through. # Full paths from the document root will be passed through.
# Used internally by image_tag to build the image path. # Used internally by +image_tag+ to build the image path.
# #
# ==== Examples # ==== Examples
# image_path("edit") # => /images/edit # image_path("edit") # => /images/edit
@ -454,8 +454,8 @@ module ActionView
end end
end end
# Add the .ext if not present. Return full URLs otherwise untouched. # Add the the extension +ext+ if not present. Return full URLs otherwise untouched.
# Prefix with /dir/ if lacking a leading /. Account for relative URL # Prefix with <tt>/dir/</tt> if lacking a leading +/+. Account for relative URL
# roots. Rewrite the asset path for cache-busting asset ids. Include # roots. Rewrite the asset path for cache-busting asset ids. Include
# asset host, if configured, with the correct request protocol. # asset host, if configured, with the correct request protocol.
def compute_public_path(source, dir, ext = nil, include_host = true) def compute_public_path(source, dir, ext = nil, include_host = true)
@ -502,9 +502,9 @@ module ActionView
end end
end end
# Pick an asset host for this source. Returns nil if no host is set, # Pick an asset host for this source. Returns +nil+ if no host is set,
# the host if no wildcard is set, the host interpolated with the # the host if no wildcard is set, the host interpolated with the
# numbers 0-3 if it contains %d (the number is the source hash mod 4), # numbers 0-3 if it contains <tt>%d</tt> (the number is the source hash mod 4),
# or the value returned from invoking the proc if it's a proc. # or the value returned from invoking the proc if it's a proc.
def compute_asset_host(source) def compute_asset_host(source)
if host = ActionController::Base.asset_host if host = ActionController::Base.asset_host

View file

@ -689,7 +689,7 @@ module ActionView
default[key] ||= time.send(key) default[key] ||= time.send(key)
end end
Time.utc(default[:year], default[:month], default[:day], default[:hour], default[:min], default[:sec]) Time.utc_time(default[:year], default[:month], default[:day], default[:hour], default[:min], default[:sec])
end end
end end
end end

View file

@ -73,30 +73,81 @@ module ActionView
# There are also methods for helping to build form tags in link:classes/ActionView/Helpers/FormOptionsHelper.html, # There are also methods for helping to build form tags in link:classes/ActionView/Helpers/FormOptionsHelper.html,
# link:classes/ActionView/Helpers/DateHelper.html, and link:classes/ActionView/Helpers/ActiveRecordHelper.html # link:classes/ActionView/Helpers/DateHelper.html, and link:classes/ActionView/Helpers/ActiveRecordHelper.html
module FormHelper module FormHelper
# Creates a form and a scope around a specific model object that is used as a base for questioning about # Creates a form and a scope around a specific model object that is used as
# values for the fields. # a base for questioning about values for the fields.
# #
# <% form_for :person, @person, :url => { :action => "update" } do |f| %> # Rails provides succint resource-oriented form generation with +form_for+
# <%= f.error_messages %> # like this:
# First name: <%= f.text_field :first_name %> #
# Last name : <%= f.text_field :last_name %> # <% form_for @offer do |f| %>
# Biography : <%= f.text_area :biography %> # <%= f.label :version, 'Version' %>:
# Admin? : <%= f.check_box :admin %> # <%= f.text_field :version %><br />
# <%= f.label :author, 'Author' %>:
# <%= f.text_field :author %><br />
# <% end %> # <% end %>
# #
# Worth noting is that the form_for tag is called in a ERb evaluation block, not an ERb output block. So that's <tt><% %></tt>, # There, +form_for+ is able to generate the rest of RESTful form parameters
# not <tt><%= %></tt>. Also worth noting is that form_for yields a <tt>form_builder</tt> object, in this example as <tt>f</tt>, which emulates # based on introspection on the record, but to understand what it does we
# the API for the stand-alone FormHelper methods, but without the object name. So instead of <tt>text_field :person, :name</tt>, # need to dig first into the alternative generic usage it is based upon.
# you get away with <tt>f.text_field :name</tt>. Notice that you can even do <tt><%= f.error_messages %></tt> to display the
# error messsages of the model object in question.
# #
# Even further, the form_for method allows you to more easily escape the instance variable convention. So while the stand-alone # === Generic form_for
# approach would require <tt>text_field :person, :name, :object => person</tt>
# to work with local variables instead of instance ones, the form_for calls remain the same. You simply declare once with
# <tt>:person, person</tt> and all subsequent field calls save <tt>:person</tt> and <tt>:object => person</tt>.
# #
# Also note that form_for doesn't create an exclusive scope. It's still possible to use both the stand-alone FormHelper methods # The generic way to call +form_for+ yields a form builder around a model:
# and methods from FormTagHelper. For example: #
# <% 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 />
# Biography : <%= f.text_area :biography %><br />
# Admin? : <%= f.check_box :admin %><br />
# <% end %>
#
# There, the first argument is a symbol or string with the name of the
# object the form is about, and also the name of the instance variable the
# object is stored in.
#
# The form builder acts as a regular form helper that somehow carries the
# model. Thus, the idea is that
#
# <%= f.text_field :first_name %>
#
# gets expanded to
#
# <%= text_field :person, :first_name %>
#
# 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| %>
# ...
# <% end %>
#
# In that case you can think
#
# <%= f.text_field :first_name %>
#
# gets expanded to
#
# <%= text_field :person, :first_name, :object => person %>
#
# You can even display error messages of the wrapped model this way:
#
# <%= f.error_messages %>
#
# In any of its variants, the rightmost argument to +form_for+ is an
# optional hash of options:
#
# * <tt>:url</tt> - The URL the form is submitted to. It takes the same fields
# you pass to +url_for+ or +link_to+. In particular you may pass here a
# named route directly as well. Defaults to the current action.
# * <tt>:html</tt> - Optional HTML attributes for the form tag.
#
# Worth noting is that the +form_for+ tag is called in a ERb evaluation block,
# not an ERb output block. So that's <tt><% %></tt>, not <tt><%= %></tt>.
#
# Also note that +form_for+ doesn't create an exclusive scope. It's still
# 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 %> # First name: <%= f.text_field :first_name %>
@ -105,42 +156,38 @@ module ActionView
# Admin? : <%= check_box_tag "person[admin]", @person.company.admin? %> # Admin? : <%= check_box_tag "person[admin]", @person.company.admin? %>
# <% end %> # <% end %>
# #
# Note: This also works for the methods in FormOptionHelper and DateHelper that are designed to work with an object as base, # This also works for the methods in FormOptionHelper and DateHelper that are
# like FormOptionHelper#collection_select and DateHelper#datetime_select. # designed to work with an object as base, like FormOptionHelper#collection_select
# and DateHelper#datetime_select.
# #
# HTML attributes for the form tag can be given as <tt>:html => {...}</tt>. For example: # === Resource-oriented style
# #
# <% form_for :person, @person, :html => {:id => 'person_form'} do |f| %> # As we said above, in addition to manually configuring the +form_for+ call,
# you can rely on automated resource identification, which will use the conventions
# and named routes of that approach. This is the preferred way to use +form_for+
# nowadays.
#
# For example, if <tt>@post</tt> is an existing record you want to edit
#
# <% form_for @post do |f| %>
# ... # ...
# <% end %> # <% end %>
# #
# The above form will then have the <tt>id</tt> attribute with the value </tt>person_form</tt>, which you can then # is equivalent to something like:
# style with CSS or manipulate with JavaScript.
#
# === Relying on record identification
#
# In addition to manually configuring the form_for call, you can also rely on record identification, which will use
# the conventions and named routes of that approach. Examples:
#
# <% form_for(@post) do |f| %>
# ...
# <% end %>
#
# This will expand to be the same as:
# #
# <% 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 %> # <% end %>
# #
# And for new records: # And for new records
# #
# <% form_for(Post.new) do |f| %> # <% form_for(Post.new) do |f| %>
# ... # ...
# <% end %> # <% end %>
# #
# This will expand to be the same as: # expands to
# #
# <% form_for :post, @post, :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 %> # <% end %>
# #
@ -150,7 +197,7 @@ module ActionView
# ... # ...
# <% end %> # <% end %>
# #
# And for namespaced routes, like admin_post_url: # And for namespaced routes, like +admin_post_url+:
# #
# <% form_for([:admin, @post]) do |f| %> # <% form_for([:admin, @post]) do |f| %>
# ... # ...
@ -277,13 +324,13 @@ module ActionView
# #
# ==== Examples # ==== Examples
# label(:post, :title) # label(:post, :title)
# #=> <label for="post_title">Title</label> # # => <label for="post_title">Title</label>
# #
# label(:post, :title, "A short title") # label(:post, :title, "A short title")
# #=> <label for="post_title">A short title</label> # # => <label for="post_title">A short title</label>
# #
# label(:post, :title, "A short title", :class => "title_label") # label(:post, :title, "A short title", :class => "title_label")
# #=> <label for="post_title" class="title_label">A short title</label> # # => <label for="post_title" class="title_label">A short title</label>
# #
def label(object_name, method, text = nil, options = {}) def label(object_name, method, text = nil, options = {})
InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_label_tag(text, options) InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_label_tag(text, options)
@ -588,6 +635,8 @@ module ActionView
value != 0 value != 0
when String when String
value == checked_value value == checked_value
when Array
value.include?(checked_value)
else else
value.to_i != 0 value.to_i != 0
end end

View file

@ -119,7 +119,7 @@ module ActionView
# end # end
# end # end
# #
# Sample usage (selecting the associated +Author+ for an instance of +Post+, <tt>@post</tt>): # Sample usage (selecting the associated Author for an instance of Post, <tt>@post</tt>):
# collection_select(:post, :author_id, Author.find(:all), :id, :name_with_initial, {:prompt => true}) # collection_select(:post, :author_id, Author.find(:all), :id, :name_with_initial, {:prompt => true})
# #
# If <tt>@post.author_id</tt> is already <tt>1</tt>, this would return: # If <tt>@post.author_id</tt> is already <tt>1</tt>, this would return:
@ -144,10 +144,16 @@ module ActionView
# In addition to the <tt>:include_blank</tt> option documented above, # In addition to the <tt>:include_blank</tt> option documented above,
# this method also supports a <tt>:model</tt> option, which defaults # this method also supports a <tt>:model</tt> option, which defaults
# to TimeZone. This may be used by users to specify a different time # to TimeZone. This may be used by users to specify a different time
# zone model object. (See #time_zone_options_for_select for more # zone model object. (See +time_zone_options_for_select+ for more
# information.) # information.)
#
# You can also supply an array of TimeZone objects
# as +priority_zones+, so that they will be listed above the rest of the
# (long) list. (You can use TimeZone.us_zones as a convenience for
# obtaining a list of the US time zones.)
#
# Finally, this method supports a <tt>:default</tt> option, which selects # Finally, this method supports a <tt>:default</tt> option, which selects
# a default TimeZone if the object's time zone is nil. # a default TimeZone if the object's time zone is +nil+.
# #
# Examples: # Examples:
# time_zone_select( "user", "time_zone", nil, :include_blank => true) # time_zone_select( "user", "time_zone", nil, :include_blank => true)
@ -156,6 +162,8 @@ module ActionView
# #
# time_zone_select( "user", 'time_zone', TimeZone.us_zones, :default => "Pacific Time (US & Canada)") # time_zone_select( "user", 'time_zone', TimeZone.us_zones, :default => "Pacific Time (US & Canada)")
# #
# time_zone_select( "user", 'time_zone', [ TimeZone['Alaska'], TimeZone['Hawaii'] ])
#
# time_zone_select( "user", "time_zone", TZInfo::Timezone.all.sort, :model => TZInfo::Timezone) # time_zone_select( "user", "time_zone", TZInfo::Timezone.all.sort, :model => TZInfo::Timezone)
def time_zone_select(object, method, priority_zones = nil, options = {}, html_options = {}) def time_zone_select(object, method, priority_zones = nil, options = {}, html_options = {})
InstanceTag.new(object, method, self, nil, options.delete(:object)).to_time_zone_select_tag(priority_zones, options, html_options) InstanceTag.new(object, method, self, nil, options.delete(:object)).to_time_zone_select_tag(priority_zones, options, html_options)
@ -164,7 +172,7 @@ module ActionView
# Accepts a container (hash, array, enumerable, your type) and returns a string of option tags. Given a container # Accepts a container (hash, array, enumerable, your type) and returns a string of option tags. Given a container
# where the elements respond to first and last (such as a two-element array), the "lasts" serve as option values and # where the elements respond to first and last (such as a two-element array), the "lasts" serve as option values and
# the "firsts" as option text. Hashes are turned into this form automatically, so the keys become "firsts" and values # the "firsts" as option text. Hashes are turned into this form automatically, so the keys become "firsts" and values
# become lasts. If +selected+ is specified, the matching "last" or element will get the selected option-tag. +Selected+ # become lasts. If +selected+ is specified, the matching "last" or element will get the selected option-tag. +selected+
# may also be an array of values to be selected when using a multiple select. # may also be an array of values to be selected when using a multiple select.
# #
# Examples (call, result): # Examples (call, result):
@ -209,24 +217,22 @@ module ActionView
options_for_select(options, selected) options_for_select(options, selected)
end end
# Returns a string of <tt><option></tt> tags, like <tt>#options_from_collection_for_select</tt>, but # Returns a string of <tt><option></tt> tags, like <tt>options_from_collection_for_select</tt>, but
# groups them by <tt><optgroup></tt> tags based on the object relationships of the arguments. # groups them by <tt><optgroup></tt> tags based on the object relationships of the arguments.
# #
# Parameters: # Parameters:
# +collection+:: An array of objects representing the <tt><optgroup></tt> tags # * +collection+ - An array of objects representing the <tt><optgroup></tt> tags.
# +group_method+:: The name of a method which, when called on a member of +collection+, returns an # * +group_method+ - The name of a method which, when called on a member of +collection+, returns an
# array of child objects representing the <tt><option></tt> tags # array of child objects representing the <tt><option></tt> tags.
# +group_label_method+:: The name of a method which, when called on a member of +collection+, returns a # * group_label_method+ - The name of a method which, when called on a member of +collection+, returns a
# string to be used as the +label+ attribute for its <tt><optgroup></tt> tag # string to be used as the +label+ attribute for its <tt><optgroup></tt> tag.
# +option_key_method+:: The name of a method which, when called on a child object of a member of # * +option_key_method+ - The name of a method which, when called on a child object of a member of
# +collection+, returns a value to be used as the +value+ attribute for its # +collection+, returns a value to be used as the +value+ attribute for its <tt><option></tt> tag.
# <tt><option></tt> tag # * +option_value_method+ - The name of a method which, when called on a child object of a member of
# +option_value_method+:: The name of a method which, when called on a child object of a member of # +collection+, returns a value to be used as the contents of its <tt><option></tt> tag.
# +collection+, returns a value to be used as the contents of its # * +selected_key+ - A value equal to the +value+ attribute for one of the <tt><option></tt> tags,
# <tt><option></tt> tag # which will have the +selected+ attribute set. Corresponds to the return value of one of the calls
# +selected_key+:: A value equal to the +value+ attribute for one of the <tt><option></tt> tags, # to +option_key_method+. If +nil+, no selection is made.
# which will have the +selected+ attribute set. Corresponds to the return value
# of one of the calls to +option_key_method+. If +nil+, no selection is made.
# #
# Example object structure for use with this method: # Example object structure for use with this method:
# class Continent < ActiveRecord::Base # class Continent < ActiveRecord::Base
@ -292,8 +298,8 @@ module ActionView
# a TimeZone. # a TimeZone.
# #
# By default, +model+ is the TimeZone constant (which can be obtained # By default, +model+ is the TimeZone constant (which can be obtained
# in ActiveRecord as a value object). The only requirement is that the # in Active Record as a value object). The only requirement is that the
# +model+ parameter be an object that responds to #all, and returns # +model+ parameter be an object that responds to +all+, and returns
# an array of objects that represent time zones. # an array of objects that represent time zones.
# #
# NOTE: Only the option tags are returned, you have to wrap this call in # NOTE: Only the option tags are returned, you have to wrap this call in

View file

@ -3,7 +3,7 @@ require 'action_view/helpers/tag_helper'
module ActionView module ActionView
module Helpers module Helpers
# Provides a number of methods for creating form tags that doesn't rely on an ActiveRecord object assigned to the template like # Provides a number of methods for creating form tags that doesn't rely on an Active Record object assigned to the template like
# FormHelper does. Instead, you provide the names and values manually. # FormHelper does. Instead, you provide the names and values manually.
# #
# NOTE: The HTML options <tt>disabled</tt>, <tt>readonly</tt>, and <tt>multiple</tt> can all be treated as booleans. So specifying # NOTE: The HTML options <tt>disabled</tt>, <tt>readonly</tt>, and <tt>multiple</tt> can all be treated as booleans. So specifying
@ -14,9 +14,9 @@ module ActionView
# #
# ==== Options # ==== Options
# * <tt>:multipart</tt> - If set to true, the enctype is set to "multipart/form-data". # * <tt>:multipart</tt> - If set to true, the enctype is set to "multipart/form-data".
# * <tt>:method</tt> - The method to use when submitting the form, usually either "get" or "post". # * <tt>:method</tt> - The method to use when submitting the form, usually either "get" or "post".
# If "put", "delete", or another verb is used, a hidden input with name _method # If "put", "delete", or another verb is used, a hidden input with name <tt>_method</tt>
# is added to simulate the verb over post. # is added to simulate the verb over post.
# * A list of parameters to feed to the URL the form will be posted to. # * A list of parameters to feed to the URL the form will be posted to.
# #
# ==== Examples # ==== Examples

View file

@ -458,7 +458,7 @@ module ActionView
url_options = options[:url] url_options = options[:url]
url_options = url_options.merge(:escape => false) if url_options.is_a?(Hash) url_options = url_options.merge(:escape => false) if url_options.is_a?(Hash)
function << "'#{url_for(url_options)}'" function << "'#{escape_javascript(url_for(url_options))}'"
function << ", #{javascript_options})" function << ", #{javascript_options})"
function = "#{options[:before]}; #{function}" if options[:before] function = "#{options[:before]}; #{function}" if options[:before]
@ -595,8 +595,8 @@ module ActionView
# JavaScript sent with a Content-type of "text/javascript". # JavaScript sent with a Content-type of "text/javascript".
# #
# Create new instances with PrototypeHelper#update_page or with # Create new instances with PrototypeHelper#update_page or with
# ActionController::Base#render, then call #insert_html, #replace_html, # ActionController::Base#render, then call +insert_html+, +replace_html+,
# #remove, #show, #hide, #visual_effect, or any other of the built-in # +remove+, +show+, +hide+, +visual_effect+, or any other of the built-in
# methods on the yielded generator in any order you like to modify the # methods on the yielded generator in any order you like to modify the
# content and appearance of the current page. # content and appearance of the current page.
# #
@ -687,7 +687,7 @@ module ActionView
end end
end end
# Returns an object whose <tt>#to_json</tt> evaluates to +code+. Use this to pass a literal JavaScript # Returns an object whose <tt>to_json</tt> evaluates to +code+. Use this to pass a literal JavaScript
# expression as an argument to another JavaScriptGenerator method. # expression as an argument to another JavaScriptGenerator method.
def literal(code) def literal(code)
ActiveSupport::JSON::Variable.new(code.to_s) ActiveSupport::JSON::Variable.new(code.to_s)
@ -1068,7 +1068,7 @@ module ActionView
def build_observer(klass, name, options = {}) def build_observer(klass, name, options = {})
if options[:with] && (options[:with] !~ /[\{=(.]/) if options[:with] && (options[:with] !~ /[\{=(.]/)
options[:with] = "'#{options[:with]}=' + value" options[:with] = "'#{options[:with]}=' + encodeURIComponent(value)"
else else
options[:with] ||= 'value' unless options[:function] options[:with] ||= 'value' unless options[:function]
end end
@ -1173,7 +1173,7 @@ module ActionView
super(generator) super(generator)
end end
# The JSON Encoder calls this to check for the #to_json method # The JSON Encoder calls this to check for the +to_json+ method
# Since it's a blank slate object, I suppose it responds to anything. # Since it's a blank slate object, I suppose it responds to anything.
def respond_to?(method) def respond_to?(method)
true true

View file

@ -2,7 +2,7 @@ module ActionView
module Helpers module Helpers
module RecordTagHelper module RecordTagHelper
# Produces a wrapper DIV element with id and class parameters that # Produces a wrapper DIV element with id and class parameters that
# relate to the specified ActiveRecord object. Usage example: # relate to the specified Active Record object. Usage example:
# #
# <% div_for(@person, :class => "foo") do %> # <% div_for(@person, :class => "foo") do %>
# <%=h @person.name %> # <%=h @person.name %>
@ -17,7 +17,7 @@ module ActionView
end end
# content_tag_for creates an HTML element with id and class parameters # content_tag_for creates an HTML element with id and class parameters
# that relate to the specified ActiveRecord object. For example: # 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.first_name %></td>

View file

@ -57,7 +57,7 @@ module ActionView
self.class.white_list_sanitizer.sanitize(html, options) self.class.white_list_sanitizer.sanitize(html, options)
end end
# Sanitizes a block of css code. Used by #sanitize when it comes across a style attribute # Sanitizes a block of CSS code. Used by +sanitize+ when it comes across a style attribute.
def sanitize_css(style) def sanitize_css(style)
self.class.white_list_sanitizer.sanitize_css(style) self.class.white_list_sanitizer.sanitize_css(style)
end end
@ -111,8 +111,8 @@ module ActionView
end end
end end
# Gets the HTML::FullSanitizer instance used by strip_tags. Replace with # Gets the HTML::FullSanitizer instance used by +strip_tags+. Replace with
# any object that responds to #sanitize # any object that responds to +sanitize+.
# #
# Rails::Initializer.run do |config| # Rails::Initializer.run do |config|
# config.action_view.full_sanitizer = MySpecialSanitizer.new # config.action_view.full_sanitizer = MySpecialSanitizer.new
@ -122,8 +122,8 @@ module ActionView
@full_sanitizer ||= HTML::FullSanitizer.new @full_sanitizer ||= HTML::FullSanitizer.new
end end
# Gets the HTML::LinkSanitizer instance used by strip_links. Replace with # Gets the HTML::LinkSanitizer instance used by +strip_links+. Replace with
# any object that responds to #sanitize # any object that responds to +sanitize+.
# #
# Rails::Initializer.run do |config| # Rails::Initializer.run do |config|
# config.action_view.link_sanitizer = MySpecialSanitizer.new # config.action_view.link_sanitizer = MySpecialSanitizer.new
@ -133,8 +133,8 @@ module ActionView
@link_sanitizer ||= HTML::LinkSanitizer.new @link_sanitizer ||= HTML::LinkSanitizer.new
end end
# Gets the HTML::WhiteListSanitizer instance used by sanitize and sanitize_css. # Gets the HTML::WhiteListSanitizer instance used by sanitize and +sanitize_css+.
# Replace with any object that responds to #sanitize # Replace with any object that responds to +sanitize+.
# #
# Rails::Initializer.run do |config| # Rails::Initializer.run do |config|
# config.action_view.white_list_sanitizer = MySpecialSanitizer.new # config.action_view.white_list_sanitizer = MySpecialSanitizer.new
@ -144,7 +144,7 @@ module ActionView
@white_list_sanitizer ||= HTML::WhiteListSanitizer.new @white_list_sanitizer ||= HTML::WhiteListSanitizer.new
end end
# Adds valid HTML attributes that the #sanitize helper checks for URIs. # Adds valid HTML attributes that the +sanitize+ helper checks for URIs.
# #
# Rails::Initializer.run do |config| # Rails::Initializer.run do |config|
# config.action_view.sanitized_uri_attributes = 'lowsrc', 'target' # config.action_view.sanitized_uri_attributes = 'lowsrc', 'target'
@ -154,7 +154,7 @@ module ActionView
HTML::WhiteListSanitizer.uri_attributes.merge(attributes) HTML::WhiteListSanitizer.uri_attributes.merge(attributes)
end end
# Adds to the Set of 'bad' tags for the #sanitize helper. # Adds to the Set of 'bad' tags for the +sanitize+ helper.
# #
# Rails::Initializer.run do |config| # Rails::Initializer.run do |config|
# config.action_view.sanitized_bad_tags = 'embed', 'object' # config.action_view.sanitized_bad_tags = 'embed', 'object'
@ -163,7 +163,8 @@ module ActionView
def sanitized_bad_tags=(attributes) def sanitized_bad_tags=(attributes)
HTML::WhiteListSanitizer.bad_tags.merge(attributes) HTML::WhiteListSanitizer.bad_tags.merge(attributes)
end end
# Adds to the Set of allowed tags for the #sanitize helper.
# Adds to the Set of allowed tags for the +sanitize+ helper.
# #
# Rails::Initializer.run do |config| # Rails::Initializer.run do |config|
# config.action_view.sanitized_allowed_tags = 'table', 'tr', 'td' # config.action_view.sanitized_allowed_tags = 'table', 'tr', 'td'
@ -173,7 +174,7 @@ module ActionView
HTML::WhiteListSanitizer.allowed_tags.merge(attributes) HTML::WhiteListSanitizer.allowed_tags.merge(attributes)
end end
# Adds to the Set of allowed html attributes for the #sanitize helper. # Adds to the Set of allowed HTML attributes for the +sanitize+ helper.
# #
# Rails::Initializer.run do |config| # Rails::Initializer.run do |config|
# config.action_view.sanitized_allowed_attributes = 'onclick', 'longdesc' # config.action_view.sanitized_allowed_attributes = 'onclick', 'longdesc'
@ -183,7 +184,7 @@ module ActionView
HTML::WhiteListSanitizer.allowed_attributes.merge(attributes) HTML::WhiteListSanitizer.allowed_attributes.merge(attributes)
end end
# Adds to the Set of allowed css properties for the #sanitize and #sanitize_css heleprs. # Adds to the Set of allowed CSS properties for the #sanitize and +sanitize_css+ heleprs.
# #
# Rails::Initializer.run do |config| # Rails::Initializer.run do |config|
# config.action_view.sanitized_allowed_css_properties = 'expression' # config.action_view.sanitized_allowed_css_properties = 'expression'
@ -193,7 +194,7 @@ module ActionView
HTML::WhiteListSanitizer.allowed_css_properties.merge(attributes) HTML::WhiteListSanitizer.allowed_css_properties.merge(attributes)
end end
# Adds to the Set of allowed css keywords for the #sanitize and #sanitize_css helpers. # Adds to the Set of allowed CSS keywords for the +sanitize+ and +sanitize_css+ helpers.
# #
# Rails::Initializer.run do |config| # Rails::Initializer.run do |config|
# config.action_view.sanitized_allowed_css_keywords = 'expression' # config.action_view.sanitized_allowed_css_keywords = 'expression'
@ -203,7 +204,7 @@ module ActionView
HTML::WhiteListSanitizer.allowed_css_keywords.merge(attributes) HTML::WhiteListSanitizer.allowed_css_keywords.merge(attributes)
end end
# Adds to the Set of allowed shorthand css properties for the #sanitize and #sanitize_css helpers. # Adds to the Set of allowed shorthand CSS properties for the +sanitize+ and +sanitize_css+ helpers.
# #
# Rails::Initializer.run do |config| # Rails::Initializer.run do |config|
# config.action_view.sanitized_shorthand_css_properties = 'expression' # config.action_view.sanitized_shorthand_css_properties = 'expression'
@ -213,7 +214,7 @@ module ActionView
HTML::WhiteListSanitizer.shorthand_css_properties.merge(attributes) HTML::WhiteListSanitizer.shorthand_css_properties.merge(attributes)
end end
# Adds to the Set of allowed protocols for the #sanitize helper. # Adds to the Set of allowed protocols for the +sanitize+ helper.
# #
# Rails::Initializer.run do |config| # Rails::Initializer.run do |config|
# config.action_view.sanitized_allowed_protocols = 'ssh', 'feed' # config.action_view.sanitized_allowed_protocols = 'ssh', 'feed'

View file

@ -26,9 +26,9 @@ module ActionView
# :url => { :action => "reload" }, # :url => { :action => "reload" },
# :complete => visual_effect(:highlight, "posts", :duration => 0.5) # :complete => visual_effect(:highlight, "posts", :duration => 0.5)
# #
# If no element_id is given, it assumes "element" which should be a local # If no +element_id+ is given, it assumes "element" which should be a local
# variable in the generated JavaScript execution context. This can be # variable in the generated JavaScript execution context. This can be
# used for example with drop_receiving_element: # used for example with +drop_receiving_element+:
# #
# <%= drop_receiving_element (...), :loading => visual_effect(:fade) %> # <%= drop_receiving_element (...), :loading => visual_effect(:fade) %>
# #
@ -67,6 +67,7 @@ module ActionView
# element as parameters. # element as parameters.
# #
# Example: # Example:
#
# <%= sortable_element("my_list", :url => { :action => "order" }) %> # <%= sortable_element("my_list", :url => { :action => "order" }) %>
# #
# In the example, the action gets a "my_list" array parameter # In the example, the action gets a "my_list" array parameter
@ -79,60 +80,56 @@ module ActionView
# #
# Additional +options+ are: # Additional +options+ are:
# #
# <tt>:format</tt>:: A regular expression to determine what to send # * <tt>:format</tt> - A regular expression to determine what to send as the
# as the serialized id to the server (the default # serialized id to the server (the default is <tt>/^[^_]*_(.*)$/</tt>).
# is <tt>/^[^_]*_(.*)$/</tt>). #
# # * <tt>:constraint</tt> - Whether to constrain the dragging to either
# <tt>:constraint</tt>:: Whether to constrain the dragging to either <tt>:horizontal</tt> # <tt>:horizontal</tt> or <tt>:vertical</tt> (or false to make it unconstrained).
# or <tt>:vertical</tt> (or false to make it unconstrained). #
# # * <tt>:overlap</tt> - Calculate the item overlap in the <tt>:horizontal</tt>
# <tt>:overlap</tt>:: Calculate the item overlap in the <tt>:horizontal</tt> or # or <tt>:vertical</tt> direction.
# <tt>:vertical</tt> direction. #
# # * <tt>:tag</tt> - Which children of the container element to treat as
# <tt>:tag</tt>:: Which children of the container element to treat as # sortable (default is <tt>li</tt>).
# sortable (default is <tt>li</tt>). #
# # * <tt>:containment</tt> - Takes an element or array of elements to treat as
# <tt>:containment</tt>:: Takes an element or array of elements to treat as # potential drop targets (defaults to the original target element).
# potential drop targets (defaults to the original #
# target element). # * <tt>:only</tt> - A CSS class name or arry of class names used to filter
# # out child elements as candidates.
# <tt>:only</tt>:: A CSS class name or arry of class names used to filter #
# out child elements as candidates. # * <tt>:scroll</tt> - Determines whether to scroll the list during drag
# # operations if the list runs past the visual border.
# <tt>:scroll</tt>:: Determines whether to scroll the list during drag #
# operations if the list runs past the visual border. # * <tt>:tree</tt> - Determines whether to treat nested lists as part of the
# # main sortable list. This means that you can create multi-layer lists,
# <tt>:tree</tt>:: Determines whether to treat nested lists as part of the # and not only sort items at the same level, but drag and sort items
# main sortable list. This means that you can create multi- # between levels.
# layer lists, and not only sort items at the same level, #
# but drag and sort items between levels. # * <tt>:hoverclass</tt> - If set, the Droppable will have this additional CSS class
# # when an accepted Draggable is hovered over it.
# <tt>:hoverclass</tt>:: If set, the Droppable will have this additional CSS class #
# when an accepted Draggable is hovered over it. # * <tt>:handle</tt> - Sets whether the element should only be draggable by an
# # embedded handle. The value may be a string referencing a CSS class value
# <tt>:handle</tt>:: Sets whether the element should only be draggable by an # (as of script.aculo.us V1.5). The first child/grandchild/etc. element
# embedded handle. The value may be a string referencing a # found within the element that has this CSS class value will be used as
# CSS class value (as of script.aculo.us V1.5). The first # the handle.
# child/grandchild/etc. element found within the element #
# that has this CSS class value will be used as the handle. # * <tt>:ghosting</tt> - Clones the element and drags the clone, leaving
# # the original in place until the clone is dropped (default is <tt>false</tt>).
# <tt>:ghosting</tt>:: Clones the element and drags the clone, leaving the original #
# in place until the clone is dropped (default is <tt>false</tt>). # * <tt>:dropOnEmpty</tt> - If true the Sortable container will be made into
# # a Droppable, that can receive a Draggable (as according to the containment
# <tt>:dropOnEmpty</tt>:: If set to true, the Sortable container will be made into # rules) as a child element when there are no more elements inside (default
# a Droppable, that can receive a Draggable (as according to # is <tt>false</tt>).
# the containment rules) as a child element when there are no #
# more elements inside (default is <tt>false</tt>). # * <tt>:onChange</tt> - Called whenever the sort order changes while dragging. When
# # dragging from one Sortable to another, the callback is called once on each
# <tt>:onChange</tt>:: Called whenever the sort order changes while dragging. When # Sortable. Gets the affected element as its parameter.
# dragging from one Sortable to another, the callback is #
# called once on each Sortable. Gets the affected element as # * <tt>:onUpdate</tt> - Called when the drag ends and the Sortable's order is
# its parameter. # changed in any way. When dragging from one Sortable to another, the callback
# # is called once on each Sortable. Gets the container as its parameter.
# <tt>:onUpdate</tt>:: Called when the drag ends and the Sortable's order is
# changed in any way. When dragging from one Sortable to
# another, the callback is called once on each Sortable. Gets
# the container as its parameter.
# #
# See http://script.aculo.us for more documentation. # See http://script.aculo.us for more documentation.
def sortable_element(element_id, options = {}) def sortable_element(element_id, options = {})
@ -170,8 +167,8 @@ module ActionView
end end
# Makes the element with the DOM ID specified by +element_id+ receive # Makes the element with the DOM ID specified by +element_id+ receive
# dropped draggable elements (created by draggable_element). # dropped draggable elements (created by +draggable_element+).
# and make an AJAX call By default, the action called gets the DOM ID # and make an AJAX call. By default, the action called gets the DOM ID
# of the element as parameter. # of the element as parameter.
# #
# Example: # Example:
@ -182,32 +179,30 @@ module ActionView
# http://script.aculo.us for more documentation. # http://script.aculo.us for more documentation.
# #
# Some of these +options+ include: # Some of these +options+ include:
# <tt>:accept</tt>:: Set this to a string or an array of strings describing the # * <tt>:accept</tt> - Set this to a string or an array of strings describing the
# allowable CSS classes that the draggable_element must have in order # allowable CSS classes that the +draggable_element+ must have in order
# to be accepted by this drop_receiving_element. # to be accepted by this +drop_receiving_element+.
#
# <tt>:confirm</tt>:: Adds a confirmation dialog.
#
# Example:
# :confirm => "Are you sure you want to do this?"
#
# <tt>:hoverclass</tt>:: If set, the drop_receiving_element will have this additional CSS class
# when an accepted draggable_element is hovered over it.
#
# <tt>:onDrop</tt>:: Called when a draggable_element is dropped onto this element.
# Override this callback with a javascript expression to
# change the default drop behavour.
#
# Example:
# :onDrop => "function(draggable_element, droppable_element, event) { alert('I like bananas') }"
# #
# This callback gets three parameters: # * <tt>:confirm</tt> - Adds a confirmation dialog. Example:
# The +Draggable+ element, the +Droppable+ element and the #
# +Event+ object. You can extract additional information about the # :confirm => "Are you sure you want to do this?"
# drop - like if the Ctrl or Shift keys were pressed - from the +Event+ object. #
# # * <tt>:hoverclass</tt> - If set, the +drop_receiving_element+ will have
# <tt>:with</tt>:: A JavaScript expression specifying the parameters for the XMLHttpRequest. # this additional CSS class when an accepted +draggable_element+ is
# Any expressions should return a valid URL query string. # hovered over it.
#
# * <tt>:onDrop</tt> - Called when a +draggable_element+ is dropped onto
# this element. Override this callback with a JavaScript expression to
# change the default drop behavour. Example:
#
# :onDrop => "function(draggable_element, droppable_element, event) { alert('I like bananas') }"
#
# This callback gets three parameters: The Draggable element, the Droppable
# element and the Event object. You can extract additional information about
# the drop - like if the Ctrl or Shift keys were pressed - from the Event object.
#
# * <tt>:with</tt> - A JavaScript expression specifying the parameters for
# the XMLHttpRequest. Any expressions should return a valid URL query string.
def drop_receiving_element(element_id, options = {}) def drop_receiving_element(element_id, options = {})
javascript_tag(drop_receiving_element_js(element_id, options).chop!) javascript_tag(drop_receiving_element_js(element_id, options).chop!)
end end

View file

@ -3,19 +3,19 @@ require 'html/document'
module ActionView module ActionView
module Helpers #:nodoc: module Helpers #:nodoc:
# The TextHelper module provides a set of methods for filtering, formatting # The TextHelper module provides a set of methods for filtering, formatting
# and transforming strings, which can reduce the amount of inline Ruby code in # and transforming strings, which can reduce the amount of inline Ruby code in
# your views. These helper methods extend ActionView making them callable # your views. These helper methods extend ActionView making them callable
# within your template files. # within your template files.
module TextHelper module TextHelper
# The preferred method of outputting text in your views is to use the # The preferred method of outputting text in your views is to use the
# <%= "text" %> eRuby syntax. The regular _puts_ and _print_ methods # <%= "text" %> eRuby syntax. The regular _puts_ and _print_ methods
# do not operate as expected in an eRuby code block. If you absolutely must # do not operate as expected in an eRuby code block. If you absolutely must
# output text within a non-output code block (i.e., <% %>), you can use the concat method. # output text within a non-output code block (i.e., <% %>), you can use the concat method.
# #
# ==== Examples # ==== Examples
# <% # <%
# concat "hello", binding # concat "hello", binding
# # is the equivalent of <%= "hello" %> # # is the equivalent of <%= "hello" %>
# #
# if (logged_in == true): # if (logged_in == true):
@ -30,15 +30,15 @@ module ActionView
end end
if RUBY_VERSION < '1.9' if RUBY_VERSION < '1.9'
# If +text+ is longer than +length+, +text+ will be truncated to the length of # If +text+ is longer than +length+, +text+ will be truncated to the length of
# +length+ (defaults to 30) and the last characters will be replaced with the +truncate_string+ # +length+ (defaults to 30) and the last characters will be replaced with the +truncate_string+
# (defaults to "..."). # (defaults to "...").
# #
# ==== Examples # ==== Examples
# truncate("Once upon a time in a world far far away", 14) # truncate("Once upon a time in a world far far away", 14)
# # => Once upon a... # # => Once upon a...
# #
# truncate("Once upon a time in a world far far away") # truncate("Once upon a time in a world far far away")
# # => Once upon a time in a world f... # # => Once upon a time in a world f...
# #
# truncate("And they found that many people were sleeping better.", 25, "(clipped)") # truncate("And they found that many people were sleeping better.", 25, "(clipped)")
@ -63,20 +63,20 @@ module ActionView
end end
# Highlights one or more +phrases+ everywhere in +text+ by inserting it into # Highlights one or more +phrases+ everywhere in +text+ by inserting it into
# a +highlighter+ string. The highlighter can be specialized by passing +highlighter+ # a +highlighter+ string. The highlighter can be specialized by passing +highlighter+
# as a single-quoted string with \1 where the phrase is to be inserted (defaults to # as a single-quoted string with \1 where the phrase is to be inserted (defaults to
# '<strong class="highlight">\1</strong>') # '<strong class="highlight">\1</strong>')
# #
# ==== Examples # ==== Examples
# highlight('You searched for: rails', 'rails') # highlight('You searched for: rails', 'rails')
# # => You searched for: <strong class="highlight">rails</strong> # # => You searched for: <strong class="highlight">rails</strong>
# #
# highlight('You searched for: ruby, rails, dhh', 'actionpack') # highlight('You searched for: ruby, rails, dhh', 'actionpack')
# # => You searched for: ruby, rails, dhh # # => You searched for: ruby, rails, dhh
# #
# highlight('You searched for: rails', ['for', 'rails'], '<em>\1</em>') # highlight('You searched for: rails', ['for', 'rails'], '<em>\1</em>')
# # => You searched <em>for</em>: <em>rails</em> # # => You searched <em>for</em>: <em>rails</em>
# #
# highlight('You searched for: rails', 'rails', "<a href='search?q=\1'>\1</a>") # highlight('You searched for: rails', 'rails', "<a href='search?q=\1'>\1</a>")
# # => You searched for: <a href='search?q=rails>rails</a> # # => You searched for: <a href='search?q=rails>rails</a>
def highlight(text, phrases, highlighter = '<strong class="highlight">\1</strong>') def highlight(text, phrases, highlighter = '<strong class="highlight">\1</strong>')
@ -89,23 +89,23 @@ module ActionView
end end
if RUBY_VERSION < '1.9' if RUBY_VERSION < '1.9'
# Extracts an excerpt from +text+ that matches the first instance of +phrase+. # Extracts an excerpt from +text+ that matches the first instance of +phrase+.
# The +radius+ expands the excerpt on each side of the first occurrence of +phrase+ by the number of characters # The +radius+ expands the excerpt on each side of the first occurrence of +phrase+ by the number of characters
# defined in +radius+ (which defaults to 100). If the excerpt radius overflows the beginning or end of the +text+, # defined in +radius+ (which defaults to 100). If the excerpt radius overflows the beginning or end of the +text+,
# then the +excerpt_string+ will be prepended/appended accordingly. The resulting string will be stripped in any case. # then the +excerpt_string+ will be prepended/appended accordingly. The resulting string will be stripped in any case.
# If the +phrase+ isn't found, nil is returned. # If the +phrase+ isn't found, nil is returned.
# #
# ==== Examples # ==== Examples
# excerpt('This is an example', 'an', 5) # excerpt('This is an example', 'an', 5)
# # => "...s is an exam..." # # => "...s is an exam..."
# #
# excerpt('This is an example', 'is', 5) # excerpt('This is an example', 'is', 5)
# # => "This is a..." # # => "This is a..."
# #
# excerpt('This is an example', 'is') # excerpt('This is an example', 'is')
# # => "This is an example" # # => "This is an example"
# #
# excerpt('This next thing is an example', 'ex', 2) # excerpt('This next thing is an example', 'ex', 2)
# # => "...next..." # # => "...next..."
# #
# excerpt('This is also an example', 'an', 8, '<chop> ') # excerpt('This is also an example', 'an', 8, '<chop> ')
@ -147,33 +147,24 @@ module ActionView
end end
end end
# Attempts to pluralize the +singular+ word unless +count+ is 1. If +plural+ # Attempts to pluralize the +singular+ word unless +count+ is 1. If
# is supplied, it will use that when count is > 1, if the ActiveSupport Inflector # +plural+ is supplied, it will use that when count is > 1, otherwise
# is loaded, it will use the Inflector to determine the plural form, otherwise # it will use the Inflector to determine the plural form
# it will just add an 's' to the +singular+ word.
# #
# ==== Examples # ==== Examples
# pluralize(1, 'person') # pluralize(1, 'person')
# # => 1 person # # => 1 person
# #
# pluralize(2, 'person') # pluralize(2, 'person')
# # => 2 people # # => 2 people
# #
# pluralize(3, 'person', 'users') # pluralize(3, 'person', 'users')
# # => 3 users # # => 3 users
# #
# pluralize(0, 'person') # pluralize(0, 'person')
# # => 0 people # # => 0 people
def pluralize(count, singular, plural = nil) def pluralize(count, singular, plural = nil)
"#{count || 0} " + if count == 1 || count == '1' "#{count || 0} " + ((count == 1 || count == '1') ? singular : (plural || singular.pluralize))
singular
elsif plural
plural
elsif Object.const_defined?("Inflector")
Inflector.pluralize(singular)
else
singular + "s"
end
end end
# Wraps the +text+ into lines no longer than +line_width+ width. This method # Wraps the +text+ into lines no longer than +line_width+ width. This method
@ -229,7 +220,7 @@ module ActionView
end end
end end
# Returns the text with all the Textile codes turned into HTML tags, # Returns the text with all the Textile codes turned into HTML tags,
# but without the bounding <p> tag that RedCloth adds. # but without the bounding <p> tag that RedCloth adds.
# #
# You can learn more about Textile's syntax at its website[http://www.textism.com/tools/textile]. # You can learn more about Textile's syntax at its website[http://www.textism.com/tools/textile].
@ -273,25 +264,25 @@ module ActionView
# # => "<p>We like to <em>write</em> <code>code</code>, not just <em>read</em> it!</p>" # # => "<p>We like to <em>write</em> <code>code</code>, not just <em>read</em> it!</p>"
# #
# markdown("The [Markdown website](http://daringfireball.net/projects/markdown/) has more information.") # markdown("The [Markdown website](http://daringfireball.net/projects/markdown/) has more information.")
# # => "<p>The <a href="http://daringfireball.net/projects/markdown/">Markdown website</a> # # => "<p>The <a href="http://daringfireball.net/projects/markdown/">Markdown website</a>
# # has more information.</p>" # # has more information.</p>"
# #
# markdown('![The ROR logo](http://rubyonrails.com/images/rails.png "Ruby on Rails")') # markdown('![The ROR logo](http://rubyonrails.com/images/rails.png "Ruby on Rails")')
# # => '<p><img src="http://rubyonrails.com/images/rails.png" alt="The ROR logo" title="Ruby on Rails" /></p>' # # => '<p><img src="http://rubyonrails.com/images/rails.png" alt="The ROR logo" title="Ruby on Rails" /></p>'
def markdown(text) def markdown(text)
text.blank? ? "" : BlueCloth.new(text).to_html text.blank? ? "" : BlueCloth.new(text).to_html
end end
rescue LoadError rescue LoadError
# We can't really help what's not there # We can't really help what's not there
end end
# Returns +text+ transformed into HTML using simple formatting rules. # Returns +text+ transformed into HTML using simple formatting rules.
# Two or more consecutive newlines(<tt>\n\n</tt>) are considered as a # Two or more consecutive newlines(<tt>\n\n</tt>) are considered as a
# paragraph and wrapped in <tt><p></tt> tags. One newline (<tt>\n</tt>) is # paragraph and wrapped in <tt><p></tt> tags. One newline (<tt>\n</tt>) is
# considered as a linebreak and a <tt><br /></tt> tag is appended. This # considered as a linebreak and a <tt><br /></tt> tag is appended. This
# method does not remove the newlines from the +text+. # method does not remove the newlines from the +text+.
# #
# You can pass any HTML attributes into <tt>html_options</tt>. These # You can pass any HTML attributes into <tt>html_options</tt>. These
# will be added to all created paragraphs. # will be added to all created paragraphs.
# ==== Examples # ==== Examples
# my_text = "Here is some basic text...\n...with a line break." # my_text = "Here is some basic text...\n...with a line break."
@ -316,19 +307,19 @@ module ActionView
text << "</p>" text << "</p>"
end end
# Turns all URLs and e-mail addresses into clickable links. The +link+ parameter # Turns all URLs and e-mail addresses into clickable links. The +link+ parameter
# will limit what should be linked. You can add HTML attributes to the links using # will limit what should be linked. You can add HTML attributes to the links using
# +href_options+. Options for +link+ are <tt>:all</tt> (default), # +href_options+. Options for +link+ are <tt>:all</tt> (default),
# <tt>:email_addresses</tt>, and <tt>:urls</tt>. If a block is given, each URL and # <tt>:email_addresses</tt>, and <tt>:urls</tt>. If a block is given, each URL and
# e-mail address is yielded and the result is used as the link text. # e-mail address is yielded and the result is used as the link text.
# #
# ==== Examples # ==== Examples
# auto_link("Go to http://www.rubyonrails.org and say hello to david@loudthinking.com") # auto_link("Go to http://www.rubyonrails.org and say hello to david@loudthinking.com")
# # => "Go to <a href=\"http://www.rubyonrails.org\">http://www.rubyonrails.org</a> and # # => "Go to <a href=\"http://www.rubyonrails.org\">http://www.rubyonrails.org</a> and
# # say hello to <a href=\"mailto:david@loudthinking.com\">david@loudthinking.com</a>" # # say hello to <a href=\"mailto:david@loudthinking.com\">david@loudthinking.com</a>"
# #
# auto_link("Visit http://www.loudthinking.com/ or e-mail david@loudthinking.com", :urls) # auto_link("Visit http://www.loudthinking.com/ or e-mail david@loudthinking.com", :urls)
# # => "Visit <a href=\"http://www.loudthinking.com/\">http://www.loudthinking.com/</a> # # => "Visit <a href=\"http://www.loudthinking.com/\">http://www.loudthinking.com/</a>
# # or e-mail david@loudthinking.com" # # or e-mail david@loudthinking.com"
# #
# auto_link("Visit http://www.loudthinking.com/ or e-mail david@loudthinking.com", :email_addresses) # auto_link("Visit http://www.loudthinking.com/ or e-mail david@loudthinking.com", :email_addresses)
@ -338,9 +329,9 @@ module ActionView
# auto_link(post_body, :all, :target => '_blank') do |text| # auto_link(post_body, :all, :target => '_blank') do |text|
# truncate(text, 15) # truncate(text, 15)
# end # end
# # => "Welcome to my new blog at <a href=\"http://www.myblog.com/\" target=\"_blank\">http://www.m...</a>. # # => "Welcome to my new blog at <a href=\"http://www.myblog.com/\" target=\"_blank\">http://www.m...</a>.
# Please e-mail me at <a href=\"mailto:me@email.com\">me@email.com</a>." # Please e-mail me at <a href=\"mailto:me@email.com\">me@email.com</a>."
# #
def auto_link(text, link = :all, href_options = {}, &block) def auto_link(text, link = :all, href_options = {}, &block)
return '' if text.blank? return '' if text.blank?
case link case link
@ -349,15 +340,15 @@ module ActionView
when :urls then auto_link_urls(text, href_options, &block) when :urls then auto_link_urls(text, href_options, &block)
end end
end end
# Creates a Cycle object whose _to_s_ method cycles through elements of an # Creates a Cycle object whose _to_s_ method cycles through elements of an
# array every time it is called. This can be used for example, to alternate # array every time it is called. This can be used for example, to alternate
# classes for table rows. You can use named cycles to allow nesting in loops. # classes for table rows. You can use named cycles to allow nesting in loops.
# Passing a Hash as the last parameter with a <tt>:name</tt> key will create a # Passing a Hash as the last parameter with a <tt>:name</tt> key will create a
# named cycle. You can manually reset a cycle by calling reset_cycle and passing the # named cycle. You can manually reset a cycle by calling reset_cycle and passing the
# name of the cycle. # name of the cycle.
# #
# ==== Examples # ==== Examples
# # Alternate CSS classes for even and odd numbers... # # Alternate CSS classes for even and odd numbers...
# @items = [1,2,3,4] # @items = [1,2,3,4]
# <table> # <table>
@ -370,8 +361,8 @@ module ActionView
# #
# #
# # Cycle CSS classes for rows, and text colors for values within each row # # Cycle CSS classes for rows, and text colors for values within each row
# @items = x = [{:first => 'Robert', :middle => 'Daniel', :last => 'James'}, # @items = x = [{:first => 'Robert', :middle => 'Daniel', :last => 'James'},
# {:first => 'Emily', :middle => 'Shannon', :maiden => 'Pike', :last => 'Hicks'}, # {:first => 'Emily', :middle => 'Shannon', :maiden => 'Pike', :last => 'Hicks'},
# {:first => 'June', :middle => 'Dae', :last => 'Jones'}] # {:first => 'June', :middle => 'Dae', :last => 'Jones'}]
# <% @items.each do |item| %> # <% @items.each do |item| %>
# <tr class="<%= cycle("even", "odd", :name => "row_class") -%>"> # <tr class="<%= cycle("even", "odd", :name => "row_class") -%>">
@ -401,8 +392,8 @@ module ActionView
end end
return cycle.to_s return cycle.to_s
end end
# Resets a cycle so that it starts from the first element the next time # Resets a cycle so that it starts from the first element the next time
# it is called. Pass in +name+ to reset a named cycle. # it is called. Pass in +name+ to reset a named cycle.
# #
# ==== Example # ==== Example
@ -428,12 +419,12 @@ module ActionView
class Cycle #:nodoc: class Cycle #:nodoc:
attr_reader :values attr_reader :values
def initialize(first_value, *values) def initialize(first_value, *values)
@values = values.unshift(first_value) @values = values.unshift(first_value)
reset reset
end end
def reset def reset
@index = 0 @index = 0
end end
@ -453,7 +444,7 @@ module ActionView
@_cycles = Hash.new unless defined?(@_cycles) @_cycles = Hash.new unless defined?(@_cycles)
return @_cycles[name] return @_cycles[name]
end end
def set_cycle(name, cycle_object) def set_cycle(name, cycle_object)
@_cycles = Hash.new unless defined?(@_cycles) @_cycles = Hash.new unless defined?(@_cycles)
@_cycles[name] = cycle_object @_cycles[name] = cycle_object
@ -462,22 +453,22 @@ module ActionView
AUTO_LINK_RE = %r{ AUTO_LINK_RE = %r{
( # leading text ( # leading text
<\w+.*?>| # leading HTML tag, or <\w+.*?>| # leading HTML tag, or
[^=!:'"/]| # leading punctuation, or [^=!:'"/]| # leading punctuation, or
^ # beginning of line ^ # beginning of line
) )
( (
(?:https?://)| # protocol spec, or (?:https?://)| # protocol spec, or
(?:www\.) # www.* (?:www\.) # www.*
) )
( (
[-\w]+ # subdomain or domain [-\w]+ # subdomain or domain
(?:\.[-\w]+)* # remaining subdomains or domain (?:\.[-\w]+)* # remaining subdomains or domain
(?::\d+)? # port (?::\d+)? # port
(?:/(?:(?:[~\w\+@%=-]|(?:[,.;:][^\s$]))+)?)* # path (?:/(?:(?:[~\w\+@%=\(\)-]|(?:[,.;:][^\s$]))+)?)* # path
(?:\?[\w\+@%&=.;-]+)? # query string (?:\?[\w\+@%&=.;-]+)? # query string
(?:\#[\w\-]*)? # trailing anchor (?:\#[\w\-]*)? # trailing anchor
) )
([[:punct:]]|\s|<|$) # trailing text ([[:punct:]]|<|$|) # trailing text
}x unless const_defined?(:AUTO_LINK_RE) }x unless const_defined?(:AUTO_LINK_RE)
# Turns all urls into clickable links. If a block is given, each url # Turns all urls into clickable links. If a block is given, each url
@ -502,7 +493,7 @@ module ActionView
body = text.dup 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 text = $1
if body.match(/<a\b[^>]*>(.*)(#{Regexp.escape(text)})(.*)<\/a>/) if body.match(/<a\b[^>]*>(.*)(#{Regexp.escape(text)})(.*)<\/a>/)
text text
else else

View file

@ -10,7 +10,7 @@ module ActionView
include JavaScriptHelper include JavaScriptHelper
# Returns the URL for the set of +options+ provided. This takes the # Returns the URL for the set of +options+ provided. This takes the
# same options as url_for in ActionController (see the # same options as +url_for+ in Action Controller (see the
# documentation for ActionController::Base#url_for). Note that by default # documentation for ActionController::Base#url_for). Note that by default
# <tt>:only_path</tt> is <tt>true</tt> so you'll get the relative /controller/action # <tt>:only_path</tt> is <tt>true</tt> so you'll get the relative /controller/action
# instead of the fully qualified URL like http://example.com/controller/action. # instead of the fully qualified URL like http://example.com/controller/action.
@ -120,17 +120,72 @@ module ActionView
# exception. # exception.
# #
# ==== Examples # ==== Examples
# Because it relies on +url_for+, +link_to+ supports both older-style controller/action/id arguments
# and newer RESTful routes. Current Rails style favors RESTful routes whenever possible, so base
# your application on resources and use
#
# link_to "Profile", profile_path(@profile)
# # => <a href="/profiles/1">Profile</a>
#
# or the even pithier
#
# link_to "Profile", @profile
# # => <a href="/profiles/1">Profile</a>
#
# in place of the older more verbose, non-resource-oriented
#
# link_to "Profile", :controller => "profiles", :action => "show", :id => @profile
# # => <a href="/profiles/show/1">Profile</a>
#
# Similarly,
#
# link_to "Profiles", profiles_path
# # => <a href="/profiles">Profiles</a>
#
# is better than
#
# link_to "Profiles", :controller => "profiles"
# # => <a href="/profiles">Profiles</a>
#
# Classes and ids for CSS are easy to produce:
#
# link_to "Articles", articles_path, :id => "news", :class => "article"
# # => <a href="/articles" class="article" id="news">Articles</a>
#
# Be careful when using the older argument style, as an extra literal hash is needed:
#
# link_to "Articles", { :controller => "articles" }, :id => "news", :class => "article"
# # => <a href="/articles" class="article" id="news">Articles</a>
#
# Leaving the hash off gives the wrong link:
#
# link_to "WRONG!", :controller => "articles", :id => "news", :class => "article"
# # => <a href="/articles/index/news?class=article">WRONG!</a>
#
# +link_to+ can also produce links with anchors or query strings:
#
# link_to "Comment wall", profile_path(@profile, :anchor => "wall")
# # => <a href="/profiles/1#wall">Comment wall</a>
#
# link_to "Ruby on Rails search", :controller => "searches", :query => "ruby on rails"
# # => <a href="/searches?query=ruby+on+rails">Ruby on Rails search</a>
#
# link_to "Nonsense search", searches_path(:foo => "bar", :baz => "quux")
# # => <a href="/searches?foo=bar&amp;baz=quux">Nonsense search</a>
#
# The three options specfic to +link_to+ (<tt>:confirm</tt>, <tt>:popup</tt>, and <tt>:method</tt>) are used as follows:
#
# link_to "Visit Other Site", "http://www.rubyonrails.org/", :confirm => "Are you sure?" # link_to "Visit Other Site", "http://www.rubyonrails.org/", :confirm => "Are you sure?"
# # => <a href="http://www.rubyonrails.org/" onclick="return confirm('Are you sure?');">Visit Other Site</a> # # => <a href="http://www.rubyonrails.org/" onclick="return confirm('Are you sure?');">Visit Other Site</a>
# #
# link_to "Help", { :action => "help" }, :popup => true # link_to "Help", { :action => "help" }, :popup => true
# # => <a href="/testing/help/" onclick="window.open(this.href);return false;">Help</a> # # => <a href="/testing/help/" onclick="window.open(this.href);return false;">Help</a>
# #
# link_to "View Image", { :action => "view" }, :popup => ['new_window_name', 'height=300,width=600'] # link_to "View Image", @image, :popup => ['new_window_name', 'height=300,width=600']
# # => <a href="/testing/view/" onclick="window.open(this.href,'new_window_name','height=300,width=600');return false;">View Image</a> # # => <a href="/images/9" onclick="window.open(this.href,'new_window_name','height=300,width=600');return false;">View Image</a>
# #
# link_to "Delete Image", { :action => "delete", :id => @image.id }, :confirm => "Are you sure?", :method => :delete # link_to "Delete Image", @image, :confirm => "Are you sure?", :method => :delete
# # => <a href="/testing/delete/9/" onclick="if (confirm('Are you sure?')) { var f = document.createElement('form'); # # => <a href="/images/9" onclick="if (confirm('Are you sure?')) { var f = document.createElement('form');
# f.style.display = 'none'; this.parentNode.appendChild(f); f.method = 'POST'; f.action = this.href; # f.style.display = 'none'; this.parentNode.appendChild(f); f.method = 'POST'; f.action = this.href;
# var m = document.createElement('input'); m.setAttribute('type', 'hidden'); m.setAttribute('name', '_method'); # var m = document.createElement('input'); m.setAttribute('type', 'hidden'); m.setAttribute('name', '_method');
# m.setAttribute('value', 'delete'); f.appendChild(m);f.submit(); };return false;">Delete Image</a> # m.setAttribute('value', 'delete'); f.appendChild(m);f.submit(); };return false;">Delete Image</a>

View file

@ -93,9 +93,9 @@ module ActionView #:nodoc:
# Register a class that knows how to handle template files with the given # Register a class that knows how to handle template files with the given
# extension. This can be used to implement new template types. # extension. This can be used to implement new template types.
# The constructor for the class must take the ActiveView::Base instance # The constructor for the class must take the ActiveView::Base instance
# as a parameter, and the class must implement a #render method that # as a parameter, and the class must implement a +render+ method that
# takes the contents of the template to render as well as the Hash of # takes the contents of the template to render as well as the Hash of
# local assigns available to the template. The #render method ought to # local assigns available to the template. The +render+ method ought to
# return the rendered template as a string. # return the rendered template as a string.
def self.register_template_handler(extension, klass) def self.register_template_handler(extension, klass)
@@template_handlers[extension.to_sym] = klass @@template_handlers[extension.to_sym] = klass

View file

@ -1,14 +1,6 @@
require 'active_support/test_case' require 'active_support/test_case'
module ActionView module ActionView
class NonInferrableHelperError < ActionViewError
def initialize(name)
super "Unable to determine the helper to test from #{name}. " +
"You'll need to specify it using tests YourHelper in your " +
"test case definition"
end
end
class TestCase < ActiveSupport::TestCase class TestCase < ActiveSupport::TestCase
class_inheritable_accessor :helper_class class_inheritable_accessor :helper_class
@@helper_class = nil @@helper_class = nil
@ -29,7 +21,7 @@ module ActionView
def determine_default_helper_class(name) def determine_default_helper_class(name)
name.sub(/Test$/, '').constantize name.sub(/Test$/, '').constantize
rescue NameError rescue NameError
raise NonInferrableHelperError.new(name) nil
end end
end end
@ -42,7 +34,9 @@ module ActionView
setup :setup_with_helper_class setup :setup_with_helper_class
def setup_with_helper_class def setup_with_helper_class
self.class.send(:include, helper_class) if helper_class && !self.class.ancestors.include?(helper_class)
self.class.send(:include, helper_class)
end
end end
class TestController < ActionController::Base class TestController < ActionController::Base

View file

@ -131,6 +131,10 @@ class AssertResponseWithUnexpectedErrorController < ActionController::Base
def index def index
raise 'FAIL' raise 'FAIL'
end end
def show
render :text => "Boom", :status => 500
end
end end
module Admin module Admin
@ -483,6 +487,16 @@ class ActionPackAssertionsControllerTest < Test::Unit::TestCase
rescue Test::Unit::AssertionFailedError => e rescue Test::Unit::AssertionFailedError => e
assert e.message.include?('FAIL') assert e.message.include?('FAIL')
end end
def test_assert_response_failure_response_with_no_exception
@controller = AssertResponseWithUnexpectedErrorController.new
get :show
assert_response :success
flunk 'Expected non-success response'
rescue Test::Unit::AssertionFailedError
rescue
flunk "assert_response failed to handle failure response with missing, but optional, exception."
end
end end
class ActionPackHeaderTest < Test::Unit::TestCase class ActionPackHeaderTest < Test::Unit::TestCase

View file

@ -6,6 +6,7 @@ CACHE_DIR = 'test_cache'
FILE_STORE_PATH = File.join(File.dirname(__FILE__), '/../temp/', CACHE_DIR) FILE_STORE_PATH = File.join(File.dirname(__FILE__), '/../temp/', CACHE_DIR)
ActionController::Base.page_cache_directory = FILE_STORE_PATH ActionController::Base.page_cache_directory = FILE_STORE_PATH
ActionController::Base.cache_store = :file_store, FILE_STORE_PATH ActionController::Base.cache_store = :file_store, FILE_STORE_PATH
ActionController::Base.view_paths = [ File.dirname(__FILE__) + '/../fixtures/' ]
class PageCachingTestController < ActionController::Base class PageCachingTestController < ActionController::Base
caches_page :ok, :no_content, :if => Proc.new { |c| !c.request.format.json? } caches_page :ok, :no_content, :if => Proc.new { |c| !c.request.format.json? }
@ -128,7 +129,7 @@ class PageCachingTest < Test::Unit::TestCase
end end
end end
end end
def test_page_caching_conditional_options def test_page_caching_conditional_options
@request.env['HTTP_ACCEPT'] = 'application/json' @request.env['HTTP_ACCEPT'] = 'application/json'
get :ok get :ok
@ -151,12 +152,15 @@ end
class ActionCachingTestController < ActionController::Base class ActionCachingTestController < ActionController::Base
caches_action :index, :redirected, :forbidden caches_action :index, :redirected, :forbidden, :if => Proc.new { |c| !c.request.format.json? }
caches_action :show, :cache_path => 'http://test.host/custom/show' caches_action :show, :cache_path => 'http://test.host/custom/show'
caches_action :edit, :cache_path => Proc.new { |c| c.params[:id] ? "http://test.host/#{c.params[:id]};edit" : "http://test.host/edit" } caches_action :edit, :cache_path => Proc.new { |c| c.params[:id] ? "http://test.host/#{c.params[:id]};edit" : "http://test.host/edit" }
caches_action :with_layout
layout 'talk_from_action.erb'
def index def index
@cache_this = Time.now.to_f.to_s @cache_this = MockTime.now.to_f.to_s
render :text => @cache_this render :text => @cache_this
end end
@ -169,14 +173,26 @@ class ActionCachingTestController < ActionController::Base
headers["Status"] = "403 Forbidden" headers["Status"] = "403 Forbidden"
end end
def with_layout
@cache_this = MockTime.now.to_f.to_s
render :text => @cache_this, :layout => true
end
alias_method :show, :index alias_method :show, :index
alias_method :edit, :index alias_method :edit, :index
alias_method :destroy, :index
def expire def expire
expire_action :controller => 'action_caching_test', :action => 'index' expire_action :controller => 'action_caching_test', :action => 'index'
render :nothing => true render :nothing => true
end end
end
class MockTime < Time
# Let Time spicy to assure that Time.now != Time.now
def to_f
super+rand
end
end end
class ActionCachingMockController class ActionCachingMockController
@ -216,18 +232,48 @@ class ActionCacheTest < Test::Unit::TestCase
get :index get :index
cached_time = content_to_cache cached_time = content_to_cache
assert_equal cached_time, @response.body assert_equal cached_time, @response.body
assert_cache_exists 'hostname.com/action_caching_test' assert fragment_exist?('hostname.com/action_caching_test')
reset! reset!
get :index get :index
assert_equal cached_time, @response.body assert_equal cached_time, @response.body
end end
def test_simple_action_not_cached
get :destroy
cached_time = content_to_cache
assert_equal cached_time, @response.body
assert !fragment_exist?('hostname.com/action_caching_test/destroy')
reset!
get :destroy
assert_not_equal cached_time, @response.body
end
def test_action_cache_with_layout
get :with_layout
cached_time = content_to_cache
assert_not_equal cached_time, @response.body
assert fragment_exist?('hostname.com/action_caching_test/with_layout')
reset!
get :with_layout
assert_not_equal cached_time, @response.body
assert_equal @response.body, read_fragment('hostname.com/action_caching_test/with_layout')
end
def test_action_cache_conditional_options
@request.env['HTTP_ACCEPT'] = 'application/json'
get :index
assert !fragment_exist?('hostname.com/action_caching_test')
end
def test_action_cache_with_custom_cache_path def test_action_cache_with_custom_cache_path
get :show get :show
cached_time = content_to_cache cached_time = content_to_cache
assert_equal cached_time, @response.body assert_equal cached_time, @response.body
assert_cache_exists 'test.host/custom/show' assert fragment_exist?('test.host/custom/show')
reset! reset!
get :show get :show
@ -236,11 +282,11 @@ class ActionCacheTest < Test::Unit::TestCase
def test_action_cache_with_custom_cache_path_in_block def test_action_cache_with_custom_cache_path_in_block
get :edit get :edit
assert_cache_exists 'test.host/edit' assert fragment_exist?('test.host/edit')
reset! reset!
get :edit, :id => 1 get :edit, :id => 1
assert_cache_exists 'test.host/1;edit' assert fragment_exist?('test.host/1;edit')
end end
def test_cache_expiration def test_cache_expiration
@ -349,9 +395,12 @@ class ActionCacheTest < Test::Unit::TestCase
@request.host = 'hostname.com' @request.host = 'hostname.com'
end end
def assert_cache_exists(path) def fragment_exist?(path)
full_path = File.join(FILE_STORE_PATH, "views", path + '.cache') @controller.fragment_exist?(path)
assert File.exist?(full_path), "#{full_path.inspect} does not exist." end
def read_fragment(path)
@controller.read_fragment(path)
end end
end end
@ -391,6 +440,19 @@ class FragmentCachingTest < Test::Unit::TestCase
assert_nil @controller.read_fragment('name') assert_nil @controller.read_fragment('name')
end end
def test_fragment_exist__with_caching_enabled
@store.write('views/name', 'value')
assert @controller.fragment_exist?('name')
assert !@controller.fragment_exist?('other_name')
end
def test_fragment_exist__with_caching_disabled
ActionController::Base.perform_caching = false
@store.write('views/name', 'value')
assert !@controller.fragment_exist?('name')
assert !@controller.fragment_exist?('other_name')
end
def test_write_fragment__with_caching_enabled def test_write_fragment__with_caching_enabled
assert_nil @store.read('views/name') assert_nil @store.read('views/name')
assert_equal 'value', @controller.write_fragment('name', 'value') assert_equal 'value', @controller.write_fragment('name', 'value')
@ -435,7 +497,6 @@ class FragmentCachingTest < Test::Unit::TestCase
assert_equal 'generated till now -> ', buffer assert_equal 'generated till now -> ', buffer
end end
def test_fragment_for def test_fragment_for
@store.write('views/expensive', 'fragment content') @store.write('views/expensive', 'fragment content')
fragment_computed = false fragment_computed = false
@ -516,7 +577,7 @@ class FunctionalFragmentCachingTest < Test::Unit::TestCase
def setup def setup
ActionController::Base.perform_caching = true ActionController::Base.perform_caching = true
@store = ActiveSupport::Cache::MemoryStore.new @store = ActiveSupport::Cache::MemoryStore.new
ActionController::Base.cache_store = @store ActionController::Base.cache_store = @store
@controller = FunctionalCachingController.new @controller = FunctionalCachingController.new
@request = ActionController::TestRequest.new @request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new @response = ActionController::TestResponse.new
@ -529,17 +590,17 @@ Hello
This bit's fragment cached This bit's fragment cached
CACHED CACHED
assert_equal expected_body, @response.body assert_equal expected_body, @response.body
assert_equal "This bit's fragment cached", @store.read('views/test.host/functional_caching/fragment_cached') assert_equal "This bit's fragment cached", @store.read('views/test.host/functional_caching/fragment_cached')
end end
def test_fragment_caching_in_partials def test_fragment_caching_in_partials
get :html_fragment_cached_with_partial get :html_fragment_cached_with_partial
assert_response :success assert_response :success
assert_match /Fragment caching in a partial/, @response.body 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 "Fragment caching in a partial", @store.read('views/test.host/functional_caching/html_fragment_cached_with_partial')
end end
def test_fragment_caching_in_rjs_partials def test_fragment_caching_in_rjs_partials
xhr :get, :js_fragment_cached_with_partial xhr :get, :js_fragment_cached_with_partial
assert_response :success assert_response :success
@ -547,8 +608,3 @@ CACHED
assert_match "Fragment caching in a partial", @store.read('views/test.host/functional_caching/js_fragment_cached_with_partial') assert_match "Fragment caching in a partial", @store.read('views/test.host/functional_caching/js_fragment_cached_with_partial')
end end
end end

View file

@ -82,6 +82,7 @@ class CookieTest < Test::Unit::TestCase
def test_expiring_cookie def test_expiring_cookie
get :logout get :logout
assert_equal [ CGI::Cookie::new("name" => "user_name", "value" => "", "expires" => Time.at(0)) ], @response.headers["cookie"] assert_equal [ CGI::Cookie::new("name" => "user_name", "value" => "", "expires" => Time.at(0)) ], @response.headers["cookie"]
assert_equal CGI::Cookie::new("name" => "user_name", "value" => "", "expires" => Time.at(0)).value, []
end end
def test_cookiejar_accessor def test_cookiejar_accessor
@ -137,4 +138,9 @@ class CookieTest < Test::Unit::TestCase
cookies = CGI::Cookie.parse('return_to=http://rubyonrails.org/search?term=api&scope=all&global=true') cookies = CGI::Cookie.parse('return_to=http://rubyonrails.org/search?term=api&scope=all&global=true')
assert_equal({"return_to" => ["http://rubyonrails.org/search?term=api&scope=all&global=true"]}, cookies) assert_equal({"return_to" => ["http://rubyonrails.org/search?term=api&scope=all&global=true"]}, cookies)
end end
def test_cookies_should_not_be_split_on_values_with_newlines
cookies = CGI::Cookie.new("name" => "val", "value" => "this\nis\na\ntest")
assert cookies.size == 1
end
end end

View file

@ -7,14 +7,14 @@ class FilterParamTest < Test::Unit::TestCase
def setup def setup
@controller = FilterParamController.new @controller = FilterParamController.new
end end
def test_filter_parameters def test_filter_parameters
assert FilterParamController.respond_to?(:filter_parameter_logging) assert FilterParamController.respond_to?(:filter_parameter_logging)
assert !@controller.respond_to?(:filter_parameters) assert !@controller.respond_to?(:filter_parameters)
FilterParamController.filter_parameter_logging FilterParamController.filter_parameter_logging
assert @controller.respond_to?(:filter_parameters) assert @controller.respond_to?(:filter_parameters)
test_hashes = [[{},{},[]], test_hashes = [[{},{},[]],
[{'foo'=>nil},{'foo'=>nil},[]], [{'foo'=>nil},{'foo'=>nil},[]],
[{'foo'=>'bar'},{'foo'=>'bar'},[]], [{'foo'=>'bar'},{'foo'=>'bar'},[]],
@ -24,11 +24,11 @@ class FilterParamTest < Test::Unit::TestCase
[{'foo'=>'bar', 'baz'=>'foo'},{'foo'=>'[FILTERED]', 'baz'=>'[FILTERED]'},%w'foo baz'], [{'foo'=>'bar', 'baz'=>'foo'},{'foo'=>'[FILTERED]', 'baz'=>'[FILTERED]'},%w'foo baz'],
[{'bar'=>{'foo'=>'bar','bar'=>'foo'}},{'bar'=>{'foo'=>'[FILTERED]','bar'=>'foo'}},%w'fo'], [{'bar'=>{'foo'=>'bar','bar'=>'foo'}},{'bar'=>{'foo'=>'[FILTERED]','bar'=>'foo'}},%w'fo'],
[{'foo'=>{'foo'=>'bar','bar'=>'foo'}},{'foo'=>'[FILTERED]'},%w'f banana']] [{'foo'=>{'foo'=>'bar','bar'=>'foo'}},{'foo'=>'[FILTERED]'},%w'f banana']]
test_hashes.each do |before_filter, after_filter, filter_words| test_hashes.each do |before_filter, after_filter, filter_words|
FilterParamController.filter_parameter_logging(*filter_words) FilterParamController.filter_parameter_logging(*filter_words)
assert_equal after_filter, @controller.filter_parameters(before_filter) assert_equal after_filter, @controller.send!(:filter_parameters, before_filter)
filter_words.push('blah') filter_words.push('blah')
FilterParamController.filter_parameter_logging(*filter_words) do |key, value| FilterParamController.filter_parameter_logging(*filter_words) do |key, value|
value.reverse! if key =~ /bargain/ value.reverse! if key =~ /bargain/
@ -37,7 +37,13 @@ class FilterParamTest < Test::Unit::TestCase
before_filter['barg'] = {'bargain'=>'gain', 'blah'=>'bar', 'bar'=>{'bargain'=>{'blah'=>'foo'}}} before_filter['barg'] = {'bargain'=>'gain', 'blah'=>'bar', 'bar'=>{'bargain'=>{'blah'=>'foo'}}}
after_filter['barg'] = {'bargain'=>'niag', 'blah'=>'[FILTERED]', 'bar'=>{'bargain'=>{'blah'=>'[FILTERED]'}}} after_filter['barg'] = {'bargain'=>'niag', 'blah'=>'[FILTERED]', 'bar'=>{'bargain'=>{'blah'=>'[FILTERED]'}}}
assert_equal after_filter, @controller.filter_parameters(before_filter) assert_equal after_filter, @controller.send!(:filter_parameters, before_filter)
end end
end end
def test_filter_parameters_is_protected
FilterParamController.filter_parameter_logging(:foo)
assert !FilterParamController.action_methods.include?('filter_parameters')
assert_raise(NoMethodError) { @controller.filter_parameters([{'password' => '[FILTERED]'}]) }
end
end end

View file

@ -85,7 +85,7 @@ class HelperTest < Test::Unit::TestCase
def test_helper_block_include def test_helper_block_include
assert_equal expected_helper_methods, missing_methods assert_equal expected_helper_methods, missing_methods
assert_nothing_raised { assert_nothing_raised {
@controller_class.helper { include TestHelper } @controller_class.helper { include HelperTest::TestHelper }
} }
assert [], missing_methods assert [], missing_methods
end end

View file

@ -52,16 +52,33 @@ class MimeTypeTest < Test::Unit::TestCase
end end
def test_type_convenience_methods def test_type_convenience_methods
types = [:html, :xml, :png, :pdf, :yaml, :url_encoded_form] # Don't test Mime::ALL, since it Mime::ALL#html? == true
types = Mime::SET.to_a.map(&:to_sym).uniq - [:all]
# Remove custom Mime::Type instances set in other tests, like Mime::GIF and Mime::IPHONE
types.delete_if { |type| !Mime.const_defined?(type.to_s.upcase) }
types.each do |type| types.each do |type|
mime = Mime.const_get(type.to_s.upcase) mime = Mime.const_get(type.to_s.upcase)
assert mime.send("#{type}?"), "Mime::#{type.to_s.upcase} is not #{type}?" assert mime.send("#{type}?"), "#{mime.inspect} is not #{type}?"
(types - [type]).each { |t| assert !mime.send("#{t}?"), "Mime::#{t.to_s.upcase} is #{t}?" } (types - [type]).each { |other_type| assert !mime.send("#{other_type}?"), "#{mime.inspect} is #{other_type}?" }
end end
end end
def test_mime_all_is_html def test_mime_all_is_html
assert Mime::ALL.all?, "Mime::ALL is not all?" assert Mime::ALL.all?, "Mime::ALL is not all?"
assert Mime::ALL.html?, "Mime::ALL is not html?" assert Mime::ALL.html?, "Mime::ALL is not html?"
end end
def test_verifiable_mime_types
unverified_types = Mime::Type.unverifiable_types
all_types = Mime::SET.to_a.map(&:to_sym)
all_types.uniq!
# Remove custom Mime::Type instances set in other tests, like Mime::GIF and Mime::IPHONE
all_types.delete_if { |type| !Mime.const_defined?(type.to_s.upcase) }
unverified, verified = all_types.partition { |type| Mime::Type.unverifiable_types.include? type }
assert verified.all? { |type| Mime.const_get(type.to_s.upcase).verify_request? }, "Not all Mime Types are verified: #{verified.inspect}"
assert unverified.all? { |type| !Mime.const_get(type.to_s.upcase).verify_request? }, "Some Mime Types are verified: #{unverified.inspect}"
end
end end

View file

@ -101,19 +101,79 @@ module RequestForgeryProtectionTests
post :unsafe post :unsafe
assert_response :success assert_response :success
end end
def test_should_not_allow_post_without_token def test_should_not_allow_post_without_token
assert_raises(ActionController::InvalidAuthenticityToken) { post :index } assert_raises(ActionController::InvalidAuthenticityToken) { post :index }
end end
def test_should_not_allow_put_without_token def test_should_not_allow_put_without_token
assert_raises(ActionController::InvalidAuthenticityToken) { put :index } assert_raises(ActionController::InvalidAuthenticityToken) { put :index }
end end
def test_should_not_allow_delete_without_token def test_should_not_allow_delete_without_token
assert_raises(ActionController::InvalidAuthenticityToken) { delete :index } assert_raises(ActionController::InvalidAuthenticityToken) { delete :index }
end end
def test_should_not_allow_api_formatted_post_without_token
assert_raises(ActionController::InvalidAuthenticityToken) do
post :index, :format => 'xml'
end
end
def test_should_not_allow_api_formatted_put_without_token
assert_raises(ActionController::InvalidAuthenticityToken) do
put :index, :format => 'xml'
end
end
def test_should_not_allow_api_formatted_delete_without_token
assert_raises(ActionController::InvalidAuthenticityToken) do
delete :index, :format => 'xml'
end
end
def test_should_not_allow_api_formatted_post_sent_as_url_encoded_form_without_token
assert_raises(ActionController::InvalidAuthenticityToken) do
@request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s
post :index, :format => 'xml'
end
end
def test_should_not_allow_api_formatted_put_sent_as_url_encoded_form_without_token
assert_raises(ActionController::InvalidAuthenticityToken) do
@request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s
put :index, :format => 'xml'
end
end
def test_should_not_allow_api_formatted_delete_sent_as_url_encoded_form_without_token
assert_raises(ActionController::InvalidAuthenticityToken) do
@request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s
delete :index, :format => 'xml'
end
end
def test_should_not_allow_api_formatted_post_sent_as_multipart_form_without_token
assert_raises(ActionController::InvalidAuthenticityToken) do
@request.env['CONTENT_TYPE'] = Mime::MULTIPART_FORM.to_s
post :index, :format => 'xml'
end
end
def test_should_not_allow_api_formatted_put_sent_as_multipart_form_without_token
assert_raises(ActionController::InvalidAuthenticityToken) do
@request.env['CONTENT_TYPE'] = Mime::MULTIPART_FORM.to_s
put :index, :format => 'xml'
end
end
def test_should_not_allow_api_formatted_delete_sent_as_multipart_form_without_token
assert_raises(ActionController::InvalidAuthenticityToken) do
@request.env['CONTENT_TYPE'] = Mime::MULTIPART_FORM.to_s
delete :index, :format => 'xml'
end
end
def test_should_not_allow_xhr_post_without_token def test_should_not_allow_xhr_post_without_token
assert_raises(ActionController::InvalidAuthenticityToken) { xhr :post, :index } assert_raises(ActionController::InvalidAuthenticityToken) { xhr :post, :index }
end end
@ -142,16 +202,19 @@ module RequestForgeryProtectionTests
end end
def test_should_allow_post_with_xml def test_should_allow_post_with_xml
@request.env['CONTENT_TYPE'] = Mime::XML.to_s
post :index, :format => 'xml' post :index, :format => 'xml'
assert_response :success assert_response :success
end end
def test_should_allow_put_with_xml def test_should_allow_put_with_xml
@request.env['CONTENT_TYPE'] = Mime::XML.to_s
put :index, :format => 'xml' put :index, :format => 'xml'
assert_response :success assert_response :success
end end
def test_should_allow_delete_with_xml def test_should_allow_delete_with_xml
@request.env['CONTENT_TYPE'] = Mime::XML.to_s
delete :index, :format => 'xml' delete :index, :format => 'xml'
assert_response :success assert_response :success
end end

View file

@ -50,6 +50,13 @@ class UriReservedCharactersRoutingTest < Test::Unit::TestCase
:additional => ["add#{@segment}itional-1", "add#{@segment}itional-2"] } :additional => ["add#{@segment}itional-1", "add#{@segment}itional-2"] }
assert_equal options, @set.recognize_path("/controller/act#{@escaped}ion/var#{@escaped}iable/add#{@escaped}itional-1/add#{@escaped}itional-2") assert_equal options, @set.recognize_path("/controller/act#{@escaped}ion/var#{@escaped}iable/add#{@escaped}itional-1/add#{@escaped}itional-2")
end end
def test_route_generation_allows_passing_non_string_values_to_generated_helper
assert_equal "/controller/action/variable/1/2", @set.generate(:controller => "controller",
:action => "action",
:variable => "variable",
:additional => [1, 2])
end
end end
class LegacyRouteSetTests < Test::Unit::TestCase class LegacyRouteSetTests < Test::Unit::TestCase

View file

@ -43,7 +43,9 @@ class CookieStoreTest < Test::Unit::TestCase
{ :empty => ['BAgw--0686dcaccc01040f4bd4f35fe160afe9bc04c330', {}], { :empty => ['BAgw--0686dcaccc01040f4bd4f35fe160afe9bc04c330', {}],
:a_one => ['BAh7BiIGYWkG--5689059497d7f122a7119f171aef81dcfd807fec', { 'a' => 1 }], :a_one => ['BAh7BiIGYWkG--5689059497d7f122a7119f171aef81dcfd807fec', { 'a' => 1 }],
:typical => ['BAh7ByIMdXNlcl9pZGkBeyIKZmxhc2h7BiILbm90aWNlIgxIZXkgbm93--9d20154623b9eeea05c62ab819be0e2483238759', { 'user_id' => 123, 'flash' => { 'notice' => 'Hey now' }}], :typical => ['BAh7ByIMdXNlcl9pZGkBeyIKZmxhc2h7BiILbm90aWNlIgxIZXkgbm93--9d20154623b9eeea05c62ab819be0e2483238759', { 'user_id' => 123, 'flash' => { 'notice' => 'Hey now' }}],
:flashed => ['BAh7ByIMdXNlcl9pZGkBeyIKZmxhc2h7AA%3D%3D--bf9785a666d3c4ac09f7fe3353496b437546cfbf', { 'user_id' => 123, 'flash' => {} }] } :flashed => ['BAh7ByIMdXNlcl9pZGkBeyIKZmxhc2h7AA==--bf9785a666d3c4ac09f7fe3353496b437546cfbf', { 'user_id' => 123, 'flash' => {} }],
:double_escaped => [CGI.escape('BAh7ByIMdXNlcl9pZGkBeyIKZmxhc2h7AA%3D%3D--bf9785a666d3c4ac09f7fe3353496b437546cfbf'), { 'user_id' => 123, 'flash' => {} }] }
end end
def setup def setup
@ -101,6 +103,15 @@ class CookieStoreTest < Test::Unit::TestCase
end end
end end
def test_restores_double_encoded_cookies
set_cookie! cookie_value(:double_escaped)
new_session do |session|
session.dbman.restore
assert_equal session["user_id"], 123
assert_equal session["flash"], {}
end
end
def test_close_doesnt_write_cookie_if_data_is_blank def test_close_doesnt_write_cookie_if_data_is_blank
new_session do |session| new_session do |session|
assert_no_cookies session assert_no_cookies session
@ -241,6 +252,7 @@ class CookieStoreWithMD5DigestTest < CookieStoreTest
{ :empty => ['BAgw--0415cc0be9579b14afc22ee2d341aa21', {}], { :empty => ['BAgw--0415cc0be9579b14afc22ee2d341aa21', {}],
:a_one => ['BAh7BiIGYWkG--5a0ed962089cc6600ff44168a5d59bc8', { 'a' => 1 }], :a_one => ['BAh7BiIGYWkG--5a0ed962089cc6600ff44168a5d59bc8', { 'a' => 1 }],
:typical => ['BAh7ByIMdXNlcl9pZGkBeyIKZmxhc2h7BiILbm90aWNlIgxIZXkgbm93--f426763f6ef435b3738b493600db8d64', { 'user_id' => 123, 'flash' => { 'notice' => 'Hey now' }}], :typical => ['BAh7ByIMdXNlcl9pZGkBeyIKZmxhc2h7BiILbm90aWNlIgxIZXkgbm93--f426763f6ef435b3738b493600db8d64', { 'user_id' => 123, 'flash' => { 'notice' => 'Hey now' }}],
:flashed => ['BAh7ByIMdXNlcl9pZGkBeyIKZmxhc2h7AA%3D%3D--0af9156650dab044a53a91a4ddec2c51', { 'user_id' => 123, 'flash' => {} }] } :flashed => ['BAh7ByIMdXNlcl9pZGkBeyIKZmxhc2h7AA==--0af9156650dab044a53a91a4ddec2c51', { 'user_id' => 123, 'flash' => {} }],
:double_escaped => [CGI.escape('BAh7ByIMdXNlcl9pZGkBeyIKZmxhc2h7AA%3D%3D--0af9156650dab044a53a91a4ddec2c51'), { 'user_id' => 123, 'flash' => {} }] }
end end
end end

View file

@ -511,16 +511,26 @@ XML
FILES_DIR = File.dirname(__FILE__) + '/../fixtures/multipart' FILES_DIR = File.dirname(__FILE__) + '/../fixtures/multipart'
if RUBY_VERSION < '1.9'
READ_BINARY = 'rb'
READ_PLAIN = 'r'
else
READ_BINARY = 'rb:binary'
READ_PLAIN = 'r:binary'
end
def test_test_uploaded_file def test_test_uploaded_file
filename = 'mona_lisa.jpg' filename = 'mona_lisa.jpg'
path = "#{FILES_DIR}/#{filename}" path = "#{FILES_DIR}/#{filename}"
content_type = 'image/png' content_type = 'image/png'
expected = File.read(path)
expected.force_encoding(Encoding::BINARY) if expected.respond_to?(:force_encoding)
file = ActionController::TestUploadedFile.new(path, content_type) file = ActionController::TestUploadedFile.new(path, content_type)
assert_equal filename, file.original_filename assert_equal filename, file.original_filename
assert_equal content_type, file.content_type assert_equal content_type, file.content_type
assert_equal file.path, file.local_path assert_equal file.path, file.local_path
assert_equal File.read(path), file.read assert_equal expected, file.read
end end
def test_test_uploaded_file_with_binary def test_test_uploaded_file_with_binary
@ -529,10 +539,10 @@ XML
content_type = 'image/png' content_type = 'image/png'
binary_uploaded_file = ActionController::TestUploadedFile.new(path, content_type, :binary) binary_uploaded_file = ActionController::TestUploadedFile.new(path, content_type, :binary)
assert_equal File.open(path, 'rb').read, binary_uploaded_file.read assert_equal File.open(path, READ_BINARY).read, binary_uploaded_file.read
plain_uploaded_file = ActionController::TestUploadedFile.new(path, content_type) plain_uploaded_file = ActionController::TestUploadedFile.new(path, content_type)
assert_equal File.open(path, 'r').read, plain_uploaded_file.read assert_equal File.open(path, READ_PLAIN).read, plain_uploaded_file.read
end end
def test_fixture_file_upload_with_binary def test_fixture_file_upload_with_binary
@ -541,10 +551,10 @@ XML
content_type = 'image/jpg' content_type = 'image/jpg'
binary_file_upload = fixture_file_upload(path, content_type, :binary) binary_file_upload = fixture_file_upload(path, content_type, :binary)
assert_equal File.open(path, 'rb').read, binary_file_upload.read assert_equal File.open(path, READ_BINARY).read, binary_file_upload.read
plain_file_upload = fixture_file_upload(path, content_type) plain_file_upload = fixture_file_upload(path, content_type)
assert_equal File.open(path, 'r').read, plain_file_upload.read assert_equal File.open(path, READ_PLAIN).read, plain_file_upload.read
end end
def test_fixture_file_upload def test_fixture_file_upload

View file

@ -1,192 +0,0 @@
require 'abstract_unit'
require 'action_view/helpers/date_helper'
require 'action_view/compiled_templates'
class CompiledTemplateTests < Test::Unit::TestCase
def setup
@ct = ActionView::CompiledTemplates.new
@v = Class.new
@v.send :include, @ct
@a = './test_compile_template_a.rhtml'
@b = './test_compile_template_b.rhtml'
@s = './test_compile_template_link.rhtml'
end
def teardown
[@a, @b, @s].each do |f|
FileUtils.rm(f) if File.exist?(f) || File.symlink?(f)
end
end
attr_reader :ct, :v
def test_name_allocation
hi_world = ct.method_names['hi world']
hi_sexy = ct.method_names['hi sexy']
wish_upon_a_star = ct.method_names['I love seeing decent error messages']
assert_equal hi_world, ct.method_names['hi world']
assert_equal hi_sexy, ct.method_names['hi sexy']
assert_equal wish_upon_a_star, ct.method_names['I love seeing decent error messages']
assert_equal 3, [hi_world, hi_sexy, wish_upon_a_star].uniq.length
end
def test_wrap_source
assert_equal(
"def aliased_assignment(value)\nself.value = value\nend",
@ct.wrap_source(:aliased_assignment, [:value], 'self.value = value')
)
assert_equal(
"def simple()\nnil\nend",
@ct.wrap_source(:simple, [], 'nil')
)
end
def test_compile_source_single_method
selector = ct.compile_source('doubling method', [:a], 'a + a')
assert_equal 2, @v.new.send(selector, 1)
assert_equal 4, @v.new.send(selector, 2)
assert_equal -4, @v.new.send(selector, -2)
assert_equal 0, @v.new.send(selector, 0)
selector
end
def test_compile_source_two_method
sel1 = test_compile_source_single_method # compile the method in the other test
sel2 = ct.compile_source('doubling method', [:a, :b], 'a + b + a + b')
assert_not_equal sel1, sel2
assert_equal 2, @v.new.send(sel1, 1)
assert_equal 4, @v.new.send(sel1, 2)
assert_equal 6, @v.new.send(sel2, 1, 2)
assert_equal 32, @v.new.send(sel2, 15, 1)
end
def test_mtime
t1 = Time.now
test_compile_source_single_method
mtime = ct.mtime('doubling method', [:a])
assert mtime < Time.now
assert mtime > t1
end
uses_mocha 'test_compile_time' do
def test_compile_time
t = Time.now
File.open(@a, "w"){|f| f.puts @a}
File.open(@b, "w"){|f| f.puts @b}
# windows doesn't support symlinks (even under cygwin)
windows = (RUBY_PLATFORM =~ /win32/)
`ln -s #{@a} #{@s}` unless windows
v = ActionView::Base.new
v.base_path = '.'
v.cache_template_loading = false
ta = ActionView::Template.new(v, @a, false, {})
tb = ActionView::Template.new(v, @b, false, {})
ts = ActionView::Template.new(v, @s, false, {})
@handler_class = ActionView::Template.handler_class_for_extension(:rhtml)
@handler = @handler_class.new(v)
# All templates were created at t+1
File::Stat.any_instance.expects(:mtime).times(windows ? 2 : 3).returns(t + 1.second)
# private methods template_changed_since? and compile_template?
# should report true for all since they have not been compiled
assert @handler.send(:template_changed_since?, @a, t)
assert @handler.send(:template_changed_since?, @b, t)
assert @handler.send(:template_changed_since?, @s, t) unless windows
assert @handler.send(:compile_template?, ta)
assert @handler.send(:compile_template?, tb)
assert @handler.send(:compile_template?, ts) unless windows
# All templates are rendered at t+2
Time.expects(:now).times(windows ? 2 : 3).returns(t + 2.seconds)
v.send(:render_template, ta)
v.send(:render_template, tb)
v.send(:render_template, ts) unless windows
a_n = v.method_names[@a]
b_n = v.method_names[@b]
s_n = v.method_names[@s] unless windows
# all of the files have changed since last compile
assert @handler.compile_time[a_n] > t
assert @handler.compile_time[b_n] > t
assert @handler.compile_time[s_n] > t unless windows
# private methods template_changed_since? and compile_template?
# should report false for all since none have changed since compile
File::Stat.any_instance.expects(:mtime).times(windows ? 6 : 12).returns(t + 1.second)
assert !@handler.send(:template_changed_since?, @a, @handler.compile_time[a_n])
assert !@handler.send(:template_changed_since?, @b, @handler.compile_time[b_n])
assert !@handler.send(:template_changed_since?, @s, @handler.compile_time[s_n]) unless windows
assert !@handler.send(:compile_template?, ta)
assert !@handler.send(:compile_template?, tb)
assert !@handler.send(:compile_template?, ts) unless windows
v.send(:render_template, ta)
v.send(:render_template, tb)
v.send(:render_template, ts) unless windows
# none of the files have changed since last compile
assert @handler.compile_time[a_n] < t + 3.seconds
assert @handler.compile_time[b_n] < t + 3.seconds
assert @handler.compile_time[s_n] < t + 3.seconds unless windows
`rm #{@s}; ln -s #{@b} #{@s}` unless windows
# private methods template_changed_since? and compile_template?
# should report true for symlink since it has changed since compile
# t + 3.seconds is for the symlink
File::Stat.any_instance.expects(:mtime).times(windows ? 6 : 9).returns(
*(windows ? [ t + 1.second, t + 1.second ] :
[ t + 1.second, t + 1.second, t + 3.second ]) * 3)
assert !@handler.send(:template_changed_since?, @a, @handler.compile_time[a_n])
assert !@handler.send(:template_changed_since?, @b, @handler.compile_time[b_n])
assert @handler.send(:template_changed_since?, @s, @handler.compile_time[s_n]) unless windows
assert !@handler.send(:compile_template?, ta)
assert !@handler.send(:compile_template?, tb)
assert @handler.send(:compile_template?, ts) unless windows
# Only the symlink template gets rendered at t+3
Time.stubs(:now).returns(t + 3.seconds) unless windows
v.send(:render_template, ta)
v.send(:render_template, tb)
v.send(:render_template, ts) unless windows
# the symlink has changed since last compile
assert @handler.compile_time[a_n] < t + 3.seconds
assert @handler.compile_time[b_n] < t + 3.seconds
assert_equal @handler.compile_time[s_n], t + 3.seconds unless windows
FileUtils.touch @b
# private methods template_changed_since? and compile_template?
# should report true for symlink and file at end of symlink
# since it has changed since last compile
#
# t+4 is for @b and also for the file that @s points to, which is @b
File::Stat.any_instance.expects(:mtime).times(windows ? 6 : 12).returns(
*(windows ? [ t + 1.second, t + 4.seconds ] :
[ t + 1.second, t + 4.seconds, t + 3.second, t + 4.seconds ]) * 3)
assert !@handler.send(:template_changed_since?, @a, @handler.compile_time[a_n])
assert @handler.send(:template_changed_since?, @b, @handler.compile_time[b_n])
assert @handler.send(:template_changed_since?, @s, @handler.compile_time[s_n]) unless windows
assert !@handler.send(:compile_template?, ta)
assert @handler.send(:compile_template?, tb)
assert @handler.send(:compile_template?, ts) unless windows
Time.expects(:now).times(windows ? 1 : 2).returns(t + 5.seconds)
v.send(:render_template, ta)
v.send(:render_template, tb)
v.send(:render_template, ts) unless windows
# the file at the end of the symlink has changed since last compile
# both the symlink and the file at the end of it should be recompiled
assert @handler.compile_time[a_n] < t + 5.seconds
assert_equal @handler.compile_time[b_n], t + 5.seconds
assert_equal @handler.compile_time[s_n], t + 5.seconds unless windows
end
end
end

View file

@ -1722,6 +1722,12 @@ class DateHelperTest < ActionView::TestCase
assert_equal 2, dummy_instance_tag.send!(:default_time_from_options, :hour => 2).hour assert_equal 2, dummy_instance_tag.send!(:default_time_from_options, :hour => 2).hour
end end
end end
def test_instance_tag_default_time_from_options_handles_far_future_date
dummy_instance_tag = ActionView::Helpers::InstanceTag.new(1,2,3)
time = dummy_instance_tag.send!(:default_time_from_options, :year => 2050, :month => 2, :day => 10, :hour => 15, :min => 30, :sec => 45)
assert_equal 2050, time.year
end
end end
protected protected

View file

@ -181,6 +181,17 @@ class FormHelperTest < ActionView::TestCase
'<input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="1" /><input name="post[secret]" type="hidden" value="0" />', '<input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="1" /><input name="post[secret]" type="hidden" value="0" />',
check_box("post", "secret?") check_box("post", "secret?")
) )
@post.secret = ['0']
assert_dom_equal(
'<input id="post_secret" name="post[secret]" type="checkbox" value="1" /><input name="post[secret]" type="hidden" value="0" />',
check_box("post", "secret")
)
@post.secret = ['1']
assert_dom_equal(
'<input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="1" /><input name="post[secret]" type="hidden" value="0" />',
check_box("post", "secret")
)
end end
def test_check_box_with_explicit_checked_and_unchecked_values def test_check_box_with_explicit_checked_and_unchecked_values

View file

@ -25,8 +25,6 @@ class Author::Nested < Author; end
class PrototypeHelperBaseTest < ActionView::TestCase class PrototypeHelperBaseTest < ActionView::TestCase
tests ActionView::Helpers::PrototypeHelper
attr_accessor :template_format attr_accessor :template_format
def setup def setup
@ -86,6 +84,11 @@ class PrototypeHelperTest < PrototypeHelperBaseTest
link_to_remote("Remote outauthor", { :url => { :action => "whatnot" }, :html => { :class => "fine" } }) link_to_remote("Remote outauthor", { :url => { :action => "whatnot" }, :html => { :class => "fine" } })
end end
def test_link_to_remote_url_quote_escaping
assert_dom_equal %(<a href="#" onclick="new Ajax.Request('http://www.example.com/whatnot\\\'s', {asynchronous:true, evalScripts:true}); return false;">Remote</a>),
link_to_remote("Remote", { :url => { :action => "whatnot's" } })
end
def test_periodically_call_remote def test_periodically_call_remote
assert_dom_equal %(<script type="text/javascript">\n//<![CDATA[\nnew PeriodicalExecuter(function() {new Ajax.Updater('schremser_bier', 'http://www.example.com/mehr_bier', {asynchronous:true, evalScripts:true})}, 10)\n//]]>\n</script>), assert_dom_equal %(<script type="text/javascript">\n//<![CDATA[\nnew PeriodicalExecuter(function() {new Ajax.Updater('schremser_bier', 'http://www.example.com/mehr_bier', {asynchronous:true, evalScripts:true})}, 10)\n//]]>\n</script>),
periodically_call_remote(:update => "schremser_bier", :url => { :action => "mehr_bier" }) periodically_call_remote(:update => "schremser_bier", :url => { :action => "mehr_bier" })
@ -214,9 +217,9 @@ class PrototypeHelperTest < PrototypeHelperBaseTest
end end
def test_observe_field_using_with_option def test_observe_field_using_with_option
expected = %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Form.Element.Observer('glass', 300, function(element, value) {new Ajax.Request('http://www.example.com/check_value', {asynchronous:true, evalScripts:true, parameters:'id=' + value})})\n//]]>\n</script>) expected = %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Form.Element.Observer('glass', 300, function(element, value) {new Ajax.Request('http://www.example.com/check_value', {asynchronous:true, evalScripts:true, parameters:'id=' + encodeURIComponent(value)})})\n//]]>\n</script>)
assert_dom_equal expected, observe_field("glass", :frequency => 5.minutes, :url => { :action => "check_value" }, :with => 'id') assert_dom_equal expected, observe_field("glass", :frequency => 5.minutes, :url => { :action => "check_value" }, :with => 'id')
assert_dom_equal expected, observe_field("glass", :frequency => 5.minutes, :url => { :action => "check_value" }, :with => "'id=' + value") assert_dom_equal expected, observe_field("glass", :frequency => 5.minutes, :url => { :action => "check_value" }, :with => "'id=' + encodeURIComponent(value)")
end end
def test_observe_field_using_json_in_with_option def test_observe_field_using_json_in_with_option

View file

@ -23,9 +23,9 @@ class TextHelperTest < ActionView::TestCase
text = "A\r\n \nB\n\n\r\n\t\nC\nD".freeze text = "A\r\n \nB\n\n\r\n\t\nC\nD".freeze
assert_equal "<p>A\n<br /> \n<br />B</p>\n\n<p>\t\n<br />C\n<br />D</p>", simple_format(text) assert_equal "<p>A\n<br /> \n<br />B</p>\n\n<p>\t\n<br />C\n<br />D</p>", simple_format(text)
assert_equal %q(<p class="test">This is a classy test</p>), simple_format("This is a classy test", :class => 'test') assert_equal %q(<p class="test">This is a classy test</p>), simple_format("This is a classy test", :class => 'test')
assert_equal %Q(<p class="test">para 1</p>\n\n<p class="test">para 2</p>), simple_format("para 1\n\npara 2", :class => 'test') assert_equal %Q(<p class="test">para 1</p>\n\n<p class="test">para 2</p>), simple_format("para 1\n\npara 2", :class => 'test')
end end
def test_truncate def test_truncate
@ -41,7 +41,7 @@ class TextHelperTest < ActionView::TestCase
if RUBY_VERSION < '1.9.0' if RUBY_VERSION < '1.9.0'
def test_truncate_multibyte def test_truncate_multibyte
with_kcode 'none' do with_kcode 'none' do
assert_equal "\354\225\210\353\205\225\355...", truncate("\354\225\210\353\205\225\355\225\230\354\204\270\354\232\224", 10) assert_equal "\354\225\210\353\205\225\355...", truncate("\354\225\210\353\205\225\355\225\230\354\204\270\354\232\224", 10)
end end
with_kcode 'u' do with_kcode 'u' do
assert_equal "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 ...", assert_equal "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 ...",
@ -73,7 +73,7 @@ class TextHelperTest < ActionView::TestCase
"This is a <b>beautiful</b> morning, but also a <b>beautiful</b> day", "This is a <b>beautiful</b> morning, but also a <b>beautiful</b> day",
highlight("This is a beautiful morning, but also a beautiful day", "beautiful", '<b>\1</b>') highlight("This is a beautiful morning, but also a beautiful day", "beautiful", '<b>\1</b>')
) )
assert_equal( assert_equal(
"This text is not changed because we supplied an empty phrase", "This text is not changed because we supplied an empty phrase",
highlight("This text is not changed because we supplied an empty phrase", nil) highlight("This text is not changed because we supplied an empty phrase", nil)
@ -166,18 +166,9 @@ class TextHelperTest < ActionView::TestCase
assert_equal("2 counters", pluralize(2, "count", "counters")) assert_equal("2 counters", pluralize(2, "count", "counters"))
assert_equal("0 counters", pluralize(nil, "count", "counters")) assert_equal("0 counters", pluralize(nil, "count", "counters"))
assert_equal("2 people", pluralize(2, "person")) assert_equal("2 people", pluralize(2, "person"))
assert_equal("10 buffaloes", pluralize(10, "buffalo")) assert_equal("10 buffaloes", pluralize(10, "buffalo"))
end assert_equal("1 berry", pluralize(1, "berry"))
assert_equal("12 berries", pluralize(12, "berry"))
uses_mocha("should_just_add_s_for_pluralize_without_inflector_loaded") do
def test_should_just_add_s_for_pluralize_without_inflector_loaded
Object.expects(:const_defined?).with("Inflector").times(4).returns(false)
assert_equal("1 count", pluralize(1, "count"))
assert_equal("2 persons", pluralize(2, "person"))
assert_equal("2 personss", pluralize("2", "persons"))
assert_equal("2 counts", pluralize(2, "count"))
assert_equal("10 buffalos", pluralize(10, "buffalo"))
end
end end
def test_auto_link_parsing def test_auto_link_parsing
@ -195,6 +186,7 @@ class TextHelperTest < ActionView::TestCase
http://en.wikipedia.org/wiki/Wikipedia:Today%27s_featured_picture_%28animation%29/January_20%2C_2007 http://en.wikipedia.org/wiki/Wikipedia:Today%27s_featured_picture_%28animation%29/January_20%2C_2007
http://www.mail-archive.com/rails@lists.rubyonrails.org/ http://www.mail-archive.com/rails@lists.rubyonrails.org/
http://www.amazon.com/Testing-Equal-Sign-In-Path/ref=pd_bbs_sr_1?ie=UTF8&s=books&qid=1198861734&sr=8-1 http://www.amazon.com/Testing-Equal-Sign-In-Path/ref=pd_bbs_sr_1?ie=UTF8&s=books&qid=1198861734&sr=8-1
http://en.wikipedia.org/wiki/Sprite_(computer_graphics)
) )
urls.each do |url| urls.each do |url|
@ -271,6 +263,8 @@ class TextHelperTest < ActionView::TestCase
assert_equal email2_result, auto_link(email2_raw) assert_equal email2_result, auto_link(email2_raw)
assert_equal '', auto_link(nil) assert_equal '', auto_link(nil)
assert_equal '', auto_link('') assert_equal '', auto_link('')
assert_equal "#{link_result} #{link_result} #{link_result}", auto_link("#{link_raw} #{link_raw} #{link_raw}")
assert_equal '<a href="http://www.rubyonrails.com">Ruby On Rails</a>', auto_link('<a href="http://www.rubyonrails.com">Ruby On Rails</a>')
end end
def test_auto_link_at_eol def test_auto_link_at_eol
@ -298,7 +292,7 @@ class TextHelperTest < ActionView::TestCase
assert_equal("2", value.to_s) assert_equal("2", value.to_s)
assert_equal("3", value.to_s) assert_equal("3", value.to_s)
end end
def test_cycle_class_with_no_arguments def test_cycle_class_with_no_arguments
assert_raise(ArgumentError) { value = Cycle.new() } assert_raise(ArgumentError) { value = Cycle.new() }
end end
@ -311,11 +305,11 @@ class TextHelperTest < ActionView::TestCase
assert_equal("2", cycle("one", 2, "3")) assert_equal("2", cycle("one", 2, "3"))
assert_equal("3", cycle("one", 2, "3")) assert_equal("3", cycle("one", 2, "3"))
end end
def test_cycle_with_no_arguments def test_cycle_with_no_arguments
assert_raise(ArgumentError) { value = cycle() } assert_raise(ArgumentError) { value = cycle() }
end end
def test_cycle_resets_with_new_values def test_cycle_resets_with_new_values
assert_equal("even", cycle("even", "odd")) assert_equal("even", cycle("even", "odd"))
assert_equal("odd", cycle("even", "odd")) assert_equal("odd", cycle("even", "odd"))
@ -325,7 +319,7 @@ class TextHelperTest < ActionView::TestCase
assert_equal("3", cycle(1, 2, 3)) assert_equal("3", cycle(1, 2, 3))
assert_equal("1", cycle(1, 2, 3)) assert_equal("1", cycle(1, 2, 3))
end end
def test_named_cycles def test_named_cycles
assert_equal("1", cycle(1, 2, 3, :name => "numbers")) assert_equal("1", cycle(1, 2, 3, :name => "numbers"))
assert_equal("red", cycle("red", "blue", :name => "colors")) assert_equal("red", cycle("red", "blue", :name => "colors"))
@ -334,24 +328,24 @@ class TextHelperTest < ActionView::TestCase
assert_equal("3", cycle(1, 2, 3, :name => "numbers")) assert_equal("3", cycle(1, 2, 3, :name => "numbers"))
assert_equal("red", cycle("red", "blue", :name => "colors")) assert_equal("red", cycle("red", "blue", :name => "colors"))
end end
def test_default_named_cycle def test_default_named_cycle
assert_equal("1", cycle(1, 2, 3)) assert_equal("1", cycle(1, 2, 3))
assert_equal("2", cycle(1, 2, 3, :name => "default")) assert_equal("2", cycle(1, 2, 3, :name => "default"))
assert_equal("3", cycle(1, 2, 3)) assert_equal("3", cycle(1, 2, 3))
end end
def test_reset_cycle def test_reset_cycle
assert_equal("1", cycle(1, 2, 3)) assert_equal("1", cycle(1, 2, 3))
assert_equal("2", cycle(1, 2, 3)) assert_equal("2", cycle(1, 2, 3))
reset_cycle reset_cycle
assert_equal("1", cycle(1, 2, 3)) assert_equal("1", cycle(1, 2, 3))
end end
def test_reset_unknown_cycle def test_reset_unknown_cycle
reset_cycle("colors") reset_cycle("colors")
end end
def test_recet_named_cycle def test_recet_named_cycle
assert_equal("1", cycle(1, 2, 3, :name => "numbers")) assert_equal("1", cycle(1, 2, 3, :name => "numbers"))
assert_equal("red", cycle("red", "blue", :name => "colors")) assert_equal("red", cycle("red", "blue", :name => "colors"))
@ -361,7 +355,7 @@ class TextHelperTest < ActionView::TestCase
assert_equal("2", cycle(1, 2, 3, :name => "numbers")) assert_equal("2", cycle(1, 2, 3, :name => "numbers"))
assert_equal("red", cycle("red", "blue", :name => "colors")) assert_equal("red", cycle("red", "blue", :name => "colors"))
end end
def test_cycle_no_instance_variable_clashes def test_cycle_no_instance_variable_clashes
@cycles = %w{Specialized Fuji Giant} @cycles = %w{Specialized Fuji Giant}
assert_equal("red", cycle("red", "blue")) assert_equal("red", cycle("red", "blue"))

12
vendor/rails/activemodel/CHANGES vendored Normal file
View file

@ -0,0 +1,12 @@
Changes from extracting bits to ActiveModel
* ActiveModel::Observer#add_observer!
It has a custom hook to define after_find that should really be in a
ActiveRecord::Observer subclass:
def add_observer!(klass)
klass.add_observer(self)
klass.class_eval 'def after_find() end' unless
klass.respond_to?(:after_find)
end

21
vendor/rails/activemodel/README vendored Normal file
View file

@ -0,0 +1,21 @@
Active Model
==============
Totally experimental library that aims to extract common model mixins from
ActiveRecord for use in ActiveResource (and other similar libraries).
This is in a very rough state (no autotest or spec rake tasks set up yet),
so please excuse the mess.
Here's what I plan to extract:
* ActiveModel::Observing
* ActiveModel::Callbacks
* ActiveModel::Validations
# for ActiveResource params and ActiveRecord options
* ActiveModel::Scoping
# to_json, to_xml, etc
* ActiveModel::Serialization
I'm trying to keep ActiveRecord compatibility where possible, but I'm
annotating the spots where I'm diverging a bit.

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