Merge branch 'master' of git://github.com/rails/rails

This commit is contained in:
Rizwan Reza
2010-05-17 02:40:15 +04:30
125 changed files with 1361 additions and 479 deletions

View File

@@ -9,6 +9,7 @@ gem "mocha", ">= 0.9.8"
group :mri do
gem 'json'
gem 'yajl-ruby'
gem "nokogiri", ">= 1.4.0"
if RUBY_VERSION < '1.9'
gem "system_timer"
@@ -19,7 +20,7 @@ group :mri do
end
# AR
gem "sqlite3-ruby", ">= 1.2.5", :require => 'sqlite3'
gem "sqlite3-ruby", "= 1.3.0.beta.2", :require => 'sqlite3'
group :db do
gem "pg", ">= 0.9.0"

View File

@@ -1,3 +1,7 @@
* Changed encoding behaviour of mail, so updated tests in actionmailer and bumped mail version to 2.2.1 [ML]
* Added ability to pass Proc objects to the defaults hash [ML]
*Rails 3.0.0 [beta 3] (April 13th, 2010)*
* Removed all quoting.rb type files from ActionMailer and put Mail 2.2.0 in instead [ML]

View File

@@ -20,6 +20,6 @@ Gem::Specification.new do |s|
s.has_rdoc = true
s.add_dependency('actionpack', version)
s.add_dependency('mail', '~> 2.2.0')
s.add_dependency('mail', '~> 2.2.1')
s.add_dependency('text-format', '~> 1.0.0')
end

View File

@@ -3,6 +3,7 @@ require 'action_mailer/tmail_compat'
require 'action_mailer/collector'
require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/proc'
module ActionMailer #:nodoc:
# Action Mailer allows you to send email from your application using a mailer model and views.
@@ -22,16 +23,16 @@ module ActionMailer #:nodoc:
# class Notifier < ActionMailer::Base
# default :from => 'no-reply@example.com',
# :return_path => 'system@example.com'
#
#
# def welcome(recipient)
# @account = recipient
# mail(:to => recipient.email_address_with_name,
# :bcc => ["bcc@example.com", "Order Watcher <watcher@example.com>"])
# end
# end
#
#
# Within the mailer method, you have access to the following methods:
#
#
# * <tt>attachments[]=</tt> - Allows you to add attachments to your email in an intuitive
# manner; <tt>attachments['filename.png'] = File.read('path/to/filename.png')</tt>
#
@@ -46,16 +47,16 @@ module ActionMailer #:nodoc:
# as <tt>headers({'X-No-Spam' => 'True', 'In-Reply-To' => '1234@message.id'})</tt>
#
# * <tt>mail</tt> - Allows you to specify your email to send.
#
#
# The hash passed to the mail method allows you to specify any header that a Mail::Message
# will accept (any valid Email header including optional fields).
#
# The mail method, if not passed a block, will inspect your views and send all the views with
# the same name as the method, so the above action would send the +welcome.text.plain.erb+ view
# file as well as the +welcome.text.html.erb+ view file in a +multipart/alternative+ email.
#
#
# If you want to explicitly render only certain templates, pass a block:
#
#
# mail(:to => user.emai) do |format|
# format.text
# format.html
@@ -79,7 +80,7 @@ module ActionMailer #:nodoc:
#
# 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.
#
#
# 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
# <tt>app/views/notifier/signup_notification.text.plain.erb</tt> would be used to generate the email.
@@ -104,7 +105,7 @@ module ActionMailer #:nodoc:
#
# = Generating URLs
#
# URLs can be generated in mailer views using <tt>url_for</tt> or named routes. Unlike controllers from
# 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, so you'll need
# to provide all of the details needed to generate a URL.
#
@@ -176,7 +177,7 @@ module ActionMailer #:nodoc:
# mail(:to => recipient, :subject => "New account information")
# end
# end
#
#
# Which will (if it had both a <tt>welcome.text.plain.erb</tt> and <tt>welcome.text.html.erb</tt>
# tempalte in the view directory), send a complete <tt>multipart/mixed</tt> email with two parts,
# the first part being a <tt>multipart/alternative</tt> with the text and HTML email parts inside,
@@ -184,71 +185,71 @@ module ActionMailer #:nodoc:
# with the filename +free_book.pdf+.
#
# = Observing and Intercepting Mails
#
#
# Action Mailer provides hooks into the Mail observer and interceptor methods. These allow you to
# register objects that are called during the mail delivery life cycle.
#
#
# An observer object must implement the <tt>:delivered_email(message)</tt> method which will be
# called once for every email sent after the email has been sent.
#
#
# An interceptor object must implement the <tt>:delivering_email(message)</tt> method which will be
# called before the email is sent, allowing you to make modifications to the email before it hits
# the delivery agents. Your object should make and needed modifications directly to the passed
# in Mail::Message instance.
#
# = Default Hash
#
#
# Action Mailer provides some intelligent defaults for your emails, these are usually specified in a
# default method inside the class definition:
#
#
# class Notifier < ActionMailer::Base
# default :sender => 'system@example.com'
# end
#
#
# You can pass in any header value that a <tt>Mail::Message</tt>, out of the box, <tt>ActionMailer::Base</tt>
# sets the following:
#
#
# * <tt>:mime_version => "1.0"</tt>
# * <tt>:charset => "UTF-8",</tt>
# * <tt>:content_type => "text/plain",</tt>
# * <tt>:parts_order => [ "text/plain", "text/enriched", "text/html" ]</tt>
#
#
# <tt>parts_order</tt> and <tt>charset</tt> are not actually valid <tt>Mail::Message</tt> header fields,
# but Action Mailer translates them appropriately and sets the correct values.
#
#
# As you can pass in any header, you need to either quote the header as a string, or pass it in as
# an underscorised symbol, so the following will work:
#
#
# class Notifier < ActionMailer::Base
# default 'Content-Transfer-Encoding' => '7bit',
# :content_description => 'This is a description'
# end
#
#
# Finally, Action Mailer also supports passing <tt>Proc</tt> objects into the default hash, so you
# can define methods that evaluate as the message is being generated:
#
#
# class Notifier < ActionMailer::Base
# default 'X-Special-Header' => Proc.new { my_method }
#
#
# private
#
#
# def my_method
# 'some complex call'
# end
# end
#
#
# Note that the proc is evaluated right at the start of the mail message generation, so if you
# set something in the defaults using a proc, and then set the same thing inside of your
# set something in the defaults using a proc, and then set the same thing inside of your
# mailer method, it will get over written by the mailer method.
#
#
# = Configuration options
#
# These options are specified on the class level, like
# These options are specified on the class level, like
# <tt>ActionMailer::Base.template_root = "/my/templates"</tt>
#
# * <tt>default</tt> - You can pass this in at a class level as well as within the class itself as
# per the above section.
#
#
# * <tt>logger</tt> - the logger is used for generating information on the mailing run if available.
# Can be set to nil for no logging. Compatible with both Ruby's own Logger and Log4r loggers.
#
@@ -288,16 +289,16 @@ module ActionMailer #:nodoc:
# * <tt>deliveries</tt> - Keeps an array of all the emails sent out through the Action Mailer with
# <tt>delivery_method :test</tt>. Most useful for unit and functional testing.
#
# * <tt>default_charset</tt> - This is now deprecated, use the +default+ method above to
# * <tt>default_charset</tt> - This is now deprecated, use the +default+ method above to
# set the default +:charset+.
#
# * <tt>default_content_type</tt> - This is now deprecated, use the +default+ method above
# * <tt>default_content_type</tt> - This is now deprecated, use the +default+ method above
# to set the default +:content_type+.
#
# * <tt>default_mime_version</tt> - This is now deprecated, use the +default+ method above
# * <tt>default_mime_version</tt> - This is now deprecated, use the +default+ method above
# to set the default +:mime_version+.
#
# * <tt>default_implicit_parts_order</tt> - This is now deprecated, use the +default+ method above
# * <tt>default_implicit_parts_order</tt> - This is now deprecated, use the +default+ method above
# to set the default +:parts_order+. Parts Order is used 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.
@@ -315,7 +316,7 @@ module ActionMailer #:nodoc:
include ActionMailer::OldApi
include ActionMailer::DeprecatedApi
delegate :register_observer, :to => Mail
delegate :register_interceptor, :to => Mail
@@ -418,17 +419,17 @@ module ActionMailer #:nodoc:
# Allows you to pass random and unusual headers to the new +Mail::Message+ object
# which will add them to itself.
#
#
# headers['X-Special-Domain-Specific-Header'] = "SecretValue"
#
#
# You can also pass a hash into headers of header field names and values, which
# will then be set on the Mail::Message object:
#
#
# headers 'X-Special-Domain-Specific-Header' => "SecretValue",
# 'In-Reply-To' => incoming.message_id
#
#
# The resulting Mail::Message will have the following in it's header:
#
#
# X-Special-Domain-Specific-Header: SecretValue
def headers(args=nil)
if args
@@ -439,45 +440,45 @@ module ActionMailer #:nodoc:
end
# Allows you to add attachments to an email, like so:
#
#
# mail.attachments['filename.jpg'] = File.read('/path/to/filename.jpg')
#
#
# If you do this, then Mail will take the file name and work out the mime type
# set the Content-Type, Content-Disposition, Content-Transfer-Encoding and
# set the Content-Type, Content-Disposition, Content-Transfer-Encoding and
# base64 encode the contents of the attachment all for you.
#
#
# You can also specify overrides if you want by passing a hash instead of a string:
#
#
# mail.attachments['filename.jpg'] = {:mime_type => 'application/x-gzip',
# :content => File.read('/path/to/filename.jpg')}
#
#
# If you want to use a different encoding than Base64, you can pass an encoding in,
# but then it is up to you to pass in the content pre-encoded, and don't expect
# Mail to know how to decode this data:
#
#
# file_content = SpecialEncode(File.read('/path/to/filename.jpg'))
# mail.attachments['filename.jpg'] = {:mime_type => 'application/x-gzip',
# :encoding => 'SpecialEncoding',
# :content => file_content }
#
#
# You can also search for specific attachments:
#
#
# # By Filename
# mail.attachments['filename.jpg'] #=> Mail::Part object or nil
#
#
# # or by index
# mail.attachments[0] #=> Mail::Part (first attachment)
#
#
def attachments
@_message.attachments
end
# The main method that creates the message and renders the email templates. There are
# two ways to call this method, with a block, or without a block.
#
#
# Both methods accept a headers hash. This hash allows you to specify the most used headers
# in an email message, these are:
#
#
# * <tt>:subject</tt> - The subject of the message, if this is omitted, Action Mailer will
# ask the Rails I18n class for a translated <tt>:subject</tt> in the scope of
# <tt>[:actionmailer, mailer_scope, action_name]</tt> or if this is missing, will translate the
@@ -491,25 +492,25 @@ module ActionMailer #:nodoc:
# addresses, or an array of addresses.
# * <tt>:reply_to</tt> - Who to set the Reply-To header of the email to.
# * <tt>:date</tt> - The date to say the email was sent on.
#
# You can set default values for any of the above headers (except :date) by using the <tt>default</tt>
#
# You can set default values for any of the above headers (except :date) by using the <tt>default</tt>
# class method:
#
#
# class Notifier < ActionMailer::Base
# self.default :from => 'no-reply@test.lindsaar.net',
# :bcc => 'email_logger@test.lindsaar.net',
# :reply_to => 'bounces@test.lindsaar.net'
# end
#
#
# If you need other headers not listed above, use the <tt>headers['name'] = value</tt> method.
#
# When a <tt>:return_path</tt> is specified as header, that value will be used as the 'envelope from'
# address for the Mail message. Setting this is useful when you want delivery notifications
# sent to a different address than the one in <tt>:from</tt>. Mail will actually use the
# sent to a different address than the one in <tt>:from</tt>. Mail will actually use the
# <tt>:return_path</tt> in preference to the <tt>:sender</tt> in preference to the <tt>:from</tt>
# field for the 'envelope from' value.
#
# If you do not pass a block to the +mail+ method, it will find all templates in the
# If you do not pass a block to the +mail+ method, it will find all templates in the
# view paths using by default the mailer name and the method name that it is being
# called from, it will then create parts for each of these templates intelligently,
# making educated guesses on correct content type and sequence, and return a fully
@@ -533,19 +534,19 @@ module ActionMailer #:nodoc:
# And now it will look for all templates at "app/views/notifications" with name "another".
#
# If you do pass a block, you can render specific templates of your choice:
#
#
# mail(:to => 'mikel@test.lindsaar.net') do |format|
# format.text
# format.html
# end
#
#
# You can even render text directly without using a template:
#
#
# mail(:to => 'mikel@test.lindsaar.net') do |format|
# format.text { render :text => "Hello Mikel!" }
# format.html { render :text => "<h1>Hello Mikel!</h1>" }
# end
#
#
# Which will render a <tt>multipart/alternative</tt> email with <tt>text/plain</tt> and
# <tt>text/html</tt> parts.
#
@@ -570,7 +571,7 @@ module ActionMailer #:nodoc:
default_values = self.class.default.merge(self.class.default) do |k,v|
v.respond_to?(:call) ? v.bind(self).call : v
end
# Handle defaults
headers = headers.reverse_merge(default_values)
headers[:subject] ||= default_i18n_subject
@@ -684,6 +685,28 @@ module ActionMailer #:nodoc:
container.add_part(part)
end
module DeprecatedUrlOptions
def default_url_options
deprecated_url_options
end
def default_url_options=(val)
deprecated_url_options
end
def deprecated_url_options
raise "You can no longer call ActionMailer::Base.default_url_options " \
"directly. You need to set config.action_mailer.default_url_options. " \
"If you are using ActionMailer standalone, you need to include the " \
"url_helpers of a router directly."
end
end
# This module will complain if the user tries to set default_url_options
# directly instead of through the config object. In ActionMailer's Railtie,
# we include the url_helpers of the router, which will override this module
extend DeprecatedUrlOptions
ActiveSupport.run_load_hooks(:action_mailer, self)
end
end

