mirror of
https://github.com/github/rails.git
synced 2026-04-26 03:00:59 -04:00
Merge branch 'master' of git://github.com/rails/rails
This commit is contained in:
3
Gemfile
3
Gemfile
@@ -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"
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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+$}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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!"
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
1
actionpack/test/fixtures/test/_customer_counter_with_as.erb
vendored
Normal file
1
actionpack/test/fixtures/test/_customer_counter_with_as.erb
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<%= client.name %><%= client_counter %>
|
||||
1
actionpack/test/fixtures/test/proper_block_detection.erb
vendored
Normal file
1
actionpack/test/fixtures/test/proper_block_detection.erb
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<%= @todo %>
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -767,6 +767,62 @@ class FormOptionsHelperTest < ActionView::TestCase
|
||||
html
|
||||
end
|
||||
|
||||
def test_options_for_select_with_element_attributes
|
||||
assert_dom_equal(
|
||||
"<option value=\"<Denmark>\" class=\"bold\"><Denmark></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=\"<Denmark>\"><Denmark></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=\"<Denmark>\"><Denmark></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("<code>")\"",
|
||||
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")] ),
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
#
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
#
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
12
activemodel/test/models/automobile.rb
Normal file
12
activemodel/test/models/automobile.rb
Normal 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
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -891,6 +891,7 @@ module ActiveRecord
|
||||
|
||||
instances.size == 1 ? instances.first : instances
|
||||
end
|
||||
private table_name
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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!
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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) },
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
5
activeresource/test/fixtures/sound.rb
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
module Asset
|
||||
class Sound < ActiveResource::Base
|
||||
self.site = "http://37s.sunrise.i:3000"
|
||||
end
|
||||
end
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
67
activesupport/test/lazy_load_hooks_test.rb
Normal file
67
activesupport/test/lazy_load_hooks_test.rb
Normal 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
Reference in New Issue
Block a user