View File

@@ -5,10 +5,6 @@ module ActionMailer
class Railtie < Rails::Railtie
config.action_mailer = ActiveSupport::OrderedOptions.new
initializer "action_mailer.url_for", :before => :load_environment_config do |app|
ActiveSupport.on_load(:action_mailer) { include app.routes.url_helpers }
end
require "action_mailer/railties/log_subscriber"
log_subscriber :action_mailer, ActionMailer::Railties::LogSubscriber.new
@@ -18,6 +14,8 @@ module ActionMailer
initializer "action_mailer.set_configs" do |app|
ActiveSupport.on_load(:action_mailer) do
include app.routes.url_helpers
app.config.action_mailer.each do |k,v|
send "#{k}=", v
end

View File

@@ -674,7 +674,7 @@ The body
EOF
mail = Mail.new(msg)
assert_equal "testing testing \326\244", mail.subject
assert_equal "Subject: testing testing =?UTF-8?Q?_=D6=A4=?=\r\n", mail[:subject].encoded
assert_equal "Subject: =?UTF-8?Q?testing_testing_=D6=A4?=\r\n", mail[:subject].encoded
end
def test_unquote_7bit_subject
@@ -863,7 +863,7 @@ EOF
def test_multipart_with_utf8_subject
mail = TestMailer.multipart_with_utf8_subject(@recipient)
regex = Regexp.escape('Subject: Foo =?UTF-8?Q?=C3=A1=C3=AB=C3=B4=?= =?UTF-8?Q?_=C3=AE=C3=BC=?=')
regex = Regexp.escape('Subject: =?UTF-8?Q?Foo_=C3=A1=C3=AB=C3=B4_=C3=AE=C3=BC?=')
assert_match(/#{regex}/, mail.encoded)
string = "Foo áëô îü"
assert_match(string, mail.subject)
@@ -871,7 +871,7 @@ EOF
def test_implicitly_multipart_with_utf8
mail = TestMailer.implicitly_multipart_with_utf8
regex = Regexp.escape('Subject: Foo =?UTF-8?Q?=C3=A1=C3=AB=C3=B4=?= =?UTF-8?Q?_=C3=AE=C3=BC=?=')
regex = Regexp.escape('Subject: =?UTF-8?Q?Foo_=C3=A1=C3=AB=C3=B4_=C3=AE=C3=BC?=')
assert_match(/#{regex}/, mail.encoded)
string = "Foo áëô îü"
assert_match(string, mail.subject)

View File

@@ -88,23 +88,4 @@ task :lines do
end
puts "Total: Lines #{total_lines}, LOC #{total_codelines}"
end
# Publishing ------------------------------------------------------
task :update_scriptaculous do
for js in %w( controls dragdrop effects )
system("svn export --force http://dev.rubyonrails.org/svn/rails/spinoffs/scriptaculous/src/#{js}.js #{File.dirname(__FILE__)}/lib/action_view/helpers/javascripts/#{js}.js")
end
end
desc "Updates actionpack to the latest version of the javascript spinoffs"
task :update_js => [ :update_scriptaculous ]
# Publishing ------------------------------------------------------
desc "Publish the API documentation"
task :pdoc => [:rdoc] do
require 'rake/contrib/sshpublisher'
Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/ap", "doc").upload
end

View File

@@ -49,6 +49,16 @@ module ActionDispatch
def [](k)
@flash[k]
end
# Convenience accessor for flash.now[:alert]=
def alert=(message)
self[:alert] = message
end
# Convenience accessor for flash.now[:notice]=
def notice=(message)
self[:notice] = message
end
end
class FlashHash < Hash

View File

@@ -177,7 +177,7 @@ module ActionDispatch
if key.blank?
raise ArgumentError, 'A key is required to write a ' +
'cookie containing the session data. Use ' +
'config.action_controller.session_store :cookie_store, { :key => ' +
'config.session_store :cookie_store, { :key => ' +
'"_myapp_session" } in config/application.rb'
end
end

View File

@@ -133,14 +133,10 @@ module ActionDispatch
return unless logger
ActiveSupport::Deprecation.silence do
if ActionView::Template::Error === exception
logger.fatal(exception.to_s)
else
logger.fatal(
"\n#{exception.class} (#{exception.message}):\n " +
clean_backtrace(exception).join("\n ") + "\n\n"
)
end
message = "\n#{exception.class} (#{exception.message}):\n"
message << exception.annoted_source_code if exception.respond_to?(:annoted_source_code)
message << exception.backtrace.join("\n ")
logger.fatal("#{message}\n\n")
end
end

View File

@@ -298,10 +298,14 @@ module ActionDispatch
# found one but expecting two.
message ||= content_mismatch if matches.empty?
# Test minimum/maximum occurrence.
min, max = equals[:minimum], equals[:maximum]
message = message || %(Expected #{count_description(min, max)} matching "#{selector.to_s}", found #{matches.size}.)
assert matches.size >= min, message if min
assert matches.size <= max, message if max
min, max, count = equals[:minimum], equals[:maximum], equals[:count]
message = message || %(Expected #{count_description(min, max, count)} matching "#{selector.to_s}", found #{matches.size}.)
if count
assert matches.size == count, message
else
assert matches.size >= min, message if min
assert matches.size <= max, message if max
end
# If a block is given call that block. Set @selected to allow
# nested assert_select, which can be nested several levels deep.
@@ -318,11 +322,13 @@ module ActionDispatch
matches
end
def count_description(min, max) #:nodoc:
def count_description(min, max, count) #:nodoc:
pluralize = lambda {|word, quantity| word << (quantity == 1 ? '' : 's')}
if min && max && (max != min)
"between #{min} and #{max} elements"
elsif min && max && max == min && count
"exactly #{count} #{pluralize['element', min]}"
elsif min && !(min == 1 && max == 1)
"at least #{min} #{pluralize['element', min]}"
elsif max

View File

@@ -620,7 +620,7 @@ module ActionView
options.symbolize_keys!
src = options[:src] = path_to_image(source)
options[:alt] ||= File.basename(src, '.*').capitalize
options[:alt] = options.fetch(:alt){ File.basename(src, '.*').capitalize }
if size = options.delete(:size)
options[:width], options[:height] = size.split("x") if size =~ %r{^\d+x\d+$}

View File

@@ -573,8 +573,19 @@ module ActionView
# label(:post, :privacy, "Public Post", :value => "public")
# # => <label for="post_privacy_public">Public Post</label>
#
def label(object_name, method, text = nil, options = {})
InstanceTag.new(object_name, method, self, options.delete(:object)).to_label_tag(text, options)
# label(:post, :terms) do
# 'Accept <a href="/terms">Terms</a>.'
# end
def label(object_name, method, content_or_options = nil, options = nil, &block)
if block_given?
options = content_or_options if content_or_options.is_a?(Hash)
text = nil
else
text = content_or_options
end
options ||= {}
InstanceTag.new(object_name, method, self, options.delete(:object)).to_label_tag(text, options, &block)
end
# Returns an input tag of the "text" type tailored for accessing a specified attribute (identified by +method+) on an object
@@ -823,7 +834,7 @@ module ActionView
module InstanceTagMethods #:nodoc:
extend ActiveSupport::Concern
include Helpers::TagHelper, Helpers::FormTagHelper
include Helpers::CaptureHelper, Context, Helpers::TagHelper, Helpers::FormTagHelper
attr_reader :method_name, :object_name
@@ -844,28 +855,38 @@ module ActionView
end
end
def to_label_tag(text = nil, options = {})
def to_label_tag(text = nil, options = {}, &block)
options = options.stringify_keys
tag_value = options.delete("value")
name_and_id = options.dup
name_and_id["id"] = name_and_id["for"]
if name_and_id["for"]
name_and_id["id"] = name_and_id["for"]
else
name_and_id.delete("id")
end
add_default_name_and_id_for_value(tag_value, name_and_id)
options.delete("index")
options["for"] ||= name_and_id["id"]
content = if text.blank?
I18n.t("helpers.label.#{object_name}.#{method_name}", :default => "").presence
if block_given?
label_tag(name_and_id["id"], options, &block)
else
text.to_s
content = if text.blank?
I18n.t("helpers.label.#{object_name}.#{method_name}", :default => "").presence
else
text.to_s
end
content ||= if object && object.class.respond_to?(:human_attribute_name)
object.class.human_attribute_name(method_name)
end
content ||= method_name.humanize
label_tag(name_and_id["id"], content, options)
end
content ||= if object && object.class.respond_to?(:human_attribute_name)
object.class.human_attribute_name(method_name)
end
content ||= method_name.humanize
label_tag(name_and_id["id"], content, options)
end
def to_input_field_tag(field_type, options = {})
@@ -1012,7 +1033,7 @@ module ActionView
pretty_tag_value = tag_value.to_s.gsub(/\s/, "_").gsub(/\W/, "").downcase
specified_id = options["id"]
add_default_name_and_id(options)
options["id"] += "_#{pretty_tag_value}" unless specified_id
options["id"] += "_#{pretty_tag_value}" if specified_id.blank? && options["id"].present?
else
add_default_name_and_id(options)
end
@@ -1021,14 +1042,14 @@ module ActionView
def add_default_name_and_id(options)
if options.has_key?("index")
options["name"] ||= tag_name_with_index(options["index"])
options["id"] ||= tag_id_with_index(options["index"])
options["id"] = options.fetch("id", tag_id_with_index(options["index"]))
options.delete("index")
elsif defined?(@auto_index)
options["name"] ||= tag_name_with_index(@auto_index)
options["id"] ||= tag_id_with_index(@auto_index)
options["id"] = options.fetch("id", tag_id_with_index(@auto_index))
else
options["name"] ||= tag_name + (options.has_key?('multiple') ? '[]' : '')
options["id"] ||= tag_id
options["id"] = options.fetch("id", tag_id)
end
end
@@ -1137,8 +1158,8 @@ module ActionView
@template.fields_for(name, *args, &block)
end
def label(method, text = nil, options = {})
@template.label(@object_name, method, text, objectify_options(options))
def label(method, text = nil, options = {}, &block)
@template.label(@object_name, method, text, objectify_options(options), &block)
end
def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")

View File

@@ -270,6 +270,15 @@ module ActionView
# options_for_select([ "VISA", "MasterCard", "Discover" ], ["VISA", "Discover"])
# <option selected="selected">VISA</option>\n<option>MasterCard</option>\n<option selected="selected">Discover</option>
#
# You can optionally provide html attributes as the last element of the array.
#
# Examples:
# options_for_select([ "Denmark", ["USA", {:class=>'bold'}], "Sweden" ], ["USA", "Sweden"])
# <option value="Denmark">Denmark</option>\n<option value="USA" class="bold" selected="selected">USA</option>\n<option value="Sweden" selected="selected">Sweden</option>
#
# options_for_select([["Dollar", "$", {:class=>"bold"}], ["Kroner", "DKK", {:onclick => "alert('HI');"}]])
# <option value="$" class="bold">Dollar</option>\n<option value="DKK" onclick="alert('HI');">Kroner</option>
#
# If you wish to specify disabled option tags, set +selected+ to be a hash, with <tt>:disabled</tt> being either a value
# or array of values to be disabled. In this case, you can use <tt>:selected</tt> to specify selected option tags.
#
@@ -291,10 +300,11 @@ module ActionView
selected, disabled = extract_selected_and_disabled(selected)
options_for_select = container.inject([]) do |options, element|
html_attributes = option_html_attributes(element)
text, value = option_text_and_value(element)
selected_attribute = ' selected="selected"' if option_value_selected?(value, selected)
disabled_attribute = ' disabled="disabled"' if disabled && option_value_selected?(value, disabled)
options << %(<option value="#{html_escape(value.to_s)}"#{selected_attribute}#{disabled_attribute}>#{html_escape(text.to_s)}</option>)
options << %(<option value="#{html_escape(value.to_s)}"#{selected_attribute}#{disabled_attribute}#{html_attributes}>#{html_escape(text.to_s)}</option>)
end
options_for_select.join("\n").html_safe
@@ -486,9 +496,22 @@ module ActionView
end
private
def option_html_attributes(element)
return "" unless Array === element
html_attributes = []
element.select { |e| Hash === e }.reduce({}, :merge).each do |k, v|
html_attributes << " #{k}=\"#{html_escape(v.to_s)}\""
end
html_attributes.join
end
def option_text_and_value(option)
# Options are [text, value] pairs or strings used for both.
if !option.is_a?(String) and option.respond_to?(:first) and option.respond_to?(:last)
case
when Array === option
option = option.reject { |e| Hash === e }
[option.first, option.last]
when !option.is_a?(String) && option.respond_to?(:first) && option.respond_to?(:last)
[option.first, option.last]
else
[option, option]

View File

@@ -142,7 +142,7 @@ module ActionView
tag :input, { "type" => "text", "name" => name, "id" => sanitize_to_id(name), "value" => value }.update(options.stringify_keys)
end
# Creates a label field
# Creates a label element. Accepts a block.
#
# ==== Options
# * Creates standard HTML attributes for the tag.
@@ -156,8 +156,12 @@ module ActionView
#
# label_tag 'name', nil, :class => 'small_label'
# # => <label for="name" class="small_label">Name</label>
def label_tag(name, text = nil, options = {})
content_tag :label, text || name.to_s.humanize, { "for" => sanitize_to_id(name) }.update(options.stringify_keys)
def label_tag(name = nil, content_or_options = nil, options = nil, &block)
options = content_or_options if block_given? && content_or_options.is_a?(Hash)
options ||= {}
options.stringify_keys!
options["for"] = sanitize_to_id(name) unless name.blank? || options.has_key?("for")
content_tag :label, content_or_options || name.to_s.humanize, options, &block
end
# Creates a hidden form input field used to transmit data that would be lost due to HTTP's statelessness or

View File

@@ -241,15 +241,21 @@ module ActionView
end
def collection_with_template(template = @template)
segments, locals, as, template = [], @locals, @options[:as] || @template.variable_name, @template
segments, locals, template = [], @locals, @template
counter_name = template.counter_name
locals[counter_name] = -1
if @options[:as]
as = @options[:as]
counter = "#{as}_counter".to_sym
else
as = template.variable_name
counter = template.counter_name
end
locals[counter] = -1
@collection.each do |object|
locals[counter_name] += 1
locals[counter] += 1
locals[as] = object
segments << template.render(@view, locals)
end
@@ -257,13 +263,18 @@ module ActionView
end
def collection_without_template(collection_paths = @collection_paths)
segments, locals, as = [], @locals, @options[:as]
index, template = -1, nil
segments, locals = [], @locals
index, template = -1, nil
if @options[:as]
as = @options[:as]
counter = "#{as}_counter"
end
@collection.each_with_index do |object, i|
template = find_template(collection_paths[i])
locals[template.counter_name] = (index += 1)
locals[as || template.variable_name] = object
locals[counter || template.counter_name] = (index += 1)
segments << template.render(@view, locals)
end

View File

@@ -84,9 +84,8 @@ module ActionView
end
end
def to_s
"\n#{self.class} (#{message}) #{source_location}:\n\n" +
"#{source_extract(4)}\n #{backtrace.join("\n ")}\n\n"
def annoted_source_code
source_extract(4)
end
private

View File

@@ -28,7 +28,7 @@ module ActionView
src << "@output_buffer.safe_concat('" << escape_text(text) << "');"
end
BLOCK_EXPR = /(do|\{)(\s*\|[^|]*\|)?\s*\Z/
BLOCK_EXPR = /\s+(do|\{)(\s*\|[^|]*\|)?\s*\Z/
def add_expr_literal(src, code)
if code =~ BLOCK_EXPR

View File

@@ -80,10 +80,15 @@ class AssertSelectTest < ActionController::TestCase
def test_assert_select
render_html %Q{<div id="1"></div><div id="2"></div>}
assert_select "div", 2
assert_failure(/Expected at least 3 elements matching \"div\", found 2/) { assert_select "div", 3 }
assert_failure(/Expected at least 1 element matching \"p\", found 0/) { assert_select "p" }
end
def test_equality_integer
render_html %Q{<div id="1"></div><div id="2"></div>}
assert_failure(/Expected exactly 3 elements matching \"div\", found 2/) { assert_select "div", 3 }
assert_failure(/Expected exactly 0 elements matching \"div\", found 2/) { assert_select "div", 0 }
end
def test_equality_true_false
render_html %Q{<div id="1"></div><div id="2"></div>}
assert_nothing_raised { assert_select "div" }
@@ -94,6 +99,11 @@ class AssertSelectTest < ActionController::TestCase
assert_nothing_raised { assert_select "p", false }
end
def test_equality_false_message
render_html %Q{<div id="1"></div><div id="2"></div>}
assert_failure(/Expected exactly 0 elements matching \"div\", found 2/) { assert_select "div", false }
end
def test_equality_string_and_regexp
render_html %Q{<div id="1">foo</div><div id="2">foo</div>}
assert_nothing_raised { assert_select "div", "foo" }
@@ -128,7 +138,7 @@ class AssertSelectTest < ActionController::TestCase
def test_counts
render_html %Q{<div id="1">foo</div><div id="2">foo</div>}
assert_nothing_raised { assert_select "div", 2 }
assert_failure(/Expected at least 3 elements matching \"div\", found 2/) do
assert_failure(/Expected exactly 3 elements matching \"div\", found 2/) do
assert_select "div", 3
end
assert_nothing_raised { assert_select "div", 1..2 }
@@ -136,7 +146,7 @@ class AssertSelectTest < ActionController::TestCase
assert_select "div", 3..4
end
assert_nothing_raised { assert_select "div", :count=>2 }
assert_failure(/Expected at least 3 elements matching \"div\", found 2/) do
assert_failure(/Expected exactly 3 elements matching \"div\", found 2/) do
assert_select "div", :count=>3
end
assert_nothing_raised { assert_select "div", :minimum=>1 }
@@ -201,7 +211,7 @@ class AssertSelectTest < ActionController::TestCase
assert_nothing_raised { assert_select "div", "foo" }
assert_nothing_raised { assert_select "div", "bar" }
assert_nothing_raised { assert_select "div", /\w*/ }
assert_nothing_raised { assert_select "div", /\w*/, :count=>2 }
assert_nothing_raised { assert_select "div", :text => /\w*/, :count=>2 }
assert_raise(Assertion) { assert_select "div", :text=>"foo", :count=>2 }
assert_nothing_raised { assert_select "div", :html=>"<span>bar</span>" }
assert_nothing_raised { assert_select "div", :html=>"<span>bar</span>" }
@@ -266,8 +276,8 @@ class AssertSelectTest < ActionController::TestCase
def test_css_select
render_html %Q{<div id="1"></div><div id="2"></div>}
assert 2, css_select("div").size
assert 0, css_select("p").size
assert_equal 2, css_select("div").size
assert_equal 0, css_select("p").size
end
def test_nested_css_select

View File

@@ -61,6 +61,11 @@ class CaptureTest < ActionController::TestCase
assert_equal expected_content_for_output, @response.body
end
def test_proper_block_detection
@todo = "some todo"
get :proper_block_detection
end
private
def expected_content_for_output
"<title>Putting stuff in the title!</title>\n\nGreat stuff!"

View File

@@ -81,6 +81,16 @@ class FlashTest < ActionController::TestCase
redirect_to '/somewhere', :notice => "Good luck in the somewheres!"
end
def render_with_flash_now_alert
flash.now.alert = "Beware the nowheres now!"
render :inline => "hello"
end
def render_with_flash_now_notice
flash.now.notice = "Good luck in the somewheres now!"
render :inline => "hello"
end
def redirect_with_other_flashes
redirect_to '/wonderland', :flash => { :joyride => "Horses!" }
end
@@ -183,6 +193,16 @@ class FlashTest < ActionController::TestCase
assert_equal "Good luck in the somewheres!", @controller.send(:flash)[:notice]
end
def test_render_with_flash_now_alert
get :render_with_flash_now_alert
assert_equal "Beware the nowheres now!", @controller.send(:flash)[:alert]
end
def test_render_with_flash_now_notice
get :render_with_flash_now_notice
assert_equal "Good luck in the somewheres now!", @controller.send(:flash)[:notice]
end
def test_redirect_to_with_other_flashes
get :redirect_with_other_flashes
assert_equal "Horses!", @controller.send(:flash)[:joyride]

View File

@@ -547,6 +547,10 @@ class TestController < ActionController::Base
render :partial => "customer_counter", :collection => [ Customer.new("david"), Customer.new("mary") ]
end
def partial_collection_with_as_and_counter
render :partial => "customer_counter_with_as", :collection => [ Customer.new("david"), Customer.new("mary") ], :as => :client
end
def partial_collection_with_locals
render :partial => "customer_greeting", :collection => [ Customer.new("david"), Customer.new("mary") ], :locals => { :greeting => "Bonjour" }
end
@@ -1242,6 +1246,11 @@ class RenderTest < ActionController::TestCase
assert_equal "david0mary1", @response.body
end
def test_partial_collection_with_as_and_counter
get :partial_collection_with_as_and_counter
assert_equal "david0mary1", @response.body
end
def test_partial_collection_with_locals
get :partial_collection_with_locals
assert_equal "Bonjour: davidBonjour: mary", @response.body
@@ -1379,7 +1388,7 @@ class EtagRenderTest < ActionController::TestCase
def test_render_against_etag_request_should_have_no_content_length_when_match
@request.if_none_match = etag_for("hello david")
get :render_hello_world_from_variable
assert !@response.headers.has_key?("Content-Length"), @response.headers['Content-Length']
assert !@response.headers.has_key?("Content-Length")
end
def test_render_against_etag_request_should_200_when_no_match
@@ -1515,4 +1524,4 @@ class LastModifiedRenderTest < ActionController::TestCase
get :conditional_hello_with_bangs
assert_response :success
end
end
end

View File

@@ -21,7 +21,7 @@ class XmlParamsParsingTest < ActionController::IntegrationTest
def call(env)
bar = env['action_dispatch.request.request_parameters']['foo']
result = "<ok>#{bar}</ok>"
[200, {"Content-Type" => "application/xml", "Content-Length" => result.length.to_s}, result]
[200, {"Content-Type" => "application/xml", "Content-Length" => result.length.to_s}, [result]]
end
end
req = Rack::MockRequest.new(ActionDispatch::ParamsParser.new(Linted.new))

View File

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

View File

@@ -0,0 +1 @@
<%= @todo %>

View File

@@ -154,7 +154,8 @@ class AssetTagHelperTest < ActionView::TestCase
%(image_tag(".pdf.png")) => %(<img alt=".pdf" src="/images/.pdf.png" />),
%(image_tag("http://www.rubyonrails.com/images/rails.png")) => %(<img alt="Rails" src="http://www.rubyonrails.com/images/rails.png" />),
%(image_tag("mouse.png", :mouseover => "/images/mouse_over.png")) => %(<img alt="Mouse" onmouseover="this.src='/images/mouse_over.png'" onmouseout="this.src='/images/mouse.png'" src="/images/mouse.png" />),
%(image_tag("mouse.png", :mouseover => image_path("mouse_over.png"))) => %(<img alt="Mouse" onmouseover="this.src='/images/mouse_over.png'" onmouseout="this.src='/images/mouse.png'" src="/images/mouse.png" />)
%(image_tag("mouse.png", :mouseover => image_path("mouse_over.png"))) => %(<img alt="Mouse" onmouseover="this.src='/images/mouse_over.png'" onmouseout="this.src='/images/mouse.png'" src="/images/mouse.png" />),
%(image_tag("mouse.png", :alt => nil)) => %(<img src="/images/mouse.png" />)
}
FaviconLinkToTag = {

View File

@@ -74,7 +74,7 @@ class CaptureHelperTest < ActionView::TestCase
@av.output_buffer.force_encoding(alt_encoding)
@av.with_output_buffer do
assert alt_encoding, @av.output_buffer.encoding
assert_equal alt_encoding, @av.output_buffer.encoding
end
end
end

View File

@@ -139,6 +139,10 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal('<label for="post_title_great_title">The title goes here</label>', label("post", "title", "The title goes here", :value => "great title"))
end
def test_label_with_block
assert_dom_equal('<label for="post_title">The title, please:</label>', label(:post, :title) { "The title, please:" })
end
def test_text_field
assert_dom_equal(
'<input id="post_title" name="post[title]" size="30" type="text" value="Hello World" />', text_field("post", "title")
@@ -419,6 +423,90 @@ class FormHelperTest < ActionView::TestCase
check_box("post", "secret", :id => "i mean it")
end
def test_nil_id
assert_dom_equal(
'<input name="post[title]" size="30" type="text" value="Hello World" />', text_field("post", "title", "id" => nil)
)
assert_dom_equal(
'<textarea cols="40" name="post[body]" rows="20">Back to the hill and over it again!</textarea>',
text_area("post", "body", "id" => nil)
)
assert_dom_equal(
'<input name="post[secret]" type="hidden" value="0" /><input checked="checked" name="post[secret]" type="checkbox" value="1" />',
check_box("post", "secret", "id" => nil)
)
assert_dom_equal(
'<input type="radio" name="post[secret]" value="0" />',
radio_button("post", "secret", "0", "id" => nil)
)
assert_dom_equal(
'<select name="post[secret]"></select>',
select("post", "secret", [], {}, "id" => nil)
)
assert_dom_equal text_field("post", "title", "id" => nil),
text_field("post", "title", :id => nil)
assert_dom_equal text_area("post", "body", "id" => nil),
text_area("post", "body", :id => nil)
assert_dom_equal check_box("post", "secret", "id" => nil),
check_box("post", "secret", :id => nil)
assert_dom_equal radio_button("post", "secret", "0", "id" => nil),
radio_button("post", "secret", "0", :id => nil)
end
def test_index
assert_dom_equal(
'<input name="post[5][title]" size="30" id="post_5_title" type="text" value="Hello World" />',
text_field("post", "title", "index" => 5)
)
assert_dom_equal(
'<textarea cols="40" name="post[5][body]" id="post_5_body" rows="20">Back to the hill and over it again!</textarea>',
text_area("post", "body", "index" => 5)
)
assert_dom_equal(
'<input name="post[5][secret]" type="hidden" value="0" /><input checked="checked" name="post[5][secret]" type="checkbox" value="1" id="post_5_secret" />',
check_box("post", "secret", "index" => 5)
)
assert_dom_equal(
text_field("post", "title", "index" => 5),
text_field("post", "title", "index" => 5)
)
assert_dom_equal(
text_area("post", "body", "index" => 5),
text_area("post", "body", "index" => 5)
)
assert_dom_equal(
check_box("post", "secret", "index" => 5),
check_box("post", "secret", "index" => 5)
)
end
def test_index_with_nil_id
assert_dom_equal(
'<input name="post[5][title]" size="30" type="text" value="Hello World" />',
text_field("post", "title", "index" => 5, 'id' => nil)
)
assert_dom_equal(
'<textarea cols="40" name="post[5][body]" rows="20">Back to the hill and over it again!</textarea>',
text_area("post", "body", "index" => 5, 'id' => nil)
)
assert_dom_equal(
'<input name="post[5][secret]" type="hidden" value="0" /><input checked="checked" name="post[5][secret]" type="checkbox" value="1" />',
check_box("post", "secret", "index" => 5, 'id' => nil)
)
assert_dom_equal(
text_field("post", "title", "index" => 5, 'id' => nil),
text_field("post", "title", :index => 5, :id => nil)
)
assert_dom_equal(
text_area("post", "body", "index" => 5, 'id' => nil),
text_area("post", "body", :index => 5, :id => nil)
)
assert_dom_equal(
check_box("post", "secret", "index" => 5, 'id' => nil),
check_box("post", "secret", :index => 5, :id => nil)
)
end
def test_auto_index
pid = @post.id
assert_dom_equal(
@@ -445,10 +533,33 @@ class FormHelperTest < ActionView::TestCase
)
end
def test_auto_index_with_nil_id
pid = @post.id
assert_dom_equal(
"<input name=\"post[#{pid}][title]\" size=\"30\" type=\"text\" value=\"Hello World\" />",
text_field("post[]","title", :id => nil)
)
assert_dom_equal(
"<textarea cols=\"40\" name=\"post[#{pid}][body]\" rows=\"20\">Back to the hill and over it again!</textarea>",
text_area("post[]", "body", :id => nil)
)
assert_dom_equal(
"<input name=\"post[#{pid}][secret]\" type=\"hidden\" value=\"0\" /><input checked=\"checked\" name=\"post[#{pid}][secret]\" type=\"checkbox\" value=\"1\" />",
check_box("post[]", "secret", :id => nil)
)
assert_dom_equal(
"<input checked=\"checked\" name=\"post[#{pid}][title]\" type=\"radio\" value=\"Hello World\" />",
radio_button("post[]", "title", "Hello World", :id => nil)
)
assert_dom_equal("<input name=\"post[#{pid}][title]\" type=\"radio\" value=\"Goodbye World\" />",
radio_button("post[]", "title", "Goodbye World", :id => nil)
)
end
def test_form_for
assert_deprecated do
form_for(:post, @post, :html => { :id => 'create-post' }) do |f|
concat f.label(:title)
concat f.label(:title) { "The Title" }
concat f.text_field(:title)
concat f.text_area(:body)
concat f.check_box(:secret)
@@ -458,7 +569,7 @@ class FormHelperTest < ActionView::TestCase
expected =
"<form action='http://www.example.com' id='create-post' method='post'>" +
"<label for='post_title'>Title</label>" +
"<label for='post_title'>The Title</label>" +
"<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
"<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
"<input name='post[secret]' type='hidden' value='0' />" +
@@ -485,7 +596,7 @@ class FormHelperTest < ActionView::TestCase
"<input name='other_name[title]' size='30' id='other_name_title' value='Hello World' type='text' />" +
"<textarea name='other_name[body]' id='other_name_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
"<input name='other_name[secret]' value='0' type='hidden' />" +
"<input name='other_name[secret]' checked='checked' id='other_name_secret' value='1' type='checkbox' />" +
"<input name='other_name[secret]' checked='checked' id='other_name_secret' value='1' type='checkbox' />" +
"<input name='commit' id='other_name_submit' value='Create post' type='submit' /></form>"
assert_dom_equal expected, output_buffer

View File

@@ -767,6 +767,62 @@ class FormOptionsHelperTest < ActionView::TestCase
html
end
def test_options_for_select_with_element_attributes
assert_dom_equal(
"<option value=\"&lt;Denmark&gt;\" class=\"bold\">&lt;Denmark&gt;</option>\n<option value=\"USA\" onclick=\"alert('Hello World');\">USA</option>\n<option value=\"Sweden\">Sweden</option>\n<option value=\"Germany\">Germany</option>",
options_for_select([ [ "<Denmark>", { :class => 'bold' } ], [ "USA", { :onclick => "alert('Hello World');" } ], [ "Sweden" ], "Germany" ])
)
end
def test_options_for_select_with_element_attributes_and_selection
assert_dom_equal(
"<option value=\"&lt;Denmark&gt;\">&lt;Denmark&gt;</option>\n<option value=\"USA\" class=\"bold\" selected=\"selected\">USA</option>\n<option value=\"Sweden\">Sweden</option>",
options_for_select([ "<Denmark>", [ "USA", { :class => 'bold' } ], "Sweden" ], "USA")
)
end
def test_options_for_select_with_element_attributes_and_selection_array
assert_dom_equal(
"<option value=\"&lt;Denmark&gt;\">&lt;Denmark&gt;</option>\n<option value=\"USA\" class=\"bold\" selected=\"selected\">USA</option>\n<option value=\"Sweden\" selected=\"selected\">Sweden</option>",
options_for_select([ "<Denmark>", [ "USA", { :class => 'bold' } ], "Sweden" ], [ "USA", "Sweden" ])
)
end
def test_option_html_attributes_from_without_hash
assert_dom_equal(
"",
option_html_attributes([ 'foo', 'bar' ])
)
end
def test_option_html_attributes_with_single_element_hash
assert_dom_equal(
" class=\"fancy\"",
option_html_attributes([ 'foo', 'bar', { :class => 'fancy' } ])
)
end
def test_option_html_attributes_with_multiple_element_hash
assert_dom_equal(
" class=\"fancy\" onclick=\"alert('Hello World');\"",
option_html_attributes([ 'foo', 'bar', { :class => 'fancy', 'onclick' => "alert('Hello World');" } ])
)
end
def test_option_html_attributes_with_multiple_hashes
assert_dom_equal(
" class=\"fancy\" onclick=\"alert('Hello World');\"",
option_html_attributes([ 'foo', 'bar', { :class => 'fancy' }, { 'onclick' => "alert('Hello World');" } ])
)
end
def test_option_html_attributes_with_special_characters
assert_dom_equal(
" onclick=\"alert(&quot;&lt;code&gt;&quot;)\"",
option_html_attributes([ 'foo', 'bar', { :onclick => %(alert("<code>")) } ])
)
end
def test_grouped_collection_select
@continents = [
Continent.new("<Africa>", [Country.new("<sa>", "<South Africa>"), Country.new("so", "Somalia")] ),

View File

@@ -288,6 +288,20 @@ class FormTagHelperTest < ActionView::TestCase
assert_match VALID_HTML_ID, label_elem['for']
end
def test_label_tag_with_block
assert_dom_equal('<label>Blocked</label>', label_tag { "Blocked" })
end
def test_label_tag_with_block_and_argument
output = label_tag("clock") { "Grandfather" }
assert_dom_equal('<label for="clock">Grandfather</label>', output)
end
def test_label_tag_with_block_and_argument_and_options
output = label_tag("clock", :id => "label_clock") { "Grandfather" }
assert_dom_equal('<label for="clock" id="label_clock">Grandfather</label>', output)
end
def test_boolean_options
assert_dom_equal %(<input checked="checked" disabled="disabled" id="admin" name="admin" readonly="readonly" type="checkbox" value="1" />), check_box_tag("admin", 1, true, 'disabled' => true, :readonly => "yes")
assert_dom_equal %(<input checked="checked" id="admin" name="admin" type="checkbox" value="1" />), check_box_tag("admin", 1, true, :disabled => false, :readonly => nil)

View File

@@ -111,6 +111,7 @@ module RenderTestCases
assert_match %r!method.*doesnt_exist!, e.message
assert_equal "", e.sub_template_message
assert_equal "1", e.line_number
assert_equal "1: <%= doesnt_exist %>", e.annoted_source_code.strip
assert_equal File.expand_path("#{FIXTURE_LOAD_PATH}/test/_raise.html.erb"), e.file_name
end

View File

@@ -124,11 +124,12 @@ module ActiveModel
@previously_changed
end
# Map of change <tt>attr => original value</tt>.
def changed_attributes
@changed_attributes ||= {}
end
private
# Map of change <tt>attr => original value</tt>.
def changed_attributes
@changed_attributes ||= {}
end
# Handle <tt>*_changed?</tt> for +method_missing+.
def attribute_changed?(attr)

View File

@@ -90,7 +90,7 @@ module ActiveModel
@builder = options[:builder]
@builder.instruct! unless options[:skip_instruct]
root = (options[:root] || @serializable.class.model_name.singular).to_s
root = (options[:root] || @serializable.class.model_name.element).to_s
root = ActiveSupport::XmlMini.rename_key(root, options)
args = [root]

View File

@@ -46,6 +46,10 @@ module ActiveModel
included do
extend ActiveModel::Translation
extend HelperMethods
include HelperMethods
define_callbacks :validate, :scope => :name
attr_accessor :validation_context
@@ -138,12 +142,6 @@ module ActiveModel
def attribute_method?(attribute)
method_defined?(attribute)
end
private
def _merge_attributes(attr_names)
options = attr_names.extract_options!
options.merge(:attributes => attr_names.flatten)
end
end
# Returns the Errors object that holds all information about attribute error messages.

View File

@@ -21,7 +21,7 @@ module ActiveModel
end
end
module ClassMethods
module HelperMethods
# Encapsulates the pattern of wanting to validate the acceptance of a terms of service check box (or similar agreement). Example:
#
# class Person < ActiveRecord::Base

View File

@@ -12,7 +12,7 @@ module ActiveModel
end
end
module ClassMethods
module HelperMethods
# Encapsulates the pattern of wanting to validate a password or email address field with a confirmation. Example:
#
# Model:

View File

@@ -12,7 +12,7 @@ module ActiveModel
end
end
module ClassMethods
module HelperMethods
# Validates that the value of the specified attribute is not in a particular enumerable object.
#
# class Person < ActiveRecord::Base

View File

@@ -24,7 +24,7 @@ module ActiveModel
end
end
module ClassMethods
module HelperMethods
# Validates whether the value of the specified attribute is of the correct form, going by the regular expression provided.
# You can require that the attribute matches the regular expression:
#

View File

@@ -12,7 +12,7 @@ module ActiveModel
end
end
module ClassMethods
module HelperMethods
# Validates whether the value of the specified attribute is available in a particular enumerable object.
#
# class Person < ActiveRecord::Base

View File

@@ -51,7 +51,7 @@ module ActiveModel
end
end
module ClassMethods
module HelperMethods
# Validates that the specified attribute matches the length restrictions supplied. Only one option can be used at a time:
#

View File

@@ -70,7 +70,7 @@ module ActiveModel
end
module ClassMethods
module HelperMethods
# Validates whether the value of the specified attribute is numeric by trying to convert it to
# a float with Kernel.Float (if <tt>only_integer</tt> is false) or applying it to the regular expression
# <tt>/\A[\+\-]?\d+\Z/</tt> (if <tt>only_integer</tt> is set to true).

View File

@@ -8,7 +8,7 @@ module ActiveModel
end
end
module ClassMethods
module HelperMethods
# Validates that the specified attributes are not blank (as defined by Object#blank?). Happens by default on save. Example:
#
# class Person < ActiveRecord::Base

View File

@@ -1,5 +1,13 @@
module ActiveModel
module Validations
module HelperMethods
private
def _merge_attributes(attr_names)
options = attr_names.extract_options!
options.merge(:attributes => attr_names.flatten)
end
end
module ClassMethods
# Passes the record off to the class or classes specified and allows them
@@ -75,5 +83,50 @@ module ActiveModel
end
end
end
# Passes the record off to the class or classes specified and allows them
# to add errors based on more complex conditions.
#
# class Person
# include ActiveModel::Validations
#
# validates :instance_validations
#
# def instance_validations
# validates_with MyValidator
# end
# end
#
# Please consult the class method documentation for more information on
# creating your own validator.
#
# You may also pass it multiple classes, like so:
#
# class Person
# include ActiveModel::Validations
#
# validates :instance_validations, :on => :create
#
# def instance_validations
# validates_with MyValidator, MyOtherValidator
# end
# end
#
# Standard configuration options (:on, :if and :unless), which are
# available on the class version of validates_with, should instead be
# placed on the <tt>validates</tt> method as these are applied and tested
# in the callback
#
# If you pass any additional configuration options, they will be passed
# to the class and available as <tt>options</tt>, please refer to the
# class version of this method for more information
#
def validates_with(*args, &block)
options = args.extract_options!
args.each do |klass|
validator = klass.new(options, &block)
validator.validate(self)
end
end
end
end

View File

@@ -88,6 +88,9 @@ module ActiveModel #:nodoc:
# klass.send :attr_accessor, :custom_attribute
# end
# end
#
# This setup method is only called when used with validation macros or the
# class level <tt>validates_with</tt> method.
#
class Validator
attr_reader :options

View File

@@ -37,8 +37,8 @@ class XmlSerializationTest < ActiveModel::TestCase
test "should serialize namespaced root" do
@xml = Admin::Contact.new(@contact.attributes).to_xml
assert_match %r{^<admin-contact>}, @xml
assert_match %r{</admin-contact>$}, @xml
assert_match %r{^<contact>}, @xml
assert_match %r{</contact>$}, @xml
end
test "should serialize default root with namespace" do

View File

@@ -45,7 +45,7 @@ class ValidatesTest < ActiveModel::TestCase
Person.validates :karma, :presence => true, :email => true, :if => :condition_is_true
person = Person.new
person.valid?
assert ["can't be blank", "is not an email"], person.errors[:karma].sort
assert_equal ["can't be blank", "is not an email"], person.errors[:karma].sort
end
def test_validates_with_unless_shared_conditions
@@ -111,4 +111,4 @@ class ValidatesTest < ActiveModel::TestCase
person.valid?
assert_equal ['Local validator please'], person.errors[:title]
end
end
end

View File

@@ -4,6 +4,7 @@ require 'cases/helper'
require 'models/topic'
require 'models/reply'
require 'models/custom_reader'
require 'models/automobile'
class ValidationsTest < ActiveModel::TestCase
@@ -227,7 +228,7 @@ class ValidationsTest < ActiveModel::TestCase
t = Topic.new
assert t.invalid?
assert ["NO BLANKS HERE"], t.errors[:title]
assert_equal ["NO BLANKS HERE"], t.errors[:title]
end
def test_list_of_validators_for_model
@@ -252,4 +253,16 @@ class ValidationsTest < ActiveModel::TestCase
Topic.validates_length_of :title, :minimum => 10
assert_equal 10, Topic.validators_on(:title).first.options[:minimum]
end
def test_validations_on_the_instance_level
auto = Automobile.new
assert auto.invalid?
assert_equal 2, auto.errors.size
auto.make = 'Toyota'
auto.model = 'Corolla'
assert auto.valid?
end
end

View File

@@ -0,0 +1,12 @@
class Automobile
include ActiveModel::Validations
validate :validations
attr_accessor :make, :model
def validations
validates_presence_of :make
validates_length_of :model, :within => 2..10
end
end

View File

@@ -1,5 +1,15 @@
*Rails 3.0.0 [beta 4/release candidate] (unreleased)*
* Add index length support for MySQL. #1852 [Emili Parreno, Pratik Naik]
Example:
add_index(:accounts, :name, :name => 'by_name', :length => 10)
=> CREATE INDEX by_name ON accounts(name(10))
add_index(:accounts, [:name, :surname], :name => 'by_name_surname', :length => {:name => 10, :surname => 15})
=> CREATE INDEX by_name_surname ON accounts(name(10), surname(15))
* find_or_create_by_attr(value, ...) works when attr is protected. #4457 [Santiago Pastorino, Marc-André Lafortune]
* New callbacks: after_commit and after_rollback. Do expensive operations like image thumbnailing after_commit instead of after_save. #2991 [Brian Durand]

View File

@@ -1399,7 +1399,7 @@ module ActiveRecord
primary_key = reflection.source_reflection.primary_key_name
send(through.name).select("DISTINCT #{through.quoted_table_name}.#{primary_key}").map!(&:"#{primary_key}")
else
send(reflection.name).select("#{reflection.quoted_table_name}.#{reflection.klass.primary_key}").map!(&:id)
send(reflection.name).select("#{reflection.quoted_table_name}.#{reflection.klass.primary_key}").except(:includes).map!(&:id)
end
end
end

View File

@@ -8,7 +8,7 @@ module ActiveRecord
include AttributeMethods::Write
included do
if self < Timestamp
if self < ::ActiveRecord::Timestamp
raise "You cannot include Dirty after Timestamp"
end

View File

@@ -6,7 +6,7 @@ module ActiveRecord
# Returns this record's primary key value wrapped in an Array
# or nil if the record is a new_record?
def to_key
new_record? ? nil : [ send(self.class.primary_key) ]
new_record? ? nil : [ id ]
end
module ClassMethods

View File

@@ -668,7 +668,6 @@ module ActiveRecord #:nodoc:
name = "#{full_table_name_prefix}#{contained}#{undecorated_table_name(base.name)}#{table_name_suffix}"
end
@quoted_table_name = nil
set_table_name(name)
name
end
@@ -702,6 +701,7 @@ module ActiveRecord #:nodoc:
# set_table_name "project"
# end
def set_table_name(value = nil, &block)
@quoted_table_name = nil
define_attr_method :table_name, value, &block
end
alias :table_name= :set_table_name
@@ -1190,7 +1190,11 @@ module ActiveRecord #:nodoc:
# default_scope order('last_name, first_name')
# end
def default_scope(options = {})
self.default_scoping << construct_finder_arel(options)
self.default_scoping << construct_finder_arel(options, default_scoping.pop)
end
def clear_default_scope
self.default_scoping.clear
end
def scoped_methods #:nodoc:
@@ -1463,6 +1467,7 @@ module ActiveRecord #:nodoc:
@attributes_cache = {}
@new_record = true
ensure_proper_type
@changed_attributes = other.changed_attributes.dup
if scope = self.class.send(:current_scoped_methods)
create_with = scope.scope_for_create

View File

@@ -258,7 +258,7 @@ module ActiveRecord
end
end
class IndexDefinition < Struct.new(:table, :name, :unique, :columns) #:nodoc:
class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths) #:nodoc:
end
# Abstract representation of a column definition. Instances of this type
@@ -494,7 +494,7 @@ module ActiveRecord
end
%w( string text integer float decimal datetime timestamp time date binary boolean ).each do |column_type|
class_eval <<-EOV
class_eval <<-EOV, __FILE__, __LINE__ + 1
def #{column_type}(*args) # def string(*args)
options = args.extract_options! # options = args.extract_options!
column_names = args # column_names = args
@@ -694,7 +694,7 @@ module ActiveRecord
# t.string(:goat)
# t.string(:goat, :sheep)
%w( string text integer float decimal datetime timestamp time date binary boolean ).each do |column_type|
class_eval <<-EOV
class_eval <<-EOV, __FILE__, __LINE__ + 1
def #{column_type}(*args) # def string(*args)
options = args.extract_options! # options = args.extract_options!
column_names = args # column_names = args

View File

@@ -256,18 +256,32 @@ module ActiveRecord
# name.
#
# ===== Examples
#
# ====== Creating a simple index
# add_index(:suppliers, :name)
# generates
# CREATE INDEX suppliers_name_index ON suppliers(name)
#
# ====== Creating a unique index
# add_index(:accounts, [:branch_id, :party_id], :unique => true)
# generates
# CREATE UNIQUE INDEX accounts_branch_id_party_id_index ON accounts(branch_id, party_id)
#
# ====== Creating a named index
# add_index(:accounts, [:branch_id, :party_id], :unique => true, :name => 'by_branch_party')
# generates
# CREATE UNIQUE INDEX by_branch_party ON accounts(branch_id, party_id)
#
# ====== Creating an index with specific key length
# add_index(:accounts, :name, :name => 'by_name', :length => 10)
# generates
# CREATE INDEX by_name ON accounts(name(10))
#
# add_index(:accounts, [:name, :surname], :name => 'by_name_surname', :length => {:name => 10, :surname => 15})
# generates
# CREATE INDEX by_name_surname ON accounts(name(10), surname(15))
#
# Note: SQLite doesn't support index length
def add_index(table_name, column_name, options = {})
column_names = Array.wrap(column_name)
index_name = index_name(table_name, :column => column_names)
@@ -278,7 +292,9 @@ module ActiveRecord
else
index_type = options
end
quoted_column_names = column_names.map { |e| quote_column_name(e) }.join(", ")
quoted_column_names = quoted_columns_for_index(column_names, options).join(", ")
execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{quoted_column_names})"
end
@@ -430,6 +446,11 @@ module ActiveRecord
end
protected
# Overridden by the mysql adapter for supporting index lengths
def quoted_columns_for_index(column_names, options = {})
column_names.map {|name| quote_column_name(name) }
end
def options_include_default?(options)
options.include?(:default) && !(options[:null] == false && options[:default].nil?)
end

View File

@@ -15,26 +15,26 @@ module MysqlCompat #:nodoc:
# Ruby driver has a version string and returns null values in each_hash
# C driver >= 2.7 returns null values in each_hash
if Mysql.const_defined?(:VERSION) && (Mysql::VERSION.is_a?(String) || Mysql::VERSION >= 20700)
target.class_eval <<-'end_eval'
def all_hashes # def all_hashes
rows = [] # rows = []
each_hash { |row| rows << row } # each_hash { |row| rows << row }
rows # rows
end # end
target.class_eval <<-'end_eval', __FILE__, __LINE__ + 1
def all_hashes # def all_hashes
rows = [] # rows = []
each_hash { |row| rows << row } # each_hash { |row| rows << row }
rows # rows
end # end
end_eval
# adapters before 2.7 don't have a version constant
# and don't return null values in each_hash
else
target.class_eval <<-'end_eval'
def all_hashes # def all_hashes
rows = [] # rows = []
all_fields = fetch_fields.inject({}) { |fields, f| # all_fields = fetch_fields.inject({}) { |fields, f|
fields[f.name] = nil; fields # fields[f.name] = nil; fields
} # }
each_hash { |row| rows << all_fields.dup.update(row) } # each_hash { |row| rows << all_fields.dup.update(row) }
rows # rows
end # end
target.class_eval <<-'end_eval', __FILE__, __LINE__ + 1
def all_hashes # def all_hashes
rows = [] # rows = []
all_fields = fetch_fields.inject({}) { |fields, f| # all_fields = fetch_fields.inject({}) { |fields, f|
fields[f.name] = nil; fields # fields[f.name] = nil; fields
} # }
each_hash { |row| rows << all_fields.dup.update(row) } # each_hash { |row| rows << all_fields.dup.update(row) }
rows # rows
end # end
end_eval
end
@@ -461,10 +461,11 @@ module ActiveRecord
if current_index != row[2]
next if row[2] == "PRIMARY" # skip the primary key
current_index = row[2]
indexes << IndexDefinition.new(row[0], row[2], row[1] == "0", [])
indexes << IndexDefinition.new(row[0], row[2], row[1] == "0", [], [])
end
indexes.last.columns << row[4]
indexes.last.lengths << row[7]
end
result.free
indexes
@@ -512,7 +513,7 @@ module ActiveRecord
def change_column(table_name, column_name, type, options = {}) #:nodoc:
column = column_for(table_name, column_name)
unless options_include_default?(options)
if has_default?(type) && !options_include_default?(options)
options[:default] = column.default
end
@@ -594,6 +595,18 @@ module ActiveRecord
end
protected
def quoted_columns_for_index(column_names, options = {})
length = options[:length] if options.is_a?(Hash)
quoted_column_names = case length
when Hash
column_names.map {|name| length[name] ? "#{quote_column_name(name)}(#{length[name]})" : quote_column_name(name) }
when Fixnum
column_names.map {|name| "#{quote_column_name(name)}(#{length})"}
else
column_names.map {|name| quote_column_name(name) }
end
end
def translate_exception(exception, message)
return super unless exception.respond_to?(:errno)
@@ -662,6 +675,10 @@ module ActiveRecord
end
column
end
def has_default?(sql_type)
sql_type =~ :binary || sql_type == :text #mysql forbids defaults on blob and text columns
end
end
end
end

View File

@@ -891,6 +891,7 @@ module ActiveRecord
instances.size == 1 ? instances.first : instances
end
private table_name
end
end

View File

@@ -113,12 +113,11 @@ module ActiveRecord
lock_col = self.class.locking_column
previous_value = send(lock_col).to_i
affected_rows = connection.delete(
"DELETE FROM #{self.class.quoted_table_name} " +
"WHERE #{connection.quote_column_name(self.class.primary_key)} = #{quoted_id} " +
"AND #{self.class.quoted_locking_column} = #{quote_value(previous_value)}",
"#{self.class.name} Destroy"
)
table = self.class.arel_table
predicate = table[self.class.primary_key].eq(id)
predicate = predicate.and(table[self.class.locking_column].eq(previous_value))
affected_rows = self.class.unscoped.where(predicate).delete_all
unless affected_rows == 1
raise ActiveRecord::StaleObjectError, "Attempted to delete a stale object: #{self.class.name}"

View File

@@ -99,11 +99,7 @@ module ActiveRecord
block_given? ? relation.extending(Module.new(&block)) : relation
end
singleton_class.instance_eval do
define_method name do |*args|
scopes[name].call(*args)
end
end
singleton_class.send :define_method, name, &scopes[name]
end
def named_scope(*args, &block)

View File

@@ -15,6 +15,12 @@ module ActiveRecord
config.generators.orm :active_record, :migration => true,
:timestamps => true
config.app_middleware.insert_after "::ActionDispatch::Callbacks",
"ActiveRecord::QueryCache"
config.app_middleware.insert_after "::ActionDispatch::Callbacks",
"ActiveRecord::ConnectionAdapters::ConnectionManagement"
rake_tasks do
load "active_record/railties/databases.rake"
end
@@ -58,16 +64,9 @@ module ActiveRecord
end
end
# Setup database middleware after initializers have run
initializer "active_record.initialize_database_middleware", :after => "action_controller.set_configs" do |app|
middleware = app.config.middleware
middleware.insert_after "::ActionDispatch::Callbacks", ActiveRecord::QueryCache
middleware.insert_after "::ActionDispatch::Callbacks", ActiveRecord::ConnectionAdapters::ConnectionManagement
end
initializer "active_record.set_dispatch_hooks", :before => :set_clear_dependencies_hook do |app|
ActiveSupport.on_load(:active_record) do
unless app.config.cache_classes
unless app.config.cache_classes
ActiveSupport.on_load(:active_record) do
ActionDispatch::Callbacks.after do
ActiveRecord::Base.reset_subclasses
ActiveRecord::Base.clear_reloadable_connections!

View File

@@ -71,7 +71,12 @@ module ActiveRecord
yield records
break if records.size < batch_size
records = relation.where(primary_key.gt(records.last.id)).all
if primary_key_offset = records.last.id
records = relation.where(primary_key.gt(primary_key_offset)).all
else
raise "Primary key not included in the custom select clause"
end
end
end

View File

@@ -45,9 +45,8 @@ module ActiveRecord
calculate(:count, column_name, options)
end
# Calculates the average value on a given column. The value is returned as
# a float, or +nil+ if there's no row. See +calculate+ for examples with
# options.
# Calculates the average value on a given column. Returns +nil+ if there's
# no row. See +calculate+ for examples with options.
#
# Person.average('age') # => 35.8
def average(column_name, options = {})
@@ -241,9 +240,9 @@ module ActiveRecord
def type_cast_calculated_value(value, column, operation = nil)
if value.is_a?(String) || value.nil?
case operation
when 'count' then value.to_i
when 'sum' then type_cast_using_column(value || '0', column)
when 'average' then value && (value.is_a?(Fixnum) ? value.to_f : value).to_d
when 'count' then value.to_i
when 'sum' then type_cast_using_column(value || '0', column)
when 'average' then value.try(:to_d)
else type_cast_using_column(value, column)
end
else

View File

@@ -10,7 +10,7 @@ module ActiveRecord
attr_accessor :"#{query_method}_values"
next if [:where, :having].include?(query_method)
class_eval <<-CEVAL, __FILE__
class_eval <<-CEVAL, __FILE__, __LINE__ + 1
def #{query_method}(*args, &block)
new_relation = clone
new_relation.send(:apply_modules, Module.new(&block)) if block_given?
@@ -22,7 +22,7 @@ module ActiveRecord
end
[:where, :having].each do |query_method|
class_eval <<-CEVAL, __FILE__
class_eval <<-CEVAL, __FILE__, __LINE__ + 1
def #{query_method}(*args, &block)
new_relation = clone
new_relation.send(:apply_modules, Module.new(&block)) if block_given?
@@ -36,7 +36,7 @@ module ActiveRecord
ActiveRecord::Relation::SINGLE_VALUE_METHODS.each do |query_method|
attr_accessor :"#{query_method}_value"
class_eval <<-CEVAL, __FILE__
class_eval <<-CEVAL, __FILE__, __LINE__ + 1
def #{query_method}(value = true, &block)
new_relation = clone
new_relation.send(:apply_modules, Module.new(&block)) if block_given?

View File

@@ -178,6 +178,9 @@ HEADER
statment_parts << (':name => ' + index.name.inspect)
statment_parts << ':unique => true' if index.unique
index_lengths = index.lengths.compact if index.lengths.is_a?(Array)
statment_parts << (':length => ' + Hash[*index.columns.zip(index.lengths).flatten].inspect) if index_lengths.present?
' ' + statment_parts.join(', ')
end

View File

@@ -36,49 +36,40 @@ module ActiveRecord
# The validation process on save can be skipped by passing false. The regular Base#save method is
# replaced with this when the validations module is mixed in, which it is by default.
def save(options=nil)
return super if valid?(options)
false
end
def save_without_validation!
save!(:validate => false)
def save(options={})
perform_validations(options) ? super : false
end
# Attempts to save the record just like Base#save but will raise a RecordInvalid exception instead of returning false
# if the record is not valid.
def save!(options = nil)
return super if valid?(options)
raise RecordInvalid.new(self)
def save!(options={})
perform_validations(options) ? super : raise(RecordInvalid.new(self))
end
# Runs all the specified validations and returns true if no errors were added otherwise false.
def valid?(options = nil)
def valid?(context = nil)
context ||= (new_record? ? :create : :update)
super(context)
deprecated_callback_method(:validate)
deprecated_callback_method(:"validate_on_#{context}")
errors.empty?
end
protected
def perform_validations(options={})
perform_validation = case options
when NilClass
true
when Hash
options[:validate] != false
else
ActiveSupport::Deprecation.warn "save(#{options}) is deprecated, please give save(:validate => #{options}) instead", caller
options
when Hash
options[:validate] != false
else
ActiveSupport::Deprecation.warn "save(#{options}) is deprecated, please give save(:validate => #{options}) instead", caller
options
end
if perform_validation
errors.clear
self.validation_context = new_record? ? :create : :update
_run_validate_callbacks
deprecated_callback_method(:validate)
if new_record?
deprecated_callback_method(:validate_on_create)
else
deprecated_callback_method(:validate_on_update)
end
errors.empty?
valid?(options.is_a?(Hash) ? options[:context] : nil)
else
true
end

View File

@@ -15,6 +15,23 @@ class ActiveSchemaTest < ActiveRecord::TestCase
end
end
def test_add_index
expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`)"
assert_equal expected, add_index(:people, :last_name, :length => nil)
expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`(10))"
assert_equal expected, add_index(:people, :last_name, :length => 10)
expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(15))"
assert_equal expected, add_index(:people, [:last_name, :first_name], :length => 15)
expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`)"
assert_equal expected, add_index(:people, [:last_name, :first_name], :length => {:last_name => 15})
expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(10))"
assert_equal expected, add_index(:people, [:last_name, :first_name], :length => {:last_name => 15, :first_name => 10})
end
def test_drop_table
assert_equal "DROP TABLE `people`", drop_table(:people)
end

View File

@@ -17,7 +17,7 @@ module Remembered
module ClassMethods
def remembered; @@remembered ||= []; end
def rand; @@remembered.rand; end
def random_element; @@remembered.random_element; end
end
end
@@ -79,14 +79,14 @@ class EagerLoadPolyAssocsTest < ActiveRecord::TestCase
[Circle, Square, Triangle, NonPolyOne, NonPolyTwo].map(&:create!)
end
1.upto(NUM_SIMPLE_OBJS) do
PaintColor.create!(:non_poly_one_id => NonPolyOne.rand.id)
PaintTexture.create!(:non_poly_two_id => NonPolyTwo.rand.id)
PaintColor.create!(:non_poly_one_id => NonPolyOne.random_element.id)
PaintTexture.create!(:non_poly_two_id => NonPolyTwo.random_element.id)
end
1.upto(NUM_SHAPE_EXPRESSIONS) do
shape_type = [Circle, Square, Triangle].rand
paint_type = [PaintColor, PaintTexture].rand
ShapeExpression.create!(:shape_type => shape_type.to_s, :shape_id => shape_type.rand.id,
:paint_type => paint_type.to_s, :paint_id => paint_type.rand.id)
shape_type = [Circle, Square, Triangle].random_element
paint_type = [PaintColor, PaintTexture].random_element
ShapeExpression.create!(:shape_type => shape_type.to_s, :shape_id => shape_type.random_element.id,
:paint_type => paint_type.to_s, :paint_id => paint_type.random_element.id)
end
end

View File

@@ -900,6 +900,10 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert !company.clients.loaded?
end
def test_get_ids_ignores_include_option
assert_equal [readers(:michael_welcome).id], posts(:welcome).readers_with_person_ids
end
def test_get_ids_for_unloaded_finder_sql_associations_loads_them
company = companies(:first_firm)
assert !company.clients_using_sql.loaded?

View File

@@ -289,7 +289,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_has_many_array_methods_called_by_method_missing
assert true, authors(:david).categories.any? { |category| category.name == 'General' }
assert authors(:david).categories.any? { |category| category.name == 'General' }
assert_nothing_raised { authors(:david).categories.sort }
end

View File

@@ -765,7 +765,7 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase
@ship.destroy
@pirate.reload.catchphrase = "Arr"
@pirate.save
assert 'Arr', @pirate.reload.catchphrase
assert_equal 'Arr', @pirate.reload.catchphrase
end
def test_should_automatically_save_the_associated_model
@@ -885,7 +885,7 @@ class TestAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase
@pirate.destroy
@ship.reload.name = "The Vile Insanity"
@ship.save
assert 'The Vile Insanity', @ship.reload.name
assert_equal 'The Vile Insanity', @ship.reload.name
end
def test_should_automatically_save_the_associated_model

View File

@@ -1793,6 +1793,18 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal "bar", k.table_name
end
def test_quoted_table_name_after_set_table_name
klass = Class.new(ActiveRecord::Base)
klass.set_table_name "foo"
assert_equal "foo", klass.table_name
assert_equal klass.connection.quote_table_name("foo"), klass.quoted_table_name
klass.set_table_name "bar"
assert_equal "bar", klass.table_name
assert_equal klass.connection.quote_table_name("bar"), klass.quoted_table_name
end
def test_set_table_name_with_block
k = Class.new( ActiveRecord::Base )
k.set_table_name { "ks" }

View File

@@ -17,6 +17,20 @@ class EachTest < ActiveRecord::TestCase
end
end
def test_each_should_raise_if_select_is_set_without_id
assert_raise(RuntimeError) do
Post.find_each(:select => :title, :batch_size => 1) { |post| post }
end
end
def test_each_should_execute_if_id_is_in_select
assert_queries(4) do
Post.find_each(:select => "id, title, type", :batch_size => 2) do |post|
assert_kind_of Post, post
end
end
end
def test_each_should_raise_if_the_order_is_set
assert_raise(RuntimeError) do
Post.find_each(:order => "title") { |post| post }

View File

@@ -20,8 +20,7 @@ class CalculationsTest < ActiveRecord::TestCase
def test_should_average_field
value = Account.average(:credit_limit)
assert_kind_of BigDecimal, value
assert_equal BigDecimal.new('53.0'), value
assert_equal 53.0, value
end
def test_should_return_nil_as_average

View File

@@ -338,6 +338,15 @@ class DirtyTest < ActiveRecord::TestCase
assert !pirate.changed?
end
def test_cloned_objects_should_not_copy_dirty_flag_from_creator
pirate = Pirate.create!(:catchphrase => "shiver me timbers")
pirate_clone = pirate.clone
pirate_clone.reset_catchphrase!
pirate.catchphrase = "I love Rum"
assert pirate.catchphrase_changed?
assert !pirate_clone.catchphrase_changed?
end
def test_reverted_changes_are_not_dirty
phrase = "shiver me timbers"
pirate = Pirate.create!(:catchphrase => phrase)

View File

@@ -722,10 +722,10 @@ class FinderTest < ActiveRecord::TestCase
def test_find_all_by_one_attribute_with_options
topics = Topic.find_all_by_content("Have a nice day", :order => "id DESC")
assert topics(:first), topics.last
assert_equal topics(:first), topics.last
topics = Topic.find_all_by_content("Have a nice day", :order => "id")
assert topics(:first), topics.first
assert_equal topics(:first), topics.first
end
def test_find_all_by_array_attribute

View File

@@ -256,6 +256,11 @@ class FixturesWithoutInstantiationTest < ActiveRecord::TestCase
def test_fixtures_from_root_yml_without_instantiation
assert !defined?(@unknown), "@unknown is not defined"
end
def test_visibility_of_accessor_method
assert_equal false, respond_to?(:topics, false), "should be private method"
assert_equal true, respond_to?(:topics, true), "confirm to respond surely"
end
def test_accessor_methods
assert_equal "The First Topic", topics(:first).title

View File

@@ -587,6 +587,18 @@ class HasAndBelongsToManyScopingTest< ActiveRecord::TestCase
end
end
class ClearDefaultScopeTest < ActiveRecord::TestCase
fixtures :developers
def test_should_clear_default_scope
klass = Class.new(DeveloperCalledDavid)
klass.__send__ :clear_default_scope
expected = Developer.all.collect { |dev| dev.name }
actual = klass.all.collect { |dev| dev.name }
assert_equal expected, actual
end
end
class DefaultScopingTest < ActiveRecord::TestCase
fixtures :developers, :posts
@@ -615,15 +627,30 @@ class DefaultScopingTest < ActiveRecord::TestCase
def test_default_scoping_with_inheritance
# Inherit a class having a default scope and define a new default scope
klass = Class.new(DeveloperOrderedBySalary)
klass.send :default_scope, {}
klass.send :default_scope, :limit => 1
# Scopes added on children should append to parent scope
assert klass.scoped.order_values.blank?
assert_equal 1, klass.scoped.limit_value
assert_equal ['salary DESC'], klass.scoped.order_values
# Parent should still have the original scope
assert_equal nil, DeveloperOrderedBySalary.scoped.limit_value
assert_equal ['salary DESC'], DeveloperOrderedBySalary.scoped.order_values
end
def test_default_scope_called_twice_merges_conditions
Developer.destroy_all
Developer.create!(:name => "David", :salary => 80000)
Developer.create!(:name => "David", :salary => 100000)
Developer.create!(:name => "Brian", :salary => 100000)
klass = Class.new(Developer)
klass.__send__ :default_scope, :conditions => { :name => "David" }
klass.__send__ :default_scope, :conditions => { :salary => 100000 }
assert_equal 1, klass.count
assert_equal "David", klass.first.name
assert_equal 100000, klass.first.salary
end
def test_method_scope
expected = Developer.find(:all, :order => 'name DESC').collect { |dev| dev.salary }
received = DeveloperOrderedBySalary.all_ordered_by_name.collect { |dev| dev.salary }

View File

@@ -92,6 +92,14 @@ if ActiveRecord::Base.connection.supports_migrations?
assert_nothing_raised { Person.connection.remove_index("people", "last_name_and_first_name") }
assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"]) }
assert_nothing_raised { Person.connection.remove_index("people", ["last_name", "first_name"]) }
assert_nothing_raised { Person.connection.add_index("people", ["last_name"], :length => 10) }
assert_nothing_raised { Person.connection.remove_index("people", "last_name") }
assert_nothing_raised { Person.connection.add_index("people", ["last_name"], :length => {:last_name => 10}) }
assert_nothing_raised { Person.connection.remove_index("people", ["last_name"]) }
assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"], :length => 10) }
assert_nothing_raised { Person.connection.remove_index("people", ["last_name", "first_name"]) }
assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"], :length => {:last_name => 10, :first_name => 20}) }
assert_nothing_raised { Person.connection.remove_index("people", ["last_name", "first_name"]) }
end
# quoting
@@ -852,6 +860,18 @@ if ActiveRecord::Base.connection.supports_migrations?
assert_equal "Tester", Person.new.first_name
end
unless current_adapter?(:PostgreSQLAdapter)
def test_change_column_type_default_should_change
old_columns = Person.connection.columns(Person.table_name, "#{name} Columns")
assert !old_columns.find { |c| c.name == 'data' }
assert_nothing_raised do
Person.connection.add_column "people", "data", :string, :default => ''
Person.connection.change_column "people", "data", :binary
end
end
end
def test_change_column_quotes_column_names
Person.connection.create_table :testings do |t|
t.column :select, :string

View File

@@ -142,6 +142,11 @@ class NamedScopeTest < ActiveRecord::TestCase
assert_equal authors(:david).posts & Post.containing_the_letter_a, authors(:david).posts.containing_the_letter_a
end
def test_named_scope_with_STI
assert_equal 3,Post.containing_the_letter_a.count
assert_equal 1,SpecialPost.containing_the_letter_a.count
end
def test_has_many_through_associations_have_access_to_named_scopes
assert_not_equal Comment.containing_the_letter_e, authors(:david).comments
assert !Comment.containing_the_letter_e.empty?
@@ -296,7 +301,7 @@ class NamedScopeTest < ActiveRecord::TestCase
end
def test_rand_should_select_a_random_object_from_proxy
assert_kind_of Topic, Topic.approved.rand
assert_kind_of Topic, Topic.approved.random_element
end
def test_should_use_where_in_query_for_named_scope

View File

@@ -62,6 +62,23 @@ class ValidationsTest < ActiveRecord::TestCase
assert_equal ["is Wrong Update"], r.errors[:title], "A reply with a bad content should contain an error"
end
def test_error_on_given_context
r = WrongReply.new
assert !r.valid?(:special_case)
assert "Invalid", r.errors[:title].join
r.title = "secret"
r.content = "Good"
assert r.valid?(:special_case)
r.title = nil
assert !r.save(:context => :special_case)
assert "Invalid", r.errors[:title].join
r.title = "secret"
assert r.save(:context => :special_case)
end
def test_invalid_record_exception
assert_raise(ActiveRecord::RecordInvalid) { WrongReply.create! }
assert_raise(ActiveRecord::RecordInvalid) { WrongReply.new.save! }
@@ -135,12 +152,6 @@ class ValidationsTest < ActiveRecord::TestCase
end
end
def test_create_without_validation_bang
count = WrongReply.count
assert_nothing_raised { WrongReply.new.save_without_validation! }
assert count+1, WrongReply.count
end
def test_validates_acceptance_of_with_non_existant_table
Object.const_set :IncorporealModel, Class.new(ActiveRecord::Base)

View File

@@ -69,6 +69,7 @@ class Post < ActiveRecord::Base
has_many :authors, :through => :categorizations
has_many :readers
has_many :readers_with_person, :include => :person, :class_name => "Reader"
has_many :people, :through => :readers
has_many :people_with_callbacks, :source=>:person, :through => :readers,
:before_add => lambda {|owner, reader| log(:added, :before, reader.first_name) },

View File

@@ -17,6 +17,7 @@ class WrongReply < Reply
validate :check_empty_title
validate :check_content_mismatch, :on => :create
validate :check_wrong_update, :on => :update
validate :check_title_is_secret, :on => :special_case
def check_empty_title
errors[:title] << "Empty" unless attribute_present?("title")
@@ -39,6 +40,10 @@ class WrongReply < Reply
def check_wrong_update
errors[:title] << "is Wrong Update" if attribute_present?("title") && title == "Wrong Update"
end
def check_title_is_secret
errors[:title] << "Invalid" unless title == "secret"
end
end
class SillyReply < Reply

View File

@@ -551,11 +551,9 @@ module ActiveResource
@headers ||= {}
end
# Do not include any modules in the default element name. This makes it easier to seclude ARes objects
# in a separate namespace without having to set element_name repeatedly.
attr_accessor_with_default(:element_name) { ActiveSupport::Inflector.underscore(to_s.split("::").last) } #:nodoc:
attr_accessor_with_default(:element_name) { model_name.element } #:nodoc:
attr_accessor_with_default(:collection_name) { ActiveSupport::Inflector.pluralize(element_name) } #:nodoc:
attr_accessor_with_default(:primary_key, 'id') #:nodoc:
# Gets the \prefix for a resource's nested URL (e.g., <tt>prefix/collectionname/1.xml</tt>)
@@ -1295,6 +1293,14 @@ module ActiveResource
end
end
def to_json(options={})
super({ :root => self.class.element_name }.merge(options))
end
def to_xml(options={})
super({ :root => self.class.element_name }.merge(options))
end
protected
def connection(refresh = false)
self.class.connection(refresh)

View File

@@ -42,7 +42,7 @@ module ActiveResource # :nodoc:
# TODO: We should eventually support all of these:
# %w( string text integer float decimal datetime timestamp time date binary boolean ).each do |attr_type|
KNOWN_ATTRIBUTE_TYPES.each do |attr_type|
class_eval <<-EOV
class_eval <<-EOV, __FILE__, __LINE__ + 1
def #{attr_type.to_s}(*args)
options = args.extract_options!
attr_names = args

View File

@@ -2,6 +2,7 @@ require 'abstract_unit'
require "fixtures/person"
require "fixtures/customer"
require "fixtures/street_address"
require "fixtures/sound"
require "fixtures/beast"
require "fixtures/proxy"
require 'active_support/json'
@@ -563,6 +564,10 @@ class BaseTest < Test::Unit::TestCase
assert_equal '/people/Greg/addresses/1.xml', StreetAddress.element_path(1, 'person_id' => 'Greg')
end
def test_module_element_path
assert_equal '/sounds/1.xml', Asset::Sound.element_path(1)
end
def test_custom_element_path_with_redefined_to_param
Person.module_eval do
alias_method :original_to_param_element_path, :to_param
@@ -1009,25 +1014,66 @@ class BaseTest < Test::Unit::TestCase
def test_to_xml
matz = Person.find(1)
xml = matz.encode
encode = matz.encode
xml = matz.to_xml
assert encode, xml
assert xml.include?('<?xml version="1.0" encoding="UTF-8"?>')
assert xml.include?('<name>Matz</name>')
assert xml.include?('<id type="integer">1</id>')
end
def test_to_xml_with_element_name
old_elem_name = Person.element_name
matz = Person.find(1)
Person.element_name = 'ruby_creator'
encode = matz.encode
xml = matz.to_xml
assert encode, xml
assert xml.include?('<?xml version="1.0" encoding="UTF-8"?>')
assert xml.include?('<ruby-creator>')
assert xml.include?('<name>Matz</name>')
assert xml.include?('<id type="integer">1</id>')
assert xml.include?('</ruby-creator>')
ensure
Person.element_name = old_elem_name
end
def test_to_json
Person.include_root_in_json = true
Person.format = :json
joe = Person.find(6)
json = joe.encode
encode = joe.encode
json = joe.to_json
Person.format = :xml
assert encode, json
assert_match %r{^\{"person":\{"person":\{}, json
assert_match %r{"id":6}, json
assert_match %r{"name":"Joe"}, json
assert_match %r{\}\}\}$}, json
end
def test_to_json_with_element_name
old_elem_name = Person.element_name
Person.include_root_in_json = true
Person.format = :json
joe = Person.find(6)
Person.element_name = 'ruby_creator'
encode = joe.encode
json = joe.to_json
Person.format = :xml
assert encode, json
assert_match %r{^\{"ruby_creator":\{"person":\{}, json
assert_match %r{"id":6}, json
assert_match %r{"name":"Joe"}, json
assert_match %r{\}\}\}$}, json
ensure
Person.element_name = old_elem_name
end
def test_to_param_quacks_like_active_record
new_person = Person.new
assert_nil new_person.to_param

5
activeresource/test/fixtures/sound.rb vendored Normal file
View File

@@ -0,0 +1,5 @@
module Asset
class Sound < ActiveResource::Base
self.site = "http://37s.sunrise.i:3000"
end
end

View File

@@ -1,5 +1,7 @@
*Rails 3.0.0 [beta 4/release candidate] (unreleased)*
* Defines prev_(month|year) in Date and Time, and deprecates last_(month|year). [fxn]
* Aliases Date#sunday to Date#end_of_week. [fxn]
* Backports Date#>> from 1.9 so that calculations do the right thing around the calendar reform. [fxn]

View File

@@ -1,6 +1,16 @@
class Array
# This method is deprecated because it masks Kernel#rand within the Array class itself,
# which may be used by a 3rd party library extending Array in turn. See
#
# https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/4555
#
def rand # :nodoc:
ActiveSupport::Deprecation.warn "Array#rand is deprecated, use random_element instead", caller
random_element
end
# Returns a random element from the array.
def rand
def random_element
self[Kernel.rand(length)]
end
end

View File

@@ -2,6 +2,7 @@ require 'date'
require 'active_support/duration'
require 'active_support/core_ext/time/zones'
require 'active_support/core_ext/object/acts_like'
require 'active_support/deprecation'
class Date
if RUBY_VERSION < '1.9'
@@ -146,20 +147,30 @@ class Date
advance(:years => years)
end
# Short-hand for years_ago(1)
def last_year
years_ago(1)
def last_year # :nodoc:
ActiveSupport::Deprecation.warn("Date#last_year has been deprecated, please use Date#prev_year instead", caller)
prev_year
end
# Shorthand for years_ago(1)
def prev_year
years_ago(1)
end unless method_defined?(:prev_year)
# Short-hand for years_since(1)
def next_year
years_since(1)
end unless method_defined?(:next_year)
# Short-hand for months_ago(1)
def last_month
months_ago(1)
def last_month # :nodoc:
ActiveSupport::Deprecation.warn("Date#last_month has been deprecated, please use Date#prev_month instead", caller)
prev_month
end
# Short-hand for months_ago(1)
def prev_month
months_ago(1)
end unless method_defined?(:prev_month)
# Short-hand for months_since(1)
def next_month

View File

@@ -3,7 +3,7 @@ require 'active_support/core_ext/class/attribute_accessors'
# Adds the 'around_level' method to Logger.
class Logger #:nodoc:
def self.define_around_helper(level)
module_eval <<-end_eval
module_eval <<-end_eval, __FILE__, __LINE__ + 1
def around_#{level}(before_message, after_message, &block) # def around_debug(before_message, after_message, &block)
self.#{level}(before_message) # self.debug(before_message)
return_value = block.call(self) # return_value = block.call(self)

View File

@@ -1,3 +1,5 @@
require 'active_support/option_merger'
class Object
# An elegant way to factor duplication out of options passed to a series of
# method calls. Each method called in the block, with the block variable as

View File

@@ -2,6 +2,7 @@ require 'active_support/duration'
require 'active_support/core_ext/date/acts_like'
require 'active_support/core_ext/date/calculations'
require 'active_support/core_ext/date_time/conversions'
require 'active_support/deprecation'
class Time
COMMON_YEAR_DAYS_IN_MONTH = [nil, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
@@ -132,8 +133,13 @@ class Time
advance(:years => years)
end
def last_year # :nodoc:
ActiveSupport::Deprecation.warn("Time#last_year has been deprecated, please use Time#prev_year instead", caller)
prev_year
end
# Short-hand for years_ago(1)
def last_year
def prev_year
years_ago(1)
end
@@ -142,9 +148,13 @@ class Time
years_since(1)
end
def last_month # :nodoc:
ActiveSupport::Deprecation.warn("Time#last_month has been deprecated, please use Time#prev_month instead", caller)
prev_month
end
# Short-hand for months_ago(1)
def last_month
def prev_month
months_ago(1)
end

View File

@@ -2,16 +2,26 @@ module ActiveSupport
@load_hooks = Hash.new {|h,k| h[k] = [] }
@loaded = {}
def self.on_load(name, &block)
def self.on_load(name, options = {}, &block)
if base = @loaded[name]
base.instance_eval(&block)
execute_hook(base, options, block)
else
@load_hooks[name] << block
@load_hooks[name] << [block, options]
end
end
def self.execute_hook(base, options, block)
if options[:yield]
block.call(base)
else
base.instance_eval(&block)
end
end
def self.run_load_hooks(name, base = Object)
@load_hooks[name].each { |hook| base.instance_eval(&hook) }
@loaded[name] = base
@load_hooks[name].each do |hook, options|
execute_hook(base, options, hook)
end
end
end

View File

@@ -73,7 +73,7 @@ class BufferedLoggerTest < Test::Unit::TestCase
end
@logger.flush
assert !@output.string.empty?, @logger.send(:buffer).size
assert !@output.string.empty?, @logger.send(:buffer).size.to_s
end
define_method "test_disabling_auto_flush_with_#{disable.inspect}_should_flush_at_max_buffer_size_as_failsafe" do
@@ -86,7 +86,7 @@ class BufferedLoggerTest < Test::Unit::TestCase
end
@logger.info 'there it is.'
assert !@output.string.empty?, @logger.send(:buffer).size
assert !@output.string.empty?, @logger.send(:buffer).size.to_s
end
end

View File

@@ -375,7 +375,7 @@ module LocalCacheBehavior
def test_local_cache_of_write_nil
@cache.with_local_cache do
assert true, @cache.write('foo', nil)
assert @cache.write('foo', nil)
assert_nil @cache.read('foo')
@peek.write('foo', 'bar')
assert_nil @cache.read('foo')
@@ -394,7 +394,7 @@ module LocalCacheBehavior
@cache.with_local_cache do
@cache.write('foo', 'bar')
@peek.delete('foo')
assert true, @cache.exist?('foo')
assert @cache.exist?('foo')
end
end

View File

@@ -358,15 +358,19 @@ class ArrayUniqByTests < Test::Unit::TestCase
end
end
class ArrayExtRandomTests < Test::Unit::TestCase
class ArrayExtRandomTests < ActiveSupport::TestCase
def test_random_element_from_array
assert_nil [].rand
assert_nil [].random_element
Kernel.expects(:rand).with(1).returns(0)
assert_equal 'x', ['x'].rand
assert_equal 'x', ['x'].random_element
Kernel.expects(:rand).with(3).returns(1)
assert_equal 2, [1, 2, 3].rand
assert_equal 2, [1, 2, 3].random_element
end
def test_deprecated_rand_on_array
assert_deprecated { [].rand }
end
end

View File

@@ -1,7 +1,7 @@
require 'abstract_unit'
require 'active_support/time'
class DateExtCalculationsTest < Test::Unit::TestCase
class DateExtCalculationsTest < ActiveSupport::TestCase
def test_to_s
date = Date.new(2005, 2, 21)
assert_equal "2005-02-21", date.to_s
@@ -145,16 +145,20 @@ class DateExtCalculationsTest < Test::Unit::TestCase
assert_equal Date.new(2005,2,28), Date.new(2004,2,29).years_since(1) # 1 year since leap day
end
def test_last_year
assert_equal Date.new(2004,6,5), Date.new(2005,6,5).last_year
def test_last_year_is_deprecated
assert_deprecated { Date.today.last_year }
end
def test_last_year_in_leap_years
assert_equal Date.new(1999,2,28), Date.new(2000,2,29).last_year
def test_prev_year
assert_equal Date.new(2004,6,5), Date.new(2005,6,5).prev_year
end
def test_last_year_in_calendar_reform
assert_equal Date.new(1582,10,4), Date.new(1583,10,14).last_year
def test_prev_year_in_leap_years
assert_equal Date.new(1999,2,28), Date.new(2000,2,29).prev_year
end
def test_prev_year_in_calendar_reform
assert_equal Date.new(1582,10,4), Date.new(1583,10,14).prev_year
end
def test_next_year
@@ -225,8 +229,12 @@ class DateExtCalculationsTest < Test::Unit::TestCase
assert_equal Date.new(2005, 9, 30), Date.new(2005, 8, 31).next_month
end
def test_last_month_on_31st
assert_equal Date.new(2004, 2, 29), Date.new(2004, 3, 31).last_month
def test_last_month_is_deprecated
assert_deprecated { Date.today.last_month }
end
def test_prev_month_on_31st
assert_equal Date.new(2004, 2, 29), Date.new(2004, 3, 31).prev_month
end
def test_yesterday_constructor

View File

@@ -127,8 +127,8 @@ class DateTimeExtCalculationsTest < Test::Unit::TestCase
assert_equal DateTime.civil(2005,2,28,10), DateTime.civil(2004,2,29,10,0,0).years_since(1) # 1 year since leap day
end
def test_last_year
assert_equal DateTime.civil(2004,6,5,10), DateTime.civil(2005,6,5,10,0,0).last_year
def test_prev_year
assert_equal DateTime.civil(2004,6,5,10), DateTime.civil(2005,6,5,10,0,0).prev_year
end
def test_next_year
@@ -200,8 +200,8 @@ class DateTimeExtCalculationsTest < Test::Unit::TestCase
assert_equal DateTime.civil(2005, 9, 30), DateTime.civil(2005, 8, 31).next_month
end
def test_last_month_on_31st
assert_equal DateTime.civil(2004, 2, 29), DateTime.civil(2004, 3, 31).last_month
def test_prev_month_on_31st
assert_equal DateTime.civil(2004, 2, 29), DateTime.civil(2004, 3, 31).prev_month
end
def test_xmlschema

View File

@@ -1,7 +1,7 @@
require 'abstract_unit'
require 'active_support/time'
class TimeExtCalculationsTest < Test::Unit::TestCase
class TimeExtCalculationsTest < ActiveSupport::TestCase
def test_seconds_since_midnight
assert_equal 1,Time.local(2005,1,1,0,0,1).seconds_since_midnight
assert_equal 60,Time.local(2005,1,1,0,1,0).seconds_since_midnight
@@ -166,8 +166,12 @@ class TimeExtCalculationsTest < Test::Unit::TestCase
# assert_equal Time.local(2182,6,5,10), Time.local(2005,6,5,10,0,0).years_since(177)
end
def test_last_year
assert_equal Time.local(2004,6,5,10), Time.local(2005,6,5,10,0,0).last_year
def test_last_year_is_deprecated
assert_deprecated { Time.now.last_year }
end
def test_prev_year
assert_equal Time.local(2004,6,5,10), Time.local(2005,6,5,10,0,0).prev_year
end
def test_next_year
@@ -615,8 +619,12 @@ class TimeExtCalculationsTest < Test::Unit::TestCase
assert_equal Time.local(2005, 9, 30), Time.local(2005, 8, 31).next_month
end
def test_last_month_on_31st
assert_equal Time.local(2004, 2, 29), Time.local(2004, 3, 31).last_month
def test_last_month_is_deprecated
assert_deprecated { Time.now.last_month }
end
def test_prev_month_on_31st
assert_equal Time.local(2004, 2, 29), Time.local(2004, 3, 31).prev_month
end
def test_xmlschema_is_available

View File

@@ -0,0 +1,67 @@
require 'abstract_unit'
class LazyLoadHooksTest < ActiveSupport::TestCase
def test_basic_hook
i = 0
ActiveSupport.on_load(:basic_hook) { i += 1 }
ActiveSupport.run_load_hooks(:basic_hook)
assert_equal 1, i
end
def test_hook_registered_after_run
i = 0
ActiveSupport.run_load_hooks(:registered_after)
assert_equal 0, i
ActiveSupport.on_load(:registered_after) { i += 1 }
assert_equal 1, i
end
def test_hook_receives_a_context
i = 0
ActiveSupport.on_load(:contextual) { i += incr }
assert_equal 0, i
ActiveSupport.run_load_hooks(:contextual, FakeContext.new(2))
assert_equal 2, i
end
def test_hook_receives_a_context_afterward
i = 0
ActiveSupport.run_load_hooks(:contextual_after, FakeContext.new(2))
assert_equal 0, i
ActiveSupport.on_load(:contextual_after) { i += incr }
assert_equal 2, i
end
def test_hook_with_yield_true
i = 0
ActiveSupport.on_load(:contextual_yield, :yield => true) do |obj|
i += obj.incr + incr_amt
end
assert_equal 0, i
ActiveSupport.run_load_hooks(:contextual_yield, FakeContext.new(2))
assert_equal 7, i
end
def test_hook_with_yield_true_afterward
i = 0
ActiveSupport.run_load_hooks(:contextual_yield_after, FakeContext.new(2))
assert_equal 0, i
ActiveSupport.on_load(:contextual_yield_after, :yield => true) do |obj|
i += obj.incr + incr_amt
end
assert_equal 7, i
end
private
def incr_amt
5
end
class FakeContext
attr_reader :incr
def initialize(incr)
@incr = incr
end
end
end

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