Merge branch 'master' of github.com:rails/rails
This commit is contained in:
commit
7475b43cdb
18
.travis.yml
18
.travis.yml
@ -1,3 +1,4 @@
|
||||
language: ruby
|
||||
script: 'ci/travis.rb'
|
||||
before_install:
|
||||
- travis_retry gem install bundler
|
||||
@ -10,13 +11,16 @@ rvm:
|
||||
- rbx-2
|
||||
- jruby
|
||||
env:
|
||||
- "GEM=railties"
|
||||
- "GEM=ap"
|
||||
- "GEM=am,amo,as,av,aj"
|
||||
- "GEM=ar:mysql"
|
||||
- "GEM=ar:mysql2"
|
||||
- "GEM=ar:sqlite3"
|
||||
- "GEM=ar:postgresql"
|
||||
global:
|
||||
- JRUBY_OPTS='-J-XX:+TieredCompilation -J-XX:TieredStopAtLevel=1 -J-Djruby.compile.mode=OFF -J-Djruby.compile.invokedynamic=false -J-Xmx1024M'
|
||||
matrix:
|
||||
- "GEM=railties"
|
||||
- "GEM=ap"
|
||||
- "GEM=am,amo,as,av,aj"
|
||||
- "GEM=ar:mysql"
|
||||
- "GEM=ar:mysql2"
|
||||
- "GEM=ar:sqlite3"
|
||||
- "GEM=ar:postgresql"
|
||||
matrix:
|
||||
allow_failures:
|
||||
- rvm: 1.9.3
|
||||
|
@ -1,7 +1,9 @@
|
||||
* Added #deliver_later in addition to #deliver, which will enqueue a job to render and
|
||||
deliver the mail instead of delivering it right at that moment. The job is enqueued
|
||||
using the new Active Job framework in Rails, and will use whatever queue is configured for Rails.
|
||||
|
||||
* Added #deliver_later, #deliver_now and deprecate #deliver in favour of
|
||||
#deliver_now. #deliver_later will enqueue a job to render and deliver
|
||||
the mail instead of delivering it right at that moment. The job is enqueued
|
||||
using the new Active Job framework in Rails, and will use whatever queue is
|
||||
configured for Rails.
|
||||
|
||||
*DHH/Abdelkader Boudih/Cristian Bica*
|
||||
|
||||
* Make ActionMailer::Previews methods class methods. Previously they were
|
||||
|
@ -65,12 +65,12 @@ In order to send mails, you simply call the method and then call +deliver+ on th
|
||||
|
||||
Calling the method returns a Mail Message object:
|
||||
|
||||
message = Notifier.welcome("david@loudthinking.com") # => Returns a Mail::Message object
|
||||
message.deliver # => delivers the email
|
||||
message = Notifier.welcome("david@loudthinking.com") # => Returns a Mail::Message object
|
||||
message.deliver_now # => delivers the email
|
||||
|
||||
Or you can just chain the methods together like:
|
||||
|
||||
Notifier.welcome("david@loudthinking.com").deliver # Creates the email and sends it immediately
|
||||
Notifier.welcome("david@loudthinking.com").deliver_now # Creates the email and sends it immediately
|
||||
|
||||
== Setting defaults
|
||||
|
||||
|
@ -21,6 +21,7 @@
|
||||
|
||||
s.add_dependency 'actionpack', version
|
||||
s.add_dependency 'actionview', version
|
||||
s.add_dependency 'activejob', version
|
||||
|
||||
s.add_dependency 'mail', ['~> 2.5', '>= 2.5.4']
|
||||
s.add_dependency 'rails-dom-testing', '~> 1.0', '>= 1.0.2'
|
||||
|
@ -138,9 +138,20 @@ module ActionMailer
|
||||
# Once a mailer action and template are defined, you can deliver your message or create it and save it
|
||||
# for delivery later:
|
||||
#
|
||||
# Notifier.welcome(david).deliver # sends the email
|
||||
# mail = Notifier.welcome(david) # => a Mail::Message object
|
||||
# mail.deliver # sends the email
|
||||
# Notifier.welcome(User.first).deliver_now # sends the email
|
||||
# mail = Notifier.welcome(User.first) # => an ActionMailer::MessageDelivery object
|
||||
# mail.deliver_now # sends the email
|
||||
#
|
||||
# The <tt>ActionMailer::MessageDelivery</tt> class is a wrapper around a <tt>Mail::Message</tt> object. If
|
||||
# you want direct access to the <tt>Mail::Message</tt> object you can call the <tt>message</tt> method on
|
||||
# the <tt>ActionMailer::MessageDelivery</tt> object.
|
||||
#
|
||||
# Notifier.welcome(User.first).message # => a Mail::Message object
|
||||
#
|
||||
# Action Mailer is nicely integrated with Active Job so you can send emails in the background (example: outside
|
||||
# of the request-response cycle, so the user doesn't have to wait on it):
|
||||
#
|
||||
# Notifier.welcome(User.first).deliver_later # enqueue the email sending to Active Job
|
||||
#
|
||||
# You never instantiate your mailer class. Rather, you just call the method you defined on the class itself.
|
||||
#
|
||||
@ -322,8 +333,8 @@ module ActionMailer
|
||||
# end
|
||||
#
|
||||
# Methods must return a <tt>Mail::Message</tt> object which can be generated by calling the mailer
|
||||
# method without the additional <tt>deliver</tt>. The location of the mailer previews
|
||||
# directory can be configured using the <tt>preview_path</tt> option which has a default
|
||||
# method without the additional <tt>deliver_now</tt> / <tt>deliver_later</tt>. The location of the
|
||||
# mailer previews directory can be configured using the <tt>preview_path</tt> option which has a default
|
||||
# of <tt>test/mailers/previews</tt>:
|
||||
#
|
||||
# config.action_mailer.preview_path = "#{Rails.root}/lib/mailer_previews"
|
||||
|
@ -1,10 +1,10 @@
|
||||
require 'active_job'
|
||||
|
||||
module ActionMailer
|
||||
class DeliveryJob < ActiveJob::Base
|
||||
class DeliveryJob < ActiveJob::Base #:nodoc:
|
||||
queue_as :mailers
|
||||
|
||||
def perform(mailer, mail_method, delivery_method, *args)
|
||||
def perform(mailer, mail_method, delivery_method, *args) #:nodoc#
|
||||
mailer.constantize.public_send(mail_method, *args).send(delivery_method)
|
||||
end
|
||||
end
|
||||
|
@ -1,45 +1,112 @@
|
||||
require 'delegate'
|
||||
|
||||
module ActionMailer
|
||||
|
||||
# The <tt>ActionMailer::MessageDelivery</tt> class is used by
|
||||
# <tt>ActionMailer::Base</tt> when creating a new mailer.
|
||||
# <tt>MessageDelivery</tt> is a wrapper (+Delegator+ subclass) around a lazy
|
||||
# created <tt>Mail::Message</tt>. You can get direct access to the
|
||||
# <tt>Mail::Message</tt>, deliver the email or schedule the email to be sent
|
||||
# through Active Job.
|
||||
#
|
||||
# Notifier.welcome(User.first) # an ActionMailer::MessageDelivery object
|
||||
# Notifier.welcome(User.first).deliver_now # sends the email
|
||||
# Notifier.welcome(User.first).deliver_later # enqueue email delivery as a job through Active Job
|
||||
# Notifier.welcome(User.first).message # a Mail::Message object
|
||||
class MessageDelivery < Delegator
|
||||
def initialize(mailer, mail_method, *args)
|
||||
def initialize(mailer, mail_method, *args) #:nodoc:
|
||||
@mailer = mailer
|
||||
@mail_method = mail_method
|
||||
@args = args
|
||||
end
|
||||
|
||||
def __getobj__
|
||||
def __getobj__ #:nodoc:
|
||||
@obj ||= @mailer.send(:new, @mail_method, *@args).message
|
||||
end
|
||||
|
||||
def __setobj__(obj)
|
||||
def __setobj__(obj) #:nodoc:
|
||||
@obj = obj
|
||||
end
|
||||
|
||||
def message #:nodoc:
|
||||
# Returns the Mail::Message object
|
||||
def message
|
||||
__getobj__
|
||||
end
|
||||
|
||||
# Enqueues the email to be delivered through Active Job. When the
|
||||
# job runs it will send the email using +deliver_now!+. That means
|
||||
# that the message will be sent bypassing checking +perform_deliveries+
|
||||
# and +raise_delivery_errors+, so use with caution.
|
||||
#
|
||||
# Notifier.welcome(User.first).deliver_later
|
||||
# Notifier.welcome(User.first).deliver_later(in: 1.hour)
|
||||
# Notifier.welcome(User.first).deliver_later(at: 10.hours.from_now)
|
||||
#
|
||||
# Options:
|
||||
#
|
||||
# * <tt>:in</tt> - Enqueue the email to be delivered with a delay
|
||||
# * <tt>:at</tt> - Enqueue the email to be delivered at (after) a specific date / time
|
||||
def deliver_later!(options={})
|
||||
enqueue_delivery :deliver!, options
|
||||
enqueue_delivery :deliver_now!, options
|
||||
end
|
||||
|
||||
# Enqueues the email to be delivered through Active Job. When the
|
||||
# job runs it will send the email using +deliver_now+.
|
||||
#
|
||||
# Notifier.welcome(User.first).deliver_later
|
||||
# Notifier.welcome(User.first).deliver_later(in: 1.hour)
|
||||
# Notifier.welcome(User.first).deliver_later(at: 10.hours.from_now)
|
||||
#
|
||||
# Options:
|
||||
#
|
||||
# * <tt>:in</tt> - Enqueue the email to be delivered with a delay
|
||||
# * <tt>:at</tt> - Enqueue the email to be delivered at (after) a specific date / time
|
||||
def deliver_later(options={})
|
||||
enqueue_delivery :deliver, options
|
||||
enqueue_delivery :deliver_now, options
|
||||
end
|
||||
|
||||
# Delivers an email without checking +perform_deliveries+ and +raise_delivery_errors+,
|
||||
# so use with caution.
|
||||
#
|
||||
# Notifier.welcome(User.first).deliver_now!
|
||||
#
|
||||
def deliver_now!
|
||||
message.deliver!
|
||||
end
|
||||
|
||||
# Delivers an email:
|
||||
#
|
||||
# Notifier.welcome(User.first).deliver_now
|
||||
#
|
||||
def deliver_now
|
||||
message.deliver
|
||||
end
|
||||
|
||||
def deliver! #:nodoc:
|
||||
ActiveSupport::Deprecation.warn "#deliver! is deprecated and will be removed in Rails 5. " \
|
||||
"Use #deliver_now! to deliver immediately or #deliver_later! to deliver through Active Job."
|
||||
deliver_now!
|
||||
end
|
||||
|
||||
def deliver #:nodoc:
|
||||
ActiveSupport::Deprecation.warn "#deliver is deprecated and will be removed in Rails 5. " \
|
||||
"Use #deliver_now to deliver immediately or #deliver_later to deliver through Active Job."
|
||||
deliver_now
|
||||
end
|
||||
|
||||
private
|
||||
def enqueue_delivery(delivery_method, options={})
|
||||
args = @mailer.name, @mail_method.to_s, delivery_method.to_s, *@args
|
||||
enqueue_method = :enqueue
|
||||
if options[:at]
|
||||
enqueue_method = :enqueue_at
|
||||
args.unshift options[:at]
|
||||
elsif options[:in]
|
||||
enqueue_method = :enqueue_in
|
||||
args.unshift options[:in]
|
||||
|
||||
def enqueue_delivery(delivery_method, options={})
|
||||
args = @mailer.name, @mail_method.to_s, delivery_method.to_s, *@args
|
||||
enqueue_method = :enqueue
|
||||
if options[:at]
|
||||
enqueue_method = :enqueue_at
|
||||
args.unshift options[:at]
|
||||
elsif options[:in]
|
||||
enqueue_method = :enqueue_in
|
||||
args.unshift options[:in]
|
||||
end
|
||||
ActionMailer::DeliveryJob.send enqueue_method, *args
|
||||
end
|
||||
ActionMailer::DeliveryJob.send enqueue_method, *args
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1,3 +1,4 @@
|
||||
require 'active_job/railtie'
|
||||
require "action_mailer"
|
||||
require "rails"
|
||||
require "abstract_controller/railties/routes_helpers"
|
||||
|
@ -6,9 +6,9 @@ module TestHelper
|
||||
#
|
||||
# def test_emails
|
||||
# assert_emails 0
|
||||
# ContactMailer.welcome.deliver
|
||||
# ContactMailer.welcome.deliver_now
|
||||
# assert_emails 1
|
||||
# ContactMailer.welcome.deliver
|
||||
# ContactMailer.welcome.deliver_now
|
||||
# assert_emails 2
|
||||
# end
|
||||
#
|
||||
@ -17,12 +17,12 @@ module TestHelper
|
||||
#
|
||||
# def test_emails_again
|
||||
# assert_emails 1 do
|
||||
# ContactMailer.welcome.deliver
|
||||
# ContactMailer.welcome.deliver_now
|
||||
# end
|
||||
#
|
||||
# assert_emails 2 do
|
||||
# ContactMailer.welcome.deliver
|
||||
# ContactMailer.welcome.deliver
|
||||
# ContactMailer.welcome.deliver_now
|
||||
# ContactMailer.welcome.deliver_now
|
||||
# end
|
||||
# end
|
||||
def assert_emails(number)
|
||||
@ -40,7 +40,7 @@ def assert_emails(number)
|
||||
#
|
||||
# def test_emails
|
||||
# assert_no_emails
|
||||
# ContactMailer.welcome.deliver
|
||||
# ContactMailer.welcome.deliver_now
|
||||
# assert_emails 1
|
||||
# end
|
||||
#
|
||||
|
@ -32,15 +32,6 @@ def self.root
|
||||
end
|
||||
end
|
||||
|
||||
def set_delivery_method(method)
|
||||
@old_delivery_method = ActionMailer::Base.delivery_method
|
||||
ActionMailer::Base.delivery_method = method
|
||||
end
|
||||
|
||||
def restore_delivery_method
|
||||
ActionMailer::Base.delivery_method = @old_delivery_method
|
||||
end
|
||||
|
||||
# Skips the current run on Rubinius using Minitest::Assertions#skip
|
||||
def rubinius_skip(message = '')
|
||||
skip message if RUBY_ENGINE == 'rbx'
|
||||
|
@ -26,7 +26,7 @@ def test_assert_select_email
|
||||
assert_select_email {}
|
||||
end
|
||||
|
||||
AssertSelectMailer.test("<div><p>foo</p><p>bar</p></div>").deliver
|
||||
AssertSelectMailer.test("<div><p>foo</p><p>bar</p></div>").deliver_now
|
||||
assert_select_email do
|
||||
assert_select "div:root" do
|
||||
assert_select "p:first-child", "foo"
|
||||
@ -36,7 +36,7 @@ def test_assert_select_email
|
||||
end
|
||||
|
||||
def test_assert_select_email_multipart
|
||||
AssertMultipartSelectMailer.test(html: "<div><p>foo</p><p>bar</p></div>", text: 'foo bar').deliver
|
||||
AssertMultipartSelectMailer.test(html: "<div><p>foo</p><p>bar</p></div>", text: 'foo bar').deliver_now
|
||||
assert_select_email do
|
||||
assert_select "div:root" do
|
||||
assert_select "p:first-child", "foo"
|
||||
|
@ -9,11 +9,8 @@ def email_with_asset
|
||||
end
|
||||
end
|
||||
|
||||
class AssetHostTest < ActiveSupport::TestCase
|
||||
class AssetHostTest < ActionMailer::TestCase
|
||||
def setup
|
||||
set_delivery_method :test
|
||||
ActionMailer::Base.perform_deliveries = true
|
||||
ActionMailer::Base.deliveries.clear
|
||||
AssetHostMailer.configure do |c|
|
||||
c.asset_host = "http://www.example.com"
|
||||
end
|
||||
|
@ -466,12 +466,12 @@ def welcome
|
||||
|
||||
test "calling deliver on the action should deliver the mail object" do
|
||||
BaseMailer.expects(:deliver_mail).once
|
||||
mail = BaseMailer.welcome.deliver
|
||||
mail = BaseMailer.welcome.deliver_now
|
||||
assert_equal 'The first email on new API!', mail.subject
|
||||
end
|
||||
|
||||
test "calling deliver on the action should increment the deliveries collection if using the test mailer" do
|
||||
BaseMailer.welcome.deliver
|
||||
BaseMailer.welcome.deliver_now
|
||||
assert_equal(1, BaseMailer.deliveries.length)
|
||||
end
|
||||
|
||||
@ -484,35 +484,35 @@ def welcome
|
||||
|
||||
# Rendering
|
||||
test "you can specify a different template for implicit render" do
|
||||
mail = BaseMailer.implicit_different_template('implicit_multipart').deliver
|
||||
mail = BaseMailer.implicit_different_template('implicit_multipart').deliver_now
|
||||
assert_equal("HTML Implicit Multipart", mail.html_part.body.decoded)
|
||||
assert_equal("TEXT Implicit Multipart", mail.text_part.body.decoded)
|
||||
end
|
||||
|
||||
test "should raise if missing template in implicit render" do
|
||||
assert_raises ActionView::MissingTemplate do
|
||||
BaseMailer.implicit_different_template('missing_template').deliver
|
||||
BaseMailer.implicit_different_template('missing_template').deliver_now
|
||||
end
|
||||
assert_equal(0, BaseMailer.deliveries.length)
|
||||
end
|
||||
|
||||
test "you can specify a different template for explicit render" do
|
||||
mail = BaseMailer.explicit_different_template('explicit_multipart_templates').deliver
|
||||
mail = BaseMailer.explicit_different_template('explicit_multipart_templates').deliver_now
|
||||
assert_equal("HTML Explicit Multipart Templates", mail.html_part.body.decoded)
|
||||
assert_equal("TEXT Explicit Multipart Templates", mail.text_part.body.decoded)
|
||||
end
|
||||
|
||||
test "you can specify a different layout" do
|
||||
mail = BaseMailer.different_layout('different_layout').deliver
|
||||
mail = BaseMailer.different_layout('different_layout').deliver_now
|
||||
assert_equal("HTML -- HTML", mail.html_part.body.decoded)
|
||||
assert_equal("PLAIN -- PLAIN", mail.text_part.body.decoded)
|
||||
end
|
||||
|
||||
test "you can specify the template path for implicit lookup" do
|
||||
mail = BaseMailer.welcome_from_another_path('another.path/base_mailer').deliver
|
||||
mail = BaseMailer.welcome_from_another_path('another.path/base_mailer').deliver_now
|
||||
assert_equal("Welcome from another path", mail.body.encoded)
|
||||
|
||||
mail = BaseMailer.welcome_from_another_path(['unknown/invalid', 'another.path/base_mailer']).deliver
|
||||
mail = BaseMailer.welcome_from_another_path(['unknown/invalid', 'another.path/base_mailer']).deliver_now
|
||||
assert_equal("Welcome from another path", mail.body.encoded)
|
||||
end
|
||||
|
||||
@ -542,13 +542,13 @@ def welcome
|
||||
test 'the view is not rendered when mail was never called' do
|
||||
mail = BaseMailer.without_mail_call
|
||||
assert_equal('', mail.body.to_s.strip)
|
||||
mail.deliver
|
||||
mail.deliver_now
|
||||
end
|
||||
|
||||
test 'the return value of mailer methods is not relevant' do
|
||||
mail = BaseMailer.with_nil_as_return_value
|
||||
assert_equal('Welcome', mail.body.to_s.strip)
|
||||
mail.deliver
|
||||
mail.deliver_now
|
||||
end
|
||||
|
||||
# Before and After hooks
|
||||
@ -568,7 +568,7 @@ def self.delivered_email(mail)
|
||||
ActionMailer::Base.register_observer(MyObserver)
|
||||
mail = BaseMailer.welcome
|
||||
MyObserver.expects(:delivered_email).with(mail)
|
||||
mail.deliver
|
||||
mail.deliver_now
|
||||
end
|
||||
end
|
||||
|
||||
@ -577,7 +577,7 @@ def self.delivered_email(mail)
|
||||
ActionMailer::Base.register_observer("BaseTest::MyObserver")
|
||||
mail = BaseMailer.welcome
|
||||
MyObserver.expects(:delivered_email).with(mail)
|
||||
mail.deliver
|
||||
mail.deliver_now
|
||||
end
|
||||
end
|
||||
|
||||
@ -586,7 +586,7 @@ def self.delivered_email(mail)
|
||||
ActionMailer::Base.register_observer(:"base_test/my_observer")
|
||||
mail = BaseMailer.welcome
|
||||
MyObserver.expects(:delivered_email).with(mail)
|
||||
mail.deliver
|
||||
mail.deliver_now
|
||||
end
|
||||
end
|
||||
|
||||
@ -596,7 +596,7 @@ def self.delivered_email(mail)
|
||||
mail = BaseMailer.welcome
|
||||
MyObserver.expects(:delivered_email).with(mail)
|
||||
MySecondObserver.expects(:delivered_email).with(mail)
|
||||
mail.deliver
|
||||
mail.deliver_now
|
||||
end
|
||||
end
|
||||
|
||||
@ -615,7 +615,7 @@ def self.previewing_email(mail); end
|
||||
ActionMailer::Base.register_interceptor(MyInterceptor)
|
||||
mail = BaseMailer.welcome
|
||||
MyInterceptor.expects(:delivering_email).with(mail)
|
||||
mail.deliver
|
||||
mail.deliver_now
|
||||
end
|
||||
end
|
||||
|
||||
@ -624,7 +624,7 @@ def self.previewing_email(mail); end
|
||||
ActionMailer::Base.register_interceptor("BaseTest::MyInterceptor")
|
||||
mail = BaseMailer.welcome
|
||||
MyInterceptor.expects(:delivering_email).with(mail)
|
||||
mail.deliver
|
||||
mail.deliver_now
|
||||
end
|
||||
end
|
||||
|
||||
@ -633,7 +633,7 @@ def self.previewing_email(mail); end
|
||||
ActionMailer::Base.register_interceptor(:"base_test/my_interceptor")
|
||||
mail = BaseMailer.welcome
|
||||
MyInterceptor.expects(:delivering_email).with(mail)
|
||||
mail.deliver
|
||||
mail.deliver_now
|
||||
end
|
||||
end
|
||||
|
||||
@ -643,7 +643,7 @@ def self.previewing_email(mail); end
|
||||
mail = BaseMailer.welcome
|
||||
MyInterceptor.expects(:delivering_email).with(mail)
|
||||
MySecondInterceptor.expects(:delivering_email).with(mail)
|
||||
mail.deliver
|
||||
mail.deliver_now
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -104,21 +104,21 @@ def welcome(hash={})
|
||||
|
||||
test "ActionMailer should be told when Mail gets delivered" do
|
||||
DeliveryMailer.expects(:deliver_mail).once
|
||||
DeliveryMailer.welcome.deliver
|
||||
DeliveryMailer.welcome.deliver_now
|
||||
end
|
||||
|
||||
test "delivery method can be customized per instance" do
|
||||
Mail::SMTP.any_instance.expects(:deliver!)
|
||||
email = DeliveryMailer.welcome.deliver
|
||||
email = DeliveryMailer.welcome.deliver_now
|
||||
assert_instance_of Mail::SMTP, email.delivery_method
|
||||
email = DeliveryMailer.welcome(delivery_method: :test).deliver
|
||||
email = DeliveryMailer.welcome(delivery_method: :test).deliver_now
|
||||
assert_instance_of Mail::TestMailer, email.delivery_method
|
||||
end
|
||||
|
||||
test "delivery method can be customized in subclasses not changing the parent" do
|
||||
DeliveryMailer.delivery_method = :test
|
||||
assert_equal :smtp, ActionMailer::Base.delivery_method
|
||||
email = DeliveryMailer.welcome.deliver
|
||||
email = DeliveryMailer.welcome.deliver_now
|
||||
assert_instance_of Mail::TestMailer, email.delivery_method
|
||||
end
|
||||
|
||||
@ -162,14 +162,14 @@ def welcome(hash={})
|
||||
test "non registered delivery methods raises errors" do
|
||||
DeliveryMailer.delivery_method = :unknown
|
||||
assert_raise RuntimeError do
|
||||
DeliveryMailer.welcome.deliver
|
||||
DeliveryMailer.welcome.deliver_now
|
||||
end
|
||||
end
|
||||
|
||||
test "undefined delivery methods raises errors" do
|
||||
DeliveryMailer.delivery_method = nil
|
||||
assert_raise RuntimeError do
|
||||
DeliveryMailer.welcome.deliver
|
||||
DeliveryMailer.welcome.deliver_now
|
||||
end
|
||||
end
|
||||
|
||||
@ -178,7 +178,7 @@ def welcome(hash={})
|
||||
begin
|
||||
DeliveryMailer.perform_deliveries = false
|
||||
Mail::Message.any_instance.expects(:deliver!).never
|
||||
DeliveryMailer.welcome.deliver
|
||||
DeliveryMailer.welcome.deliver_now
|
||||
ensure
|
||||
DeliveryMailer.perform_deliveries = old_perform_deliveries
|
||||
end
|
||||
@ -188,7 +188,7 @@ def welcome(hash={})
|
||||
old_perform_deliveries = DeliveryMailer.perform_deliveries
|
||||
begin
|
||||
DeliveryMailer.perform_deliveries = false
|
||||
DeliveryMailer.welcome.deliver
|
||||
DeliveryMailer.welcome.deliver_now
|
||||
assert_equal [], DeliveryMailer.deliveries
|
||||
ensure
|
||||
DeliveryMailer.perform_deliveries = old_perform_deliveries
|
||||
@ -198,14 +198,14 @@ def welcome(hash={})
|
||||
test "raise errors on bogus deliveries" do
|
||||
DeliveryMailer.delivery_method = BogusDelivery
|
||||
assert_raise RuntimeError do
|
||||
DeliveryMailer.welcome.deliver
|
||||
DeliveryMailer.welcome.deliver_now
|
||||
end
|
||||
end
|
||||
|
||||
test "does not increment the deliveries collection on error" do
|
||||
DeliveryMailer.delivery_method = BogusDelivery
|
||||
assert_raise RuntimeError do
|
||||
DeliveryMailer.welcome.deliver
|
||||
DeliveryMailer.welcome.deliver_now
|
||||
end
|
||||
assert_equal [], DeliveryMailer.deliveries
|
||||
end
|
||||
@ -216,7 +216,7 @@ def welcome(hash={})
|
||||
DeliveryMailer.delivery_method = BogusDelivery
|
||||
DeliveryMailer.raise_delivery_errors = false
|
||||
assert_nothing_raised do
|
||||
DeliveryMailer.welcome.deliver
|
||||
DeliveryMailer.welcome.deliver_now
|
||||
end
|
||||
ensure
|
||||
DeliveryMailer.raise_delivery_errors = old_raise_delivery_errors
|
||||
@ -228,7 +228,7 @@ def welcome(hash={})
|
||||
begin
|
||||
DeliveryMailer.delivery_method = BogusDelivery
|
||||
DeliveryMailer.raise_delivery_errors = false
|
||||
DeliveryMailer.welcome.deliver
|
||||
DeliveryMailer.welcome.deliver_now
|
||||
assert_equal [], DeliveryMailer.deliveries
|
||||
ensure
|
||||
DeliveryMailer.raise_delivery_errors = old_raise_delivery_errors
|
||||
|
@ -17,7 +17,7 @@ def mail_with_i18n_subject(recipient)
|
||||
|
||||
class TestController < ActionController::Base
|
||||
def send_mail
|
||||
email = I18nTestMailer.mail_with_i18n_subject("test@localhost").deliver
|
||||
email = I18nTestMailer.mail_with_i18n_subject("test@localhost").deliver_now
|
||||
render text: "Mail sent - Subject: #{email.subject}"
|
||||
end
|
||||
end
|
||||
|
@ -22,7 +22,7 @@ def set_logger(logger)
|
||||
end
|
||||
|
||||
def test_deliver_is_notified
|
||||
BaseMailer.welcome.deliver
|
||||
BaseMailer.welcome.deliver_now
|
||||
wait
|
||||
|
||||
assert_equal(1, @logger.logged(:info).size)
|
||||
|
@ -1,9 +1,8 @@
|
||||
# encoding: utf-8
|
||||
gem 'activejob'
|
||||
require 'active_job'
|
||||
require 'abstract_unit'
|
||||
require 'active_job'
|
||||
require 'minitest/mock'
|
||||
require_relative 'mailers/delayed_mailer'
|
||||
require 'mailers/delayed_mailer'
|
||||
|
||||
class MessageDeliveryTest < ActiveSupport::TestCase
|
||||
|
||||
@ -37,6 +36,17 @@ class MessageDeliveryTest < ActiveSupport::TestCase
|
||||
assert_respond_to @mail, :deliver!
|
||||
end
|
||||
|
||||
test '.deliver is deprecated' do
|
||||
assert_deprecated do
|
||||
@mail.deliver
|
||||
end
|
||||
end
|
||||
test '.deliver! is deprecated' do
|
||||
assert_deprecated do
|
||||
@mail.deliver!
|
||||
end
|
||||
end
|
||||
|
||||
test 'should respond to .deliver_later' do
|
||||
assert_respond_to @mail, :deliver_later
|
||||
end
|
||||
@ -45,30 +55,40 @@ class MessageDeliveryTest < ActiveSupport::TestCase
|
||||
assert_respond_to @mail, :deliver_later!
|
||||
end
|
||||
|
||||
test 'should enqueue and run correctly in activejob' do
|
||||
test 'should respond to .deliver_now' do
|
||||
assert_respond_to @mail, :deliver_now
|
||||
end
|
||||
|
||||
test 'should respond to .deliver_now!' do
|
||||
assert_respond_to @mail, :deliver_now!
|
||||
end
|
||||
|
||||
def test_should_enqueue_and_run_correctly_in_activejob
|
||||
@mail.deliver_later!
|
||||
assert_equal 1 , ActionMailer::Base.deliveries.size
|
||||
assert_equal 1, ActionMailer::Base.deliveries.size
|
||||
ensure
|
||||
ActionMailer::Base.deliveries.clear
|
||||
end
|
||||
|
||||
test 'should enqueue the email with :deliver delivery method' do
|
||||
ret = ActionMailer::DeliveryJob.stub :enqueue, ->(*args){ args } do
|
||||
@mail.deliver_later
|
||||
end
|
||||
assert_equal ['DelayedMailer', 'test_message', 'deliver', 1, 2, 3], ret
|
||||
assert_equal ['DelayedMailer', 'test_message', 'deliver_now', 1, 2, 3], ret
|
||||
end
|
||||
|
||||
test 'should enqueue the email with :deliver! delivery method' do
|
||||
ret = ActionMailer::DeliveryJob.stub :enqueue, ->(*args){ args } do
|
||||
@mail.deliver_later!
|
||||
end
|
||||
assert_equal ['DelayedMailer', 'test_message', 'deliver!', 1, 2, 3], ret
|
||||
assert_equal ['DelayedMailer', 'test_message', 'deliver_now!', 1, 2, 3], ret
|
||||
end
|
||||
|
||||
test 'should enqueue a delivery with a delay' do
|
||||
ret = ActionMailer::DeliveryJob.stub :enqueue_in, ->(*args){ args } do
|
||||
@mail.deliver_later in: 600
|
||||
end
|
||||
assert_equal [600, 'DelayedMailer', 'test_message', 'deliver', 1, 2, 3], ret
|
||||
assert_equal [600, 'DelayedMailer', 'test_message', 'deliver_now', 1, 2, 3], ret
|
||||
end
|
||||
|
||||
test 'should enqueue a delivery at a specific time' do
|
||||
@ -76,7 +96,7 @@ class MessageDeliveryTest < ActiveSupport::TestCase
|
||||
ret = ActionMailer::DeliveryJob.stub :enqueue_at, ->(*args){ args } do
|
||||
@mail.deliver_later at: later_time
|
||||
end
|
||||
assert_equal [later_time, 'DelayedMailer', 'test_message', 'deliver', 1, 2, 3], ret
|
||||
assert_equal [later_time, 'DelayedMailer', 'test_message', 'deliver_now', 1, 2, 3], ret
|
||||
end
|
||||
|
||||
end
|
||||
|
@ -48,7 +48,7 @@ def test_read_fixture
|
||||
def test_assert_emails
|
||||
assert_nothing_raised do
|
||||
assert_emails 1 do
|
||||
TestHelperMailer.test.deliver
|
||||
TestHelperMailer.test.deliver_now
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -56,27 +56,27 @@ def test_assert_emails
|
||||
def test_repeated_assert_emails_calls
|
||||
assert_nothing_raised do
|
||||
assert_emails 1 do
|
||||
TestHelperMailer.test.deliver
|
||||
TestHelperMailer.test.deliver_now
|
||||
end
|
||||
end
|
||||
|
||||
assert_nothing_raised do
|
||||
assert_emails 2 do
|
||||
TestHelperMailer.test.deliver
|
||||
TestHelperMailer.test.deliver
|
||||
TestHelperMailer.test.deliver_now
|
||||
TestHelperMailer.test.deliver_now
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_assert_emails_with_no_block
|
||||
assert_nothing_raised do
|
||||
TestHelperMailer.test.deliver
|
||||
TestHelperMailer.test.deliver_now
|
||||
assert_emails 1
|
||||
end
|
||||
|
||||
assert_nothing_raised do
|
||||
TestHelperMailer.test.deliver
|
||||
TestHelperMailer.test.deliver
|
||||
TestHelperMailer.test.deliver_now
|
||||
TestHelperMailer.test.deliver_now
|
||||
assert_emails 3
|
||||
end
|
||||
end
|
||||
@ -92,7 +92,7 @@ def test_assert_no_emails
|
||||
def test_assert_emails_too_few_sent
|
||||
error = assert_raise ActiveSupport::TestCase::Assertion do
|
||||
assert_emails 2 do
|
||||
TestHelperMailer.test.deliver
|
||||
TestHelperMailer.test.deliver_now
|
||||
end
|
||||
end
|
||||
|
||||
@ -102,8 +102,8 @@ def test_assert_emails_too_few_sent
|
||||
def test_assert_emails_too_many_sent
|
||||
error = assert_raise ActiveSupport::TestCase::Assertion do
|
||||
assert_emails 1 do
|
||||
TestHelperMailer.test.deliver
|
||||
TestHelperMailer.test.deliver
|
||||
TestHelperMailer.test.deliver_now
|
||||
TestHelperMailer.test.deliver_now
|
||||
end
|
||||
end
|
||||
|
||||
@ -113,7 +113,7 @@ def test_assert_emails_too_many_sent
|
||||
def test_assert_no_emails_failure
|
||||
error = assert_raise ActiveSupport::TestCase::Assertion do
|
||||
assert_no_emails do
|
||||
TestHelperMailer.test.deliver
|
||||
TestHelperMailer.test.deliver_now
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -68,7 +68,7 @@ def test_signed_up_with_url
|
||||
created.message_id = '<123@456>'
|
||||
assert_equal expected.encoded, created.encoded
|
||||
|
||||
assert_nothing_raised { UrlTestMailer.signed_up_with_url(@recipient).deliver }
|
||||
assert_nothing_raised { UrlTestMailer.signed_up_with_url(@recipient).deliver_now }
|
||||
assert_not_nil ActionMailer::Base.deliveries.first
|
||||
delivered = ActionMailer::Base.deliveries.first
|
||||
|
||||
|
@ -1,3 +1,40 @@
|
||||
* Don't rescue `IPAddr::InvalidAddressError`.
|
||||
|
||||
`IPAddr::InvalidAddressError` does not exist in Ruby 1.9.3
|
||||
and fails for JRuby in 1.9 mode.
|
||||
|
||||
*Peter Suschlik*
|
||||
|
||||
* Fix bug where the router would ignore any constraints added to redirect
|
||||
routes.
|
||||
|
||||
Fixes #16605.
|
||||
|
||||
*Agis Anastasopoulos*
|
||||
|
||||
* Allow `config.action_dispatch.trusted_proxies` to accept an IPAddr object.
|
||||
|
||||
Example:
|
||||
|
||||
# config/environments/production.rb
|
||||
config.action_dispatch.trusted_proxies = IPAddr.new('4.8.15.0/16')
|
||||
|
||||
*Sam Aarons*
|
||||
|
||||
* Avoid duplicating routes for HEAD requests.
|
||||
|
||||
Instead of duplicating the routes, we will first match the HEAD request to
|
||||
HEAD routes. If no match is found, we will then map the HEAD request to
|
||||
GET routes.
|
||||
|
||||
*Guo Xiang Tan*, *Andrew White*
|
||||
|
||||
* Requests that hit `ActionDispatch::Static` can now take advantage
|
||||
of gzipped assets on disk. By default a gzip asset will be served if
|
||||
the client supports gzip and a compressed file is on disk.
|
||||
|
||||
*Richard Schneeman*
|
||||
|
||||
* `ActionController::Parameters` will stop inheriting from `Hash` and
|
||||
`HashWithIndifferentAccess` in the next major release. If you use any method
|
||||
that is not available on `ActionController::Parameters` you should consider
|
||||
@ -33,7 +70,7 @@
|
||||
|
||||
*Prem Sichanugrist*
|
||||
|
||||
* Deprecated TagAssertions.
|
||||
* Deprecated `TagAssertions`.
|
||||
|
||||
*Kasper Timm Hansen*
|
||||
|
||||
@ -65,11 +102,11 @@
|
||||
If you render a different template, you can now pass the `:template`
|
||||
option to include its digest instead:
|
||||
|
||||
fresh_when @post, template: 'widgets/show'
|
||||
fresh_when @post, template: 'widgets/show'
|
||||
|
||||
Pass `template: false` to skip the lookup. To turn this off entirely, set:
|
||||
|
||||
config.action_controller.etag_with_template_digest = false
|
||||
config.action_controller.etag_with_template_digest = false
|
||||
|
||||
*Jeremy Kemper*
|
||||
|
||||
@ -123,7 +160,7 @@
|
||||
*Godfrey Chan*
|
||||
|
||||
* Prepend a JS comment to JSONP callbacks. Addresses CVE-2014-4671
|
||||
("Rosetta Flash")
|
||||
("Rosetta Flash").
|
||||
|
||||
*Greg Campbell*
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
require 'rake/testtask'
|
||||
require 'rubygems/package_task'
|
||||
|
||||
test_files = Dir.glob('test/{abstract,controller,dispatch,assertions,journey,routing}/**/*_test.rb')
|
||||
test_files = Dir.glob('test/**/*_test.rb')
|
||||
|
||||
desc "Default Task"
|
||||
task :default => :test
|
||||
@ -9,10 +9,7 @@ task :default => :test
|
||||
# Run the unit tests
|
||||
Rake::TestTask.new do |t|
|
||||
t.libs << 'test'
|
||||
|
||||
# make sure we include the tests in alphabetical order as on some systems
|
||||
# this will not happen automatically and the tests (as a whole) will error
|
||||
t.test_files = test_files.sort
|
||||
t.test_files = test_files
|
||||
|
||||
t.warning = true
|
||||
t.verbose = true
|
||||
|
@ -105,6 +105,12 @@ def request_method
|
||||
@request_method ||= check_method(env["REQUEST_METHOD"])
|
||||
end
|
||||
|
||||
def request_method=(request_method) #:nodoc:
|
||||
if check_method(request_method)
|
||||
@request_method = env["REQUEST_METHOD"] = request_method
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a symbol form of the #request_method
|
||||
def request_method_symbol
|
||||
HTTP_METHOD_LOOKUP[request_method]
|
||||
|
@ -101,11 +101,13 @@ def find_routes req
|
||||
r.path.match(req.path_info)
|
||||
}
|
||||
|
||||
if req.env["REQUEST_METHOD"] === "HEAD"
|
||||
routes.concat get_routes_as_head(routes)
|
||||
end
|
||||
routes =
|
||||
if req.request_method == "HEAD"
|
||||
match_head_routes(routes, req)
|
||||
else
|
||||
match_routes(routes, req)
|
||||
end
|
||||
|
||||
routes.select! { |r| r.matches?(req) }
|
||||
routes.sort_by!(&:precedence)
|
||||
|
||||
routes.map! { |r|
|
||||
@ -118,19 +120,23 @@ def find_routes req
|
||||
}
|
||||
end
|
||||
|
||||
def get_routes_as_head(routes)
|
||||
precedence = (routes.map(&:precedence).max || 0) + 1
|
||||
routes.select { |r|
|
||||
r.verb === "GET" && !(r.verb === "HEAD")
|
||||
}.map! { |r|
|
||||
Route.new(r.name,
|
||||
r.app,
|
||||
r.path,
|
||||
r.conditions.merge(request_method: "HEAD"),
|
||||
r.defaults).tap do |route|
|
||||
route.precedence = r.precedence + precedence
|
||||
end
|
||||
}
|
||||
def match_head_routes(routes, req)
|
||||
head_routes = match_routes(routes, req)
|
||||
|
||||
if head_routes.empty?
|
||||
begin
|
||||
req.request_method = "GET"
|
||||
match_routes(routes, req)
|
||||
ensure
|
||||
req.request_method = "HEAD"
|
||||
end
|
||||
else
|
||||
head_routes
|
||||
end
|
||||
end
|
||||
|
||||
def match_routes(routes, req)
|
||||
routes.select { |r| r.matches?(req) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1,3 +1,5 @@
|
||||
require 'ipaddr'
|
||||
|
||||
module ActionDispatch
|
||||
# This middleware calculates the IP address of the remote client that is
|
||||
# making the request. It does this by checking various headers that could
|
||||
@ -28,14 +30,14 @@ class IpSpoofAttackError < StandardError; end
|
||||
# guaranteed by the IP specification to be private addresses. Those will
|
||||
# not be the ultimate client IP in production, and so are discarded. See
|
||||
# http://en.wikipedia.org/wiki/Private_network for details.
|
||||
TRUSTED_PROXIES = %r{
|
||||
^127\.0\.0\.1$ | # localhost IPv4
|
||||
^::1$ | # localhost IPv6
|
||||
^[fF][cCdD] | # private IPv6 range fc00::/7
|
||||
^10\. | # private IPv4 range 10.x.x.x
|
||||
^172\.(1[6-9]|2[0-9]|3[0-1])\.| # private IPv4 range 172.16.0.0 .. 172.31.255.255
|
||||
^192\.168\. # private IPv4 range 192.168.x.x
|
||||
}x
|
||||
TRUSTED_PROXIES = [
|
||||
"127.0.0.1", # localhost IPv4
|
||||
"::1", # localhost IPv6
|
||||
"fc00::/7", # private IPv6 range fc00::/7
|
||||
"10.0.0.0/8", # private IPv4 range 10.x.x.x
|
||||
"172.16.0.0/12", # private IPv4 range 172.16.0.0 .. 172.31.255.255
|
||||
"192.168.0.0/16", # private IPv4 range 192.168.x.x
|
||||
].map { |proxy| IPAddr.new(proxy) }
|
||||
|
||||
attr_reader :check_ip, :proxies
|
||||
|
||||
@ -47,24 +49,24 @@ class IpSpoofAttackError < StandardError; end
|
||||
# clients (like WAP devices), or behind proxies that set headers in an
|
||||
# incorrect or confusing way (like AWS ELB).
|
||||
#
|
||||
# The +custom_proxies+ argument can take a regex, which will be used
|
||||
# instead of +TRUSTED_PROXIES+, or a string, which will be used in addition
|
||||
# to +TRUSTED_PROXIES+. Any proxy setup will put the value you want in the
|
||||
# middle (or at the beginning) of the X-Forwarded-For list, with your proxy
|
||||
# servers after it. If your proxies aren't removed, pass them in via the
|
||||
# +custom_proxies+ parameter. That way, the middleware will ignore those
|
||||
# IP addresses, and return the one that you want.
|
||||
# The +custom_proxies+ argument can take an Array of string, IPAddr, or
|
||||
# Regexp objects which will be used instead of +TRUSTED_PROXIES+. If a
|
||||
# single string, IPAddr, or Regexp object is provided, it will be used in
|
||||
# addition to +TRUSTED_PROXIES+. Any proxy setup will put the value you
|
||||
# want in the middle (or at the beginning) of the X-Forwarded-For list,
|
||||
# with your proxy servers after it. If your proxies aren't removed, pass
|
||||
# them in via the +custom_proxies+ parameter. That way, the middleware will
|
||||
# ignore those IP addresses, and return the one that you want.
|
||||
def initialize(app, check_ip_spoofing = true, custom_proxies = nil)
|
||||
@app = app
|
||||
@check_ip = check_ip_spoofing
|
||||
@proxies = case custom_proxies
|
||||
when Regexp
|
||||
custom_proxies
|
||||
when nil
|
||||
TRUSTED_PROXIES
|
||||
else
|
||||
Regexp.union(TRUSTED_PROXIES, custom_proxies)
|
||||
end
|
||||
@proxies = if custom_proxies.blank?
|
||||
TRUSTED_PROXIES
|
||||
elsif custom_proxies.respond_to?(:any?)
|
||||
custom_proxies
|
||||
else
|
||||
Array(custom_proxies) + TRUSTED_PROXIES
|
||||
end
|
||||
end
|
||||
|
||||
# Since the IP address may not be needed, we store the object here
|
||||
@ -80,32 +82,6 @@ def call(env)
|
||||
# into an actual IP address. If the ActionDispatch::Request#remote_ip method
|
||||
# is called, this class will calculate the value and then memoize it.
|
||||
class GetIp
|
||||
|
||||
# This constant contains a regular expression that validates every known
|
||||
# form of IP v4 and v6 address, with or without abbreviations, adapted
|
||||
# from {this gist}[https://gist.github.com/gazay/1289635].
|
||||
VALID_IP = %r{
|
||||
(^(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[0-9]{1,2})(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[0-9]{1,2})){3}$) | # ip v4
|
||||
(^(
|
||||
(([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}) | # ip v6 not abbreviated
|
||||
(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4}) | # ip v6 with double colon in the end
|
||||
(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4}) | # - ip addresses v6
|
||||
(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4}) | # - with
|
||||
(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4}) | # - double colon
|
||||
(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4}) | # - in the middle
|
||||
(([0-9A-Fa-f]{1,4}:){6} ((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3} (\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
|
||||
(([0-9A-Fa-f]{1,4}:){1,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
|
||||
(([0-9A-Fa-f]{1,4}:){1}:([0-9A-Fa-f]{1,4}:){0,4}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
|
||||
(([0-9A-Fa-f]{1,4}:){0,2}:([0-9A-Fa-f]{1,4}:){0,3}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
|
||||
(([0-9A-Fa-f]{1,4}:){0,3}:([0-9A-Fa-f]{1,4}:){0,2}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
|
||||
(([0-9A-Fa-f]{1,4}:){0,4}:([0-9A-Fa-f]{1,4}:){1}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
|
||||
(::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d) |(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
|
||||
([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4}) | # ip v6 with compatible to v4
|
||||
(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4}) | # ip v6 with double colon at the beginning
|
||||
(([0-9A-Fa-f]{1,4}:){1,7}:) # ip v6 without ending
|
||||
)$)
|
||||
}x
|
||||
|
||||
def initialize(env, middleware)
|
||||
@env = env
|
||||
@check_ip = middleware.check_ip
|
||||
@ -173,12 +149,22 @@ def to_s
|
||||
def ips_from(header)
|
||||
# Split the comma-separated list into an array of strings
|
||||
ips = @env[header] ? @env[header].strip.split(/[,\s]+/) : []
|
||||
# Only return IPs that are valid according to the regex
|
||||
ips.select{ |ip| ip =~ VALID_IP }
|
||||
ips.select do |ip|
|
||||
begin
|
||||
# Only return IPs that are valid according to the IPAddr#new method
|
||||
range = IPAddr.new(ip).to_range
|
||||
# we want to make sure nobody is sneaking a netmask in
|
||||
range.begin == range.end
|
||||
rescue ArgumentError
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def filter_proxies(ips)
|
||||
ips.reject { |ip| ip =~ @proxies }
|
||||
ips.reject do |ip|
|
||||
@proxies.any? { |proxy| proxy === ip }
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
@ -16,43 +16,63 @@ class FileHandler
|
||||
def initialize(root, cache_control)
|
||||
@root = root.chomp('/')
|
||||
@compiled_root = /^#{Regexp.escape(root)}/
|
||||
headers = cache_control && { 'Cache-Control' => cache_control }
|
||||
@file_server = ::Rack::File.new(@root, headers)
|
||||
headers = cache_control && { 'Cache-Control' => cache_control }
|
||||
@file_server = ::Rack::File.new(@root, headers)
|
||||
end
|
||||
|
||||
def match?(path)
|
||||
path = unescape_path(path)
|
||||
path = URI.parser.unescape(path)
|
||||
return false unless path.valid_encoding?
|
||||
|
||||
full_path = path.empty? ? @root : File.join(@root, escape_glob_chars(path))
|
||||
paths = "#{full_path}#{ext}"
|
||||
paths = [path, "#{path}#{ext}", "#{path}/index#{ext}"]
|
||||
|
||||
matches = Dir[paths]
|
||||
match = matches.detect { |m| File.file?(m) }
|
||||
if match
|
||||
match.sub!(@compiled_root, '')
|
||||
::Rack::Utils.escape(match)
|
||||
if match = paths.detect {|p| File.file?(File.join(@root, p)) }
|
||||
return ::Rack::Utils.escape(match)
|
||||
end
|
||||
end
|
||||
|
||||
def call(env)
|
||||
@file_server.call(env)
|
||||
end
|
||||
path = env['PATH_INFO']
|
||||
gzip_path = gzip_file_path(path)
|
||||
|
||||
def ext
|
||||
@ext ||= begin
|
||||
ext = ::ActionController::Base.default_static_extension
|
||||
"{,#{ext},/index#{ext}}"
|
||||
if gzip_path && gzip_encoding_accepted?(env)
|
||||
env['PATH_INFO'] = gzip_path
|
||||
status, headers, body = @file_server.call(env)
|
||||
headers['Content-Encoding'] = 'gzip'
|
||||
headers['Content-Type'] = content_type(path)
|
||||
else
|
||||
status, headers, body = @file_server.call(env)
|
||||
end
|
||||
|
||||
headers['Vary'] = 'Accept-Encoding' if gzip_path
|
||||
|
||||
return [status, headers, body]
|
||||
ensure
|
||||
env['PATH_INFO'] = path
|
||||
end
|
||||
|
||||
def unescape_path(path)
|
||||
URI.parser.unescape(path)
|
||||
end
|
||||
private
|
||||
def ext
|
||||
::ActionController::Base.default_static_extension
|
||||
end
|
||||
|
||||
def escape_glob_chars(path)
|
||||
path.gsub(/[*?{}\[\]]/, "\\\\\\&")
|
||||
end
|
||||
def content_type(path)
|
||||
::Rack::Mime.mime_type(::File.extname(path), 'text/plain')
|
||||
end
|
||||
|
||||
def gzip_encoding_accepted?(env)
|
||||
env['HTTP_ACCEPT_ENCODING'] =~ /\bgzip\b/i
|
||||
end
|
||||
|
||||
def gzip_file_path(path)
|
||||
can_gzip_mime = content_type(path) =~ /\A(?:text\/|application\/javascript)/
|
||||
gzip_path = "#{path}.gz"
|
||||
if can_gzip_mime && File.exist?(File.join(@root, ::Rack::Utils.unescape(gzip_path)))
|
||||
gzip_path
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# This middleware will attempt to return the contents of a file's body from
|
||||
|
@ -241,8 +241,6 @@ def add_request_method(via, conditions)
|
||||
end
|
||||
|
||||
def app(blocks)
|
||||
return to if Redirect === to
|
||||
|
||||
if to.respond_to?(:call)
|
||||
Constraints.new(to, blocks, false)
|
||||
else
|
||||
|
@ -206,7 +206,7 @@ def noop
|
||||
|
||||
def setup
|
||||
super
|
||||
@request.env["action_dispatch.key_generator"] = ActiveSupport::KeyGenerator.new("b3c631c314c0bbca50c1b2843150fe33")
|
||||
@request.env["action_dispatch.key_generator"] = ActiveSupport::KeyGenerator.new("b3c631c314c0bbca50c1b2843150fe33", iterations: 2)
|
||||
@request.env["action_dispatch.signed_cookie_salt"] = "b3c631c314c0bbca50c1b2843150fe33"
|
||||
@request.env["action_dispatch.encrypted_cookie_salt"] = "b3c631c314c0bbca50c1b2843150fe33"
|
||||
@request.env["action_dispatch.encrypted_signed_cookie_salt"] = "b3c631c314c0bbca50c1b2843150fe33"
|
||||
|
@ -650,6 +650,18 @@ class RequestMethod < BaseRequestTest
|
||||
end
|
||||
end
|
||||
|
||||
test "allow request method hacking" do
|
||||
request = stub_request('REQUEST_METHOD' => 'POST')
|
||||
|
||||
assert_equal 'POST', request.request_method
|
||||
assert_equal 'POST', request.env["REQUEST_METHOD"]
|
||||
|
||||
request.request_method = 'GET'
|
||||
|
||||
assert_equal 'GET', request.request_method
|
||||
assert_equal 'GET', request.env["REQUEST_METHOD"]
|
||||
end
|
||||
|
||||
test "invalid http method raises exception" do
|
||||
assert_raise(ActionController::UnknownHttpMethod) do
|
||||
stub_request('REQUEST_METHOD' => 'RANDOM_METHOD').request_method
|
||||
|
@ -14,6 +14,12 @@ def self.matches?(request)
|
||||
end
|
||||
end
|
||||
|
||||
class GrumpyRestrictor
|
||||
def self.matches?(request)
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
class YoutubeFavoritesRedirector
|
||||
def self.call(params, request)
|
||||
"http://www.youtube.com/watch?v=#{params[:youtube_id]}"
|
||||
@ -89,6 +95,24 @@ def test_namespace_redirect
|
||||
verify_redirect 'http://www.example.com/private/index'
|
||||
end
|
||||
|
||||
def test_redirect_with_failing_constraint
|
||||
draw do
|
||||
get 'hi', to: redirect("/foo"), constraints: ::TestRoutingMapper::GrumpyRestrictor
|
||||
end
|
||||
|
||||
get '/hi'
|
||||
assert_equal 404, status
|
||||
end
|
||||
|
||||
def test_redirect_with_passing_constraint
|
||||
draw do
|
||||
get 'hi', to: redirect("/foo"), constraints: ->(req) { true }
|
||||
end
|
||||
|
||||
get '/hi'
|
||||
assert_equal 301, status
|
||||
end
|
||||
|
||||
def test_namespace_with_controller_segment
|
||||
assert_raise(ArgumentError) do
|
||||
draw do
|
||||
|
@ -1,6 +1,7 @@
|
||||
# encoding: utf-8
|
||||
require 'abstract_unit'
|
||||
require 'rbconfig'
|
||||
require 'zlib'
|
||||
|
||||
module StaticTests
|
||||
def test_serves_dynamic_content
|
||||
@ -36,6 +37,10 @@ def test_serves_static_index_file_in_directory
|
||||
assert_html "/foo/index.html", get("/foo")
|
||||
end
|
||||
|
||||
def test_serves_file_with_same_name_before_index_in_directory
|
||||
assert_html "/bar.html", get("/bar")
|
||||
end
|
||||
|
||||
def test_served_static_file_with_non_english_filename
|
||||
jruby_skip "Stop skipping if following bug gets fixed: " \
|
||||
"http://jira.codehaus.org/browse/JRUBY-7192"
|
||||
@ -106,6 +111,40 @@ def test_serves_static_file_with_at_symbol_in_filename
|
||||
end
|
||||
end
|
||||
|
||||
def test_serves_gzip_files_when_header_set
|
||||
file_name = "/gzip/application-a71b3024f80aea3181c09774ca17e712.js"
|
||||
response = get(file_name, 'HTTP_ACCEPT_ENCODING' => 'gzip')
|
||||
assert_gzip file_name, response
|
||||
assert_equal 'application/javascript', response.headers['Content-Type']
|
||||
assert_equal 'Accept-Encoding', response.headers["Vary"]
|
||||
assert_equal 'gzip', response.headers["Content-Encoding"]
|
||||
|
||||
response = get(file_name, 'HTTP_ACCEPT_ENCODING' => 'Gzip')
|
||||
assert_gzip file_name, response
|
||||
|
||||
response = get(file_name, 'HTTP_ACCEPT_ENCODING' => 'GZIP')
|
||||
assert_gzip file_name, response
|
||||
|
||||
response = get(file_name, 'HTTP_ACCEPT_ENCODING' => '')
|
||||
assert_not_equal 'gzip', response.headers["Content-Encoding"]
|
||||
end
|
||||
|
||||
def test_does_not_modify_path_info
|
||||
file_name = "/gzip/application-a71b3024f80aea3181c09774ca17e712.js"
|
||||
env = {'PATH_INFO' => file_name, 'HTTP_ACCEPT_ENCODING' => 'gzip'}
|
||||
@app.call(env)
|
||||
assert_equal file_name, env['PATH_INFO']
|
||||
end
|
||||
|
||||
def test_serves_gzip_with_propper_content_type_fallback
|
||||
file_name = "/gzip/foo.zoo"
|
||||
response = get(file_name, 'HTTP_ACCEPT_ENCODING' => 'gzip')
|
||||
assert_gzip file_name, response
|
||||
|
||||
default_response = get(file_name) # no gzip
|
||||
assert_equal default_response.headers['Content-Type'], response.headers['Content-Type']
|
||||
end
|
||||
|
||||
# Windows doesn't allow \ / : * ? " < > | in filenames
|
||||
unless RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
|
||||
def test_serves_static_file_with_colon
|
||||
@ -125,13 +164,20 @@ def test_serves_static_file_with_asterisk
|
||||
|
||||
private
|
||||
|
||||
def assert_gzip(file_name, response)
|
||||
expected = File.read("#{FIXTURE_LOAD_PATH}/#{public_path}" + file_name)
|
||||
actual = Zlib::GzipReader.new(StringIO.new(response.body)).read
|
||||
assert_equal expected, actual
|
||||
end
|
||||
|
||||
def assert_html(body, response)
|
||||
assert_equal body, response.body
|
||||
assert_equal "text/html", response.headers["Content-Type"]
|
||||
assert_nil response.headers["Vary"]
|
||||
end
|
||||
|
||||
def get(path)
|
||||
Rack::MockRequest.new(@app).request("GET", path)
|
||||
def get(path, headers = {})
|
||||
Rack::MockRequest.new(@app).request("GET", path, headers)
|
||||
end
|
||||
|
||||
def with_static_file(file)
|
||||
|
1
actionpack/test/fixtures/public/bar.html
vendored
Normal file
1
actionpack/test/fixtures/public/bar.html
vendored
Normal file
@ -0,0 +1 @@
|
||||
/bar.html
|
1
actionpack/test/fixtures/public/bar/index.html
vendored
Normal file
1
actionpack/test/fixtures/public/bar/index.html
vendored
Normal file
@ -0,0 +1 @@
|
||||
/bar/index.html
|
4
actionpack/test/fixtures/public/gzip/application-a71b3024f80aea3181c09774ca17e712.js
vendored
Normal file
4
actionpack/test/fixtures/public/gzip/application-a71b3024f80aea3181c09774ca17e712.js
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
actionpack/test/fixtures/public/gzip/application-a71b3024f80aea3181c09774ca17e712.js.gz
vendored
Normal file
BIN
actionpack/test/fixtures/public/gzip/application-a71b3024f80aea3181c09774ca17e712.js.gz
vendored
Normal file
Binary file not shown.
4
actionpack/test/fixtures/public/gzip/foo.zoo
vendored
Normal file
4
actionpack/test/fixtures/public/gzip/foo.zoo
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
actionpack/test/fixtures/public/gzip/foo.zoo.gz
vendored
Normal file
BIN
actionpack/test/fixtures/public/gzip/foo.zoo.gz
vendored
Normal file
Binary file not shown.
1
actionpack/test/fixtures/公共/bar.html
vendored
Normal file
1
actionpack/test/fixtures/公共/bar.html
vendored
Normal file
@ -0,0 +1 @@
|
||||
/bar.html
|
1
actionpack/test/fixtures/公共/bar/index.html
vendored
Normal file
1
actionpack/test/fixtures/公共/bar/index.html
vendored
Normal file
@ -0,0 +1 @@
|
||||
/bar/index.html
|
4
actionpack/test/fixtures/公共/gzip/application-a71b3024f80aea3181c09774ca17e712.js
vendored
Normal file
4
actionpack/test/fixtures/公共/gzip/application-a71b3024f80aea3181c09774ca17e712.js
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
actionpack/test/fixtures/公共/gzip/application-a71b3024f80aea3181c09774ca17e712.js.gz
vendored
Normal file
BIN
actionpack/test/fixtures/公共/gzip/application-a71b3024f80aea3181c09774ca17e712.js.gz
vendored
Normal file
Binary file not shown.
4
actionpack/test/fixtures/公共/gzip/foo.zoo
vendored
Normal file
4
actionpack/test/fixtures/公共/gzip/foo.zoo
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
actionpack/test/fixtures/公共/gzip/foo.zoo.gz
vendored
Normal file
BIN
actionpack/test/fixtures/公共/gzip/foo.zoo.gz
vendored
Normal file
Binary file not shown.
@ -503,6 +503,25 @@ def test_recognize_literal
|
||||
assert called
|
||||
end
|
||||
|
||||
def test_recognize_head_route
|
||||
path = Path::Pattern.from_string "/books(/:action(.:format))"
|
||||
app = Object.new
|
||||
conditions = { request_method: 'HEAD' }
|
||||
@router.routes.add_route(app, path, conditions, {})
|
||||
|
||||
env = rails_env(
|
||||
'PATH_INFO' => '/books/list.rss',
|
||||
'REQUEST_METHOD' => 'HEAD'
|
||||
)
|
||||
|
||||
called = false
|
||||
@router.recognize(env) do |r, params|
|
||||
called = true
|
||||
end
|
||||
|
||||
assert called
|
||||
end
|
||||
|
||||
def test_recognize_head_request_as_get_route
|
||||
path = Path::Pattern.from_string "/books(/:action(.:format))"
|
||||
app = Object.new
|
||||
@ -525,19 +544,24 @@ def test_recognize_head_request_as_get_route
|
||||
def test_recognize_cares_about_verbs
|
||||
path = Path::Pattern.from_string "/books(/:action(.:format))"
|
||||
app = Object.new
|
||||
conditions = {
|
||||
:request_method => 'GET'
|
||||
}
|
||||
conditions = { request_method: 'GET' }
|
||||
@router.routes.add_route(app, path, conditions, {})
|
||||
|
||||
env = rails_env 'PATH_INFO' => '/books/list.rss',
|
||||
"REQUEST_METHOD" => "POST"
|
||||
|
||||
called = false
|
||||
@router.recognize(env) do |r, params|
|
||||
called = true
|
||||
end
|
||||
|
||||
assert_not called
|
||||
|
||||
conditions = conditions.dup
|
||||
conditions[:request_method] = 'POST'
|
||||
|
||||
post = @router.routes.add_route(app, path, conditions, {})
|
||||
|
||||
env = rails_env 'PATH_INFO' => '/books/list.rss',
|
||||
"REQUEST_METHOD" => "POST"
|
||||
|
||||
called = false
|
||||
@router.recognize(env) do |r, params|
|
||||
assert_equal post, r
|
||||
@ -561,7 +585,7 @@ def add_routes router, paths
|
||||
end
|
||||
|
||||
def rails_env env, klass = ActionDispatch::Request
|
||||
klass.new env
|
||||
klass.new(rack_env(env))
|
||||
end
|
||||
|
||||
def rack_env env
|
||||
|
@ -1,3 +1,10 @@
|
||||
* Provide a `builder` object when using the `label` form helper in block form.
|
||||
|
||||
The new `builder` object responds to `translation`, allowing I18n fallback support
|
||||
when you want to customize how a particular label is presented.
|
||||
|
||||
*Alex Robbin*
|
||||
|
||||
* Add I18n support for input/textarea placeholder text.
|
||||
|
||||
Placeholder I18n follows the same convention as `label` I18n.
|
||||
@ -19,11 +26,11 @@
|
||||
*Joel Junström*, *Lucas Uyezu*
|
||||
|
||||
* Return an absolute instead of relative path from an asset url in the case
|
||||
of the `asset_host` proc returning nil
|
||||
of the `asset_host` proc returning nil.
|
||||
|
||||
*Jolyon Pawlyn*
|
||||
|
||||
* Fix `html_escape_once` to properly handle hex escape sequences (e.g. ᨫ)
|
||||
* Fix `html_escape_once` to properly handle hex escape sequences (e.g. ᨫ).
|
||||
|
||||
*John F. Douthat*
|
||||
|
||||
@ -56,7 +63,7 @@
|
||||
|
||||
*Zuhao Wan*
|
||||
|
||||
* Bring `cache_digest` rake tasks up-to-date with the latest API changes
|
||||
* Bring `cache_digest` rake tasks up-to-date with the latest API changes.
|
||||
|
||||
*Jiri Pospisil*
|
||||
|
||||
|
@ -18,7 +18,7 @@ namespace :test do
|
||||
|
||||
Rake::TestTask.new(:template) do |t|
|
||||
t.libs << 'test'
|
||||
t.test_files = Dir.glob('test/template/**/*_test.rb').sort
|
||||
t.test_files = Dir.glob('test/template/**/*_test.rb')
|
||||
t.warning = true
|
||||
t.verbose = true
|
||||
end
|
||||
|
@ -35,10 +35,10 @@ module FormTagHelper
|
||||
# This is helpful when you're fragment-caching the form. Remote forms get the
|
||||
# authenticity token from the <tt>meta</tt> tag, so embedding is unnecessary unless you
|
||||
# support browsers without JavaScript.
|
||||
# * A list of parameters to feed to the URL the form will be posted to.
|
||||
# * <tt>:remote</tt> - If set to true, will allow the Unobtrusive JavaScript drivers to control the
|
||||
# submit behavior. By default this behavior is an ajax submit.
|
||||
# * <tt>:enforce_utf8</tt> - If set to false, a hidden input with name utf8 is not output.
|
||||
# * Any other key creates standard HTML attributes for the tag.
|
||||
#
|
||||
# ==== Examples
|
||||
# form_tag('/posts')
|
||||
|
@ -2,6 +2,39 @@ module ActionView
|
||||
module Helpers
|
||||
module Tags # :nodoc:
|
||||
class Label < Base # :nodoc:
|
||||
class LabelBuilder # :nodoc:
|
||||
attr_reader :object
|
||||
|
||||
def initialize(template_object, object_name, method_name, object, tag_value)
|
||||
@template_object = template_object
|
||||
@object_name = object_name
|
||||
@method_name = method_name
|
||||
@object = object
|
||||
@tag_value = tag_value
|
||||
end
|
||||
|
||||
def translation
|
||||
method_and_value = @tag_value.present? ? "#{@method_name}.#{@tag_value}" : @method_name
|
||||
@object_name.gsub!(/\[(.*)_attributes\]\[\d+\]/, '.\1')
|
||||
|
||||
if object.respond_to?(:to_model)
|
||||
key = object.model_name.i18n_key
|
||||
i18n_default = ["#{key}.#{method_and_value}".to_sym, ""]
|
||||
end
|
||||
|
||||
i18n_default ||= ""
|
||||
content = I18n.t("#{@object_name}.#{method_and_value}", :default => i18n_default, :scope => "helpers.label").presence
|
||||
|
||||
content ||= if object && object.class.respond_to?(:human_attribute_name)
|
||||
object.class.human_attribute_name(method_and_value)
|
||||
end
|
||||
|
||||
content ||= @method_name.humanize
|
||||
|
||||
content
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(object_name, method_name, template_object, content_or_options = nil, options = nil)
|
||||
options ||= {}
|
||||
|
||||
@ -32,33 +65,24 @@ def render(&block)
|
||||
options.delete("namespace")
|
||||
options["for"] = name_and_id["id"] unless options.key?("for")
|
||||
|
||||
if block_given?
|
||||
content = @template_object.capture(&block)
|
||||
builder = LabelBuilder.new(@template_object, @object_name, @method_name, @object, tag_value)
|
||||
|
||||
content = if block_given?
|
||||
@template_object.capture(builder, &block)
|
||||
elsif @content.present?
|
||||
@content.to_s
|
||||
else
|
||||
method_and_value = tag_value.present? ? "#{@method_name}.#{tag_value}" : @method_name
|
||||
content = if @content.blank?
|
||||
@object_name.gsub!(/\[(.*)_attributes\]\[\d+\]/, '.\1')
|
||||
|
||||
if object.respond_to?(:to_model)
|
||||
key = object.model_name.i18n_key
|
||||
i18n_default = ["#{key}.#{method_and_value}".to_sym, ""]
|
||||
end
|
||||
|
||||
i18n_default ||= ""
|
||||
I18n.t("#{@object_name}.#{method_and_value}", :default => i18n_default, :scope => "helpers.label").presence
|
||||
else
|
||||
@content.to_s
|
||||
end
|
||||
|
||||
content ||= if object && object.class.respond_to?(:human_attribute_name)
|
||||
object.class.human_attribute_name(method_and_value)
|
||||
end
|
||||
|
||||
content ||= @method_name.humanize
|
||||
render_component(builder)
|
||||
end
|
||||
|
||||
label_tag(name_and_id["id"], content, options)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def render_component(builder)
|
||||
builder.translation
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -6,6 +6,8 @@ def initialize(*)
|
||||
super
|
||||
|
||||
if tag_value = @options[:placeholder]
|
||||
placeholder = tag_value if tag_value.is_a?(String)
|
||||
|
||||
object_name = @object_name.gsub(/\[(.*)_attributes\]\[\d+\]/, '.\1')
|
||||
method_and_value = tag_value.is_a?(TrueClass) ? @method_name : "#{@method_name}.#{tag_value}"
|
||||
|
||||
@ -15,7 +17,7 @@ def initialize(*)
|
||||
end
|
||||
|
||||
i18n_default ||= ""
|
||||
placeholder = I18n.t("#{object_name}.#{method_and_value}", :default => i18n_default, :scope => "helpers.placeholder").presence
|
||||
placeholder ||= I18n.t("#{object_name}.#{method_and_value}", :default => i18n_default, :scope => "helpers.placeholder").presence
|
||||
|
||||
placeholder ||= if object && object.class.respond_to?(:human_attribute_name)
|
||||
object.class.human_attribute_name(method_and_value)
|
||||
|
@ -242,7 +242,7 @@ def compile!(view) #:nodoc:
|
||||
end
|
||||
|
||||
instrument("!compile_template") do
|
||||
compile(view, mod)
|
||||
compile(mod)
|
||||
end
|
||||
|
||||
# Just discard the source if we have a virtual path. This
|
||||
@ -264,7 +264,7 @@ def compile!(view) #:nodoc:
|
||||
# encode the source into <tt>Encoding.default_internal</tt>.
|
||||
# In general, this means that templates will be UTF-8 inside of Rails,
|
||||
# regardless of the original source encoding.
|
||||
def compile(view, mod) #:nodoc:
|
||||
def compile(mod) #:nodoc:
|
||||
encode!
|
||||
method_name = self.method_name
|
||||
code = @handler.call(self)
|
||||
@ -293,18 +293,8 @@ def #{method_name}(local_assigns, output_buffer)
|
||||
raise WrongEncodingError.new(@source, Encoding.default_internal)
|
||||
end
|
||||
|
||||
begin
|
||||
mod.module_eval(source, identifier, 0)
|
||||
ObjectSpace.define_finalizer(self, Finalizer[method_name, mod])
|
||||
rescue => e # errors from template code
|
||||
if logger = (view && view.logger)
|
||||
logger.debug "ERROR: compiling #{method_name} RAISED #{e}"
|
||||
logger.debug "Function body: #{source}"
|
||||
logger.debug "Backtrace: #{e.backtrace.join("\n")}"
|
||||
end
|
||||
|
||||
raise ActionView::Template::Error.new(self, e)
|
||||
end
|
||||
mod.module_eval(source, identifier, 0)
|
||||
ObjectSpace.define_finalizer(self, Finalizer[method_name, mod])
|
||||
end
|
||||
|
||||
def handle_render_error(view, e) #:nodoc:
|
||||
|
@ -319,6 +319,15 @@ def test_label_with_block_and_options
|
||||
)
|
||||
end
|
||||
|
||||
def test_label_with_block_and_builder
|
||||
with_locale :label do
|
||||
assert_dom_equal(
|
||||
'<label for="post_body"><b>Write entire text here</b></label>',
|
||||
label(:post, :body) { |b| "<b>#{b.translation}</b>".html_safe }
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def test_label_with_block_in_erb
|
||||
assert_equal(
|
||||
%{<label for="post_message">\n Message\n <input id="post_message" name="post[message]" type="text" />\n</label>},
|
||||
@ -344,15 +353,21 @@ def test_text_field_placeholder_with_human_attribute_name
|
||||
end
|
||||
end
|
||||
|
||||
def test_text_field_placeholder_with_string_value
|
||||
with_locale :placeholder do
|
||||
assert_dom_equal('<input id="post_cost" name="post[cost]" placeholder="HOW MUCH?" type="text" />', text_field(:post, :cost, placeholder: "HOW MUCH?"))
|
||||
end
|
||||
end
|
||||
|
||||
def test_text_field_placeholder_with_human_attribute_name_and_value
|
||||
with_locale :placeholder do
|
||||
assert_dom_equal('<input id="post_cost" name="post[cost]" placeholder="Pounds" type="text" />', text_field(:post, :cost, placeholder: "uk"))
|
||||
assert_dom_equal('<input id="post_cost" name="post[cost]" placeholder="Pounds" type="text" />', text_field(:post, :cost, placeholder: :uk))
|
||||
end
|
||||
end
|
||||
|
||||
def test_text_field_placeholder_with_locales_and_value
|
||||
with_locale :placeholder do
|
||||
assert_dom_equal('<input id="post_written_on" name="post[written_on]" placeholder="Escrito en" type="text" value="2004-06-15" />', text_field(:post, :written_on, placeholder: "spanish"))
|
||||
assert_dom_equal('<input id="post_written_on" name="post[written_on]" placeholder="Escrito en" type="text" value="2004-06-15" />', text_field(:post, :written_on, placeholder: :spanish))
|
||||
end
|
||||
end
|
||||
|
||||
@ -783,11 +798,20 @@ def test_text_area_placeholder_with_human_attribute_name
|
||||
end
|
||||
end
|
||||
|
||||
def test_text_area_placeholder_with_string_value
|
||||
with_locale :placeholder do
|
||||
assert_dom_equal(
|
||||
%{<textarea id="post_cost" name="post[cost]" placeholder="HOW MUCH?">\n</textarea>},
|
||||
text_area(:post, :cost, placeholder: "HOW MUCH?")
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def test_text_area_placeholder_with_human_attribute_name_and_value
|
||||
with_locale :placeholder do
|
||||
assert_dom_equal(
|
||||
%{<textarea id="post_cost" name="post[cost]" placeholder="Pounds">\n</textarea>},
|
||||
text_area(:post, :cost, placeholder: "uk")
|
||||
text_area(:post, :cost, placeholder: :uk)
|
||||
)
|
||||
end
|
||||
end
|
||||
@ -796,7 +820,7 @@ def test_text_area_placeholder_with_locales_and_value
|
||||
with_locale :placeholder do
|
||||
assert_dom_equal(
|
||||
%{<textarea id="post_written_on" name="post[written_on]" placeholder="Escrito en">\n2004-06-15</textarea>},
|
||||
text_area(:post, :written_on, placeholder: "spanish")
|
||||
text_area(:post, :written_on, placeholder: :spanish)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
Active Job is a framework for declaring jobs and making them run on a variety
|
||||
of queueing backends. These jobs can be everything from regularly scheduled
|
||||
clean-ups, billing charges, or mailings. Anything that can be chopped up into
|
||||
clean-ups, to billing charges, to mailings. Anything that can be chopped up into
|
||||
small units of work and run in parallel, really.
|
||||
|
||||
It also serves as the backend for ActionMailer's #deliver_later functionality
|
||||
|
@ -18,5 +18,6 @@
|
||||
s.files = Dir['CHANGELOG.md', 'MIT-LICENSE', 'README.md', 'lib/**/*']
|
||||
s.require_path = 'lib'
|
||||
|
||||
s.add_dependency 'activesupport', version
|
||||
s.add_dependency 'globalid', '>= 0.2.3'
|
||||
end
|
||||
|
@ -30,4 +30,5 @@ module ActiveJob
|
||||
extend ActiveSupport::Autoload
|
||||
|
||||
autoload :Base
|
||||
end
|
||||
autoload :QueueAdapters
|
||||
end
|
||||
|
@ -1,4 +1,7 @@
|
||||
module ActiveJob
|
||||
# Raised when an exception is raised during job arguments deserialization.
|
||||
#
|
||||
# Wraps the original exception raised as +original_exception+.
|
||||
class DeserializationError < StandardError
|
||||
attr_reader :original_exception
|
||||
|
||||
@ -9,6 +12,14 @@ def initialize(e)
|
||||
end
|
||||
end
|
||||
|
||||
# Raised when an unsupported argument type is being set as job argument. We
|
||||
# currently support NilClass, Fixnum, Float, String, TrueClass, FalseClass,
|
||||
# Bignum and object that can be represented as GlobalIDs (ex: Active Record).
|
||||
# Also raised if you set the key for a Hash something else than a string or
|
||||
# a symbol.
|
||||
class SerializationError < ArgumentError
|
||||
end
|
||||
|
||||
module Arguments
|
||||
extend self
|
||||
TYPE_WHITELIST = [ NilClass, Fixnum, Float, String, TrueClass, FalseClass, Bignum ]
|
||||
@ -33,7 +44,7 @@ def serialize_argument(argument)
|
||||
when Hash
|
||||
Hash[ argument.map { |key, value| [ serialize_hash_key(key), serialize_argument(value) ] } ]
|
||||
else
|
||||
raise "Unsupported argument type: #{argument.class.name}"
|
||||
raise SerializationError.new("Unsupported argument type: #{argument.class.name}")
|
||||
end
|
||||
end
|
||||
|
||||
@ -55,7 +66,7 @@ def serialize_hash_key(key)
|
||||
when String, Symbol
|
||||
key.to_s
|
||||
else
|
||||
raise "Unsupported hash key type: #{key.class.name}"
|
||||
raise SerializationError.new("Unsupported hash key type: #{key.class.name}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -3,8 +3,8 @@
|
||||
module ActiveJob
|
||||
# = Active Job Callbacks
|
||||
#
|
||||
# Active Job provides hooks during the lifecycle of a job. Callbacks allows you to trigger
|
||||
# logic during the lifecycle of a job. Available callbacks:
|
||||
# Active Job provides hooks during the lifecycle of a job. Callbacks allow you
|
||||
# to trigger logic during the lifecycle of a job. Available callbacks are:
|
||||
#
|
||||
# * <tt>before_enqueue</tt>
|
||||
# * <tt>around_enqueue</tt>
|
||||
|
@ -52,19 +52,19 @@ def logger_tagged_by_active_job?
|
||||
|
||||
class LogSubscriber < ActiveSupport::LogSubscriber
|
||||
def enqueue(event)
|
||||
info "Enqueued #{event.payload[:job].name} (Job ID: #{event.payload[:job_id]}) to #{queue_name(event)}" + args_info(event)
|
||||
info { "Enqueued #{event.payload[:job].name} (Job ID: #{event.payload[:job_id]}) to #{queue_name(event)}" + args_info(event) }
|
||||
end
|
||||
|
||||
def enqueue_at(event)
|
||||
info "Enqueued #{event.payload[:job].name} (Job ID: #{event.payload[:job_id]}) to #{queue_name(event)} at #{enqueued_at(event)}" + args_info(event)
|
||||
info { "Enqueued #{event.payload[:job].name} (Job ID: #{event.payload[:job_id]}) to #{queue_name(event)} at #{enqueued_at(event)}" + args_info(event) }
|
||||
end
|
||||
|
||||
def perform_start(event)
|
||||
info "Performing #{event.payload[:job].name} from #{queue_name(event)}" + args_info(event)
|
||||
info { "Performing #{event.payload[:job].name} from #{queue_name(event)}" + args_info(event) }
|
||||
end
|
||||
|
||||
def perform(event)
|
||||
info "Performed #{event.payload[:job].name} from #{queue_name(event)} in #{event.duration.round(2).to_s}ms"
|
||||
info { "Performed #{event.payload[:job].name} from #{queue_name(event)} in #{event.duration.round(2).to_s}ms" }
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -17,7 +17,6 @@ def queue_adapter=(name_or_adapter)
|
||||
|
||||
private
|
||||
def load_adapter(name)
|
||||
require "active_job/queue_adapters/#{name}_adapter"
|
||||
"ActiveJob::QueueAdapters::#{name.to_s.camelize}Adapter".constantize
|
||||
end
|
||||
end
|
||||
|
16
activejob/lib/active_job/queue_adapters.rb
Normal file
16
activejob/lib/active_job/queue_adapters.rb
Normal file
@ -0,0 +1,16 @@
|
||||
module ActiveJob
|
||||
module QueueAdapters
|
||||
extend ActiveSupport::Autoload
|
||||
|
||||
autoload :InlineAdapter
|
||||
autoload :BackburnerAdapter
|
||||
autoload :DelayedJobAdapter
|
||||
autoload :QuAdapter
|
||||
autoload :QueAdapter
|
||||
autoload :QueueClassicAdapter
|
||||
autoload :ResqueAdapter
|
||||
autoload :SidekiqAdapter
|
||||
autoload :SneakersAdapter
|
||||
autoload :SuckerPunchAdapter
|
||||
end
|
||||
end
|
@ -9,7 +9,8 @@ def enqueue(job, *args)
|
||||
end
|
||||
|
||||
def enqueue_at(job, timestamp, *args)
|
||||
raise NotImplementedError
|
||||
delay = timestamp - Time.current.to_f
|
||||
Backburner::Worker.enqueue JobWrapper, [ job.name, *args ], queue: job.queue_name, delay: delay
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -9,7 +9,7 @@ def enqueue(job, *args)
|
||||
end
|
||||
|
||||
def enqueue_at(job, timestamp, *args)
|
||||
raise NotImplementedError
|
||||
JobWrapper.enqueue job.name, *args, queue: job.queue_name, run_at: Time.at(timestamp)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
require_relative 'gem_version'
|
||||
|
||||
module ActiveJob
|
||||
# Returns the version of the currently loaded ActiveJob as a <tt>Gem::Version</tt>
|
||||
# Returns the version of the currently loaded Active Job as a <tt>Gem::Version</tt>
|
||||
def self.version
|
||||
gem_version
|
||||
end
|
||||
|
@ -7,12 +7,14 @@ class JobGenerator < Rails::Generators::NamedBase # :nodoc:
|
||||
|
||||
class_option :queue, type: :string, default: 'default', desc: 'The queue name for the generated job'
|
||||
|
||||
check_class_collision suffix: 'Job'
|
||||
|
||||
hook_for :test_framework
|
||||
|
||||
def self.default_generator_root
|
||||
File.dirname(__FILE__)
|
||||
end
|
||||
|
||||
check_class_collision suffix: 'Job'
|
||||
|
||||
def create_job_file
|
||||
template 'job.rb', File.join('app/jobs', class_path, "#{file_name}_job.rb")
|
||||
end
|
||||
|
@ -1,2 +1,4 @@
|
||||
require 'support/que/inline'
|
||||
|
||||
ActiveJob::Base.queue_adapter = :que
|
||||
Que.mode = :sync
|
||||
|
@ -19,7 +19,7 @@ class ParameterSerializationTest < ActiveSupport::TestCase
|
||||
assert_equal [ [ 1 ] ], ActiveJob::Arguments.serialize([ [ 1 ] ])
|
||||
assert_equal [ 1_000_000_000_000_000_000_000 ], ActiveJob::Arguments.serialize([ 1_000_000_000_000_000_000_000 ])
|
||||
|
||||
err = assert_raises RuntimeError do
|
||||
err = assert_raises ActiveJob::SerializationError do
|
||||
ActiveJob::Arguments.serialize([ 1, self ])
|
||||
end
|
||||
assert_equal "Unsupported argument type: #{self.class.name}", err.message
|
||||
@ -31,14 +31,14 @@ class ParameterSerializationTest < ActiveSupport::TestCase
|
||||
end
|
||||
|
||||
test 'should dive deep into arrays or hashes and raise exception on complex objects' do
|
||||
err = assert_raises RuntimeError do
|
||||
err = assert_raises ActiveJob::SerializationError do
|
||||
ActiveJob::Arguments.serialize([ 1, [self] ])
|
||||
end
|
||||
assert_equal "Unsupported argument type: #{self.class.name}", err.message
|
||||
end
|
||||
|
||||
test 'shoud dive deep into hashes and allow raise exception on not string/symbol keys' do
|
||||
err = assert_raises RuntimeError do
|
||||
err = assert_raises ActiveJob::SerializationError do
|
||||
ActiveJob::Arguments.serialize([ [ { 1 => 2 } ] ])
|
||||
end
|
||||
assert_equal "Unsupported hash key type: Fixnum", err.message
|
||||
|
@ -10,41 +10,15 @@ def sidekiq?
|
||||
@adapter == 'sidekiq'
|
||||
end
|
||||
|
||||
def rubinius?
|
||||
RUBY_ENGINE == 'rbx'
|
||||
end
|
||||
|
||||
def ruby_193?
|
||||
RUBY_VERSION == '1.9.3' && RUBY_ENGINE != 'java'
|
||||
end
|
||||
|
||||
#Sidekiq don't work with MRI 1.9.3
|
||||
#Travis uses rbx 2.6 which don't support unicode characters in methods.
|
||||
#Remove the check when Travis change to rbx 2.7+
|
||||
exit if sidekiq? && (ruby_193? || rubinius?)
|
||||
# Sidekiq doesn't work with MRI 1.9.3
|
||||
exit if sidekiq? && ruby_193?
|
||||
|
||||
require "adapters/#{@adapter}"
|
||||
|
||||
require 'active_support/testing/autorun'
|
||||
|
||||
ActiveJob::Base.logger.level = Logger::DEBUG
|
||||
|
||||
module JobBuffer
|
||||
class << self
|
||||
def clear
|
||||
@buffer = []
|
||||
end
|
||||
|
||||
def add(value)
|
||||
@buffer << value
|
||||
end
|
||||
|
||||
def values
|
||||
@buffer
|
||||
end
|
||||
|
||||
def last_value
|
||||
@buffer.last
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1,3 +1,5 @@
|
||||
require_relative '../support/job_buffer'
|
||||
|
||||
class GidJob < ActiveJob::Base
|
||||
def perform(person)
|
||||
JobBuffer.add("Person with ID: #{person.id}")
|
||||
|
@ -1,3 +1,5 @@
|
||||
require_relative '../support/job_buffer'
|
||||
|
||||
class HelloJob < ActiveJob::Base
|
||||
def perform(greeter = "David")
|
||||
JobBuffer.add("#{greeter} says hello")
|
||||
|
@ -1,3 +1,5 @@
|
||||
require_relative '../support/job_buffer'
|
||||
|
||||
class RescueJob < ActiveJob::Base
|
||||
class OtherError < StandardError; end
|
||||
|
||||
|
19
activejob/test/support/job_buffer.rb
Normal file
19
activejob/test/support/job_buffer.rb
Normal file
@ -0,0 +1,19 @@
|
||||
module JobBuffer
|
||||
class << self
|
||||
def clear
|
||||
values.clear
|
||||
end
|
||||
|
||||
def add(value)
|
||||
values << value
|
||||
end
|
||||
|
||||
def values
|
||||
@values ||= []
|
||||
end
|
||||
|
||||
def last_value
|
||||
values.last
|
||||
end
|
||||
end
|
||||
end
|
9
activejob/test/support/que/inline.rb
Normal file
9
activejob/test/support/que/inline.rb
Normal file
@ -0,0 +1,9 @@
|
||||
require 'que'
|
||||
|
||||
Que::Job.class_eval do
|
||||
class << self; alias_method :original_enqueue, :enqueue; end
|
||||
def self.enqueue(*args)
|
||||
args.pop if args.last.is_a?(Hash)
|
||||
self.run(*args)
|
||||
end
|
||||
end
|
@ -6,7 +6,7 @@ task :default => :test
|
||||
|
||||
Rake::TestTask.new do |t|
|
||||
t.libs << "test"
|
||||
t.test_files = Dir.glob("#{dir}/test/cases/**/*_test.rb").sort
|
||||
t.test_files = Dir.glob("#{dir}/test/cases/**/*_test.rb")
|
||||
t.warning = true
|
||||
t.verbose = true
|
||||
end
|
||||
|
@ -251,11 +251,9 @@ def as_json(options=nil)
|
||||
# person.errors.to_hash(true) # => {:name=>["name cannot be nil"]}
|
||||
def to_hash(full_messages = false)
|
||||
if full_messages
|
||||
messages = {}
|
||||
self.messages.each do |attribute, array|
|
||||
self.messages.each_with_object({}) do |(attribute, array), messages|
|
||||
messages[attribute] = array.map { |message| full_message(attribute, message) }
|
||||
end
|
||||
messages
|
||||
else
|
||||
self.messages.dup
|
||||
end
|
||||
|
@ -56,13 +56,13 @@ module ActiveModel
|
||||
# refer to the specific modules included in <tt>ActiveModel::Model</tt>
|
||||
# (see below).
|
||||
module Model
|
||||
def self.included(base) #:nodoc:
|
||||
base.class_eval do
|
||||
extend ActiveModel::Naming
|
||||
extend ActiveModel::Translation
|
||||
include ActiveModel::Validations
|
||||
include ActiveModel::Conversion
|
||||
end
|
||||
extend ActiveSupport::Concern
|
||||
include ActiveModel::Validations
|
||||
include ActiveModel::Conversion
|
||||
|
||||
included do
|
||||
extend ActiveModel::Naming
|
||||
extend ActiveModel::Translation
|
||||
end
|
||||
|
||||
# Initializes a new model with the given +params+.
|
||||
|
@ -54,7 +54,7 @@ module HelperMethods
|
||||
#
|
||||
# Configuration options:
|
||||
# * <tt>:message</tt> - A custom error message (default is: "doesn't match
|
||||
# confirmation").
|
||||
# <tt>%{translated_attribute_name}</tt>").
|
||||
#
|
||||
# There is also a list of default options supported by every validator:
|
||||
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
|
||||
|
@ -2,23 +2,6 @@
|
||||
require 'models/contact'
|
||||
require 'active_support/core_ext/object/instance_variables'
|
||||
|
||||
class Contact
|
||||
include ActiveModel::Serializers::JSON
|
||||
include ActiveModel::Validations
|
||||
|
||||
def attributes=(hash)
|
||||
hash.each do |k, v|
|
||||
instance_variable_set("@#{k}", v)
|
||||
end
|
||||
end
|
||||
|
||||
remove_method :attributes if method_defined?(:attributes)
|
||||
|
||||
def attributes
|
||||
instance_values
|
||||
end
|
||||
end
|
||||
|
||||
class JsonSerializationTest < ActiveModel::TestCase
|
||||
def setup
|
||||
@contact = Contact.new
|
||||
|
@ -3,18 +3,6 @@
|
||||
require 'active_support/core_ext/object/instance_variables'
|
||||
require 'ostruct'
|
||||
|
||||
class Contact
|
||||
include ActiveModel::Serializers::Xml
|
||||
|
||||
attr_accessor :address, :friends, :contact
|
||||
|
||||
remove_method :attributes if method_defined?(:attributes)
|
||||
|
||||
def attributes
|
||||
instance_values.except("address", "friends", "contact")
|
||||
end
|
||||
end
|
||||
|
||||
module Admin
|
||||
class Contact < ::Contact
|
||||
end
|
||||
|
@ -18,11 +18,11 @@ def teardown
|
||||
def test_single_field_validation
|
||||
r = Reply.new
|
||||
r.title = "There's no content!"
|
||||
assert r.invalid?, "A reply without content shouldn't be savable"
|
||||
assert r.invalid?, "A reply without content should be invalid"
|
||||
assert r.after_validation_performed, "after_validation callback should be called"
|
||||
|
||||
r.content = "Messa content!"
|
||||
assert r.valid?, "A reply with content should be savable"
|
||||
assert r.valid?, "A reply with content should be valid"
|
||||
assert r.after_validation_performed, "after_validation callback should be called"
|
||||
end
|
||||
|
||||
|
@ -1,8 +1,13 @@
|
||||
class Contact
|
||||
extend ActiveModel::Naming
|
||||
include ActiveModel::Conversion
|
||||
include ActiveModel::Validations
|
||||
|
||||
include ActiveModel::Serializers::JSON
|
||||
include ActiveModel::Serializers::Xml
|
||||
|
||||
attr_accessor :id, :name, :age, :created_at, :awesome, :preferences
|
||||
attr_accessor :address, :friends, :contact
|
||||
|
||||
def social
|
||||
%w(twitter github)
|
||||
@ -23,4 +28,14 @@ def pseudonyms
|
||||
def persisted?
|
||||
id
|
||||
end
|
||||
|
||||
def attributes=(hash)
|
||||
hash.each do |k, v|
|
||||
instance_variable_set("@#{k}", v)
|
||||
end
|
||||
end
|
||||
|
||||
def attributes
|
||||
instance_values.except("address", "friends", "contact")
|
||||
end
|
||||
end
|
||||
|
@ -1,3 +1,21 @@
|
||||
* `default_sequence_name` from the PostgreSQL adapter returns a `String`.
|
||||
|
||||
*Yves Senn*
|
||||
|
||||
* Fixed a regression where whitespaces were stripped from DISTINCT queries in
|
||||
PostgreSQL.
|
||||
|
||||
*Agis Anastasopoulos*
|
||||
|
||||
Fixes #16623.
|
||||
|
||||
* Fix has_many :through relation merging failing when dynamic conditions are
|
||||
passed as a lambda with an arity of one.
|
||||
|
||||
Fixes #16128.
|
||||
|
||||
*Agis Anastasopoulos*
|
||||
|
||||
* Fixed the `Relation#exists?` to work with polymorphic associations.
|
||||
|
||||
Fixes #15821.
|
||||
@ -9,6 +27,7 @@
|
||||
will not rescue those errors anymore, and just bubble them up, as the other callbacks.
|
||||
|
||||
This adds a opt-in flag to enable that behaviour, of not rescuing the errors.
|
||||
|
||||
Example:
|
||||
|
||||
# For not swallow errors in after_commit/after_rollback callbacks.
|
||||
@ -33,7 +52,7 @@
|
||||
|
||||
* Fix regression on after_commit that didnt fire when having nested transactions.
|
||||
|
||||
Fixes #16425
|
||||
Fixes #16425.
|
||||
|
||||
*arthurnn*
|
||||
|
||||
|
@ -32,8 +32,8 @@ defined in +Rakefile+)
|
||||
|
||||
== Config File
|
||||
|
||||
If +test/config.yml+ is present, it's parameters are obeyed. Otherwise, the
|
||||
parameters in +test/config.example.yml+ are obeyed.
|
||||
If +test/config.yml+ is present, then its parameters are obeyed; otherwise, the
|
||||
parameters in +test/config.example.yml+ are.
|
||||
|
||||
You can override the +connections:+ parameter in either file using the +ARCONN+
|
||||
(Active Record CONNection) environment variable:
|
||||
|
@ -51,7 +51,7 @@ end
|
||||
t.libs << 'test'
|
||||
t.test_files = (Dir.glob( "test/cases/**/*_test.rb" ).reject {
|
||||
|x| x =~ /\/adapters\//
|
||||
} + Dir.glob("test/cases/adapters/#{adapter_short}/**/*_test.rb")).sort
|
||||
} + Dir.glob("test/cases/adapters/#{adapter_short}/**/*_test.rb"))
|
||||
|
||||
t.warning = true
|
||||
t.verbose = true
|
||||
|
@ -447,9 +447,11 @@ def association_instance_set(name, association)
|
||||
#
|
||||
# Possible callbacks are: +before_add+, +after_add+, +before_remove+ and +after_remove+.
|
||||
#
|
||||
# Should any of the +before_add+ callbacks throw an exception, the object does not get
|
||||
# added to the collection. Same with the +before_remove+ callbacks; if an exception is
|
||||
# thrown the object doesn't get removed.
|
||||
# If any of the +before_add+ callbacks throw an exception, the object will not be
|
||||
# added to the collection.
|
||||
#
|
||||
# Similarly, if any of the +before_remove+ callbacks throw an exception, the object
|
||||
# will not be removed from the collection.
|
||||
#
|
||||
# == Association extensions
|
||||
#
|
||||
@ -647,7 +649,7 @@ def association_instance_set(name, association)
|
||||
# belongs_to :commenter
|
||||
# end
|
||||
#
|
||||
# When using nested association, you will not be able to modify the association because there
|
||||
# When using a nested association, you will not be able to modify the association because there
|
||||
# is not enough information to know what modification to make. For example, if you tried to
|
||||
# add a <tt>Commenter</tt> in the example above, there would be no way to tell how to set up the
|
||||
# intermediate <tt>Post</tt> and <tt>Comment</tt> objects.
|
||||
@ -717,7 +719,7 @@ def association_instance_set(name, association)
|
||||
# == Eager loading of associations
|
||||
#
|
||||
# Eager loading is a way to find objects of a certain class and a number of named associations.
|
||||
# This is one of the easiest ways of to prevent the dreaded N+1 problem in which fetching 100
|
||||
# It is one of the easiest ways to prevent the dreaded N+1 problem in which fetching 100
|
||||
# posts that each need to display their author triggers 101 database queries. Through the
|
||||
# use of eager loading, the number of queries will be reduced from 101 to 2.
|
||||
#
|
||||
@ -749,16 +751,16 @@ def association_instance_set(name, association)
|
||||
# Post.includes(:author, :comments).each do |post|
|
||||
#
|
||||
# This will load all comments with a single query. This reduces the total number of queries
|
||||
# to 3. More generally the number of queries will be 1 plus the number of associations
|
||||
# to 3. In general, the number of queries will be 1 plus the number of associations
|
||||
# named (except if some of the associations are polymorphic +belongs_to+ - see below).
|
||||
#
|
||||
# To include a deep hierarchy of associations, use a hash:
|
||||
#
|
||||
# Post.includes(:author, {comments: {author: :gravatar}}).each do |post|
|
||||
# Post.includes(:author, { comments: { author: :gravatar } }).each do |post|
|
||||
#
|
||||
# That'll grab not only all the comments but all their authors and gravatar pictures.
|
||||
# You can mix and match symbols, arrays and hashes in any combination to describe the
|
||||
# associations you want to load.
|
||||
# The above code will load all the comments and all of their associated
|
||||
# authors and gravatars. You can mix and match any combination of symbols,
|
||||
# arrays, and hashes to retrieve the associations you want to load.
|
||||
#
|
||||
# All of this power shouldn't fool you into thinking that you can pull out huge amounts
|
||||
# of data with no performance penalty just because you've reduced the number of queries.
|
||||
@ -767,8 +769,8 @@ def association_instance_set(name, association)
|
||||
# cut down on the number of queries in a situation as the one described above.
|
||||
#
|
||||
# Since only one table is loaded at a time, conditions or orders cannot reference tables
|
||||
# other than the main one. If this is the case Active Record falls back to the previously
|
||||
# used LEFT OUTER JOIN based strategy. For example
|
||||
# other than the main one. If this is the case, Active Record falls back to the previously
|
||||
# used LEFT OUTER JOIN based strategy. For example:
|
||||
#
|
||||
# Post.includes([:author, :comments]).where(['comments.approved = ?', true])
|
||||
#
|
||||
@ -1133,6 +1135,31 @@ module ClassMethods
|
||||
# * <tt>Firm#clients.create!</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save!</tt>)
|
||||
# The declaration can also include an +options+ hash to specialize the behavior of the association.
|
||||
#
|
||||
# === Scopes
|
||||
#
|
||||
# You can pass a second argument +scope+ as a callable (i.e. proc or
|
||||
# lambda) to retrieve a specific set of records or customize the generated
|
||||
# query when you access the associated collection.
|
||||
#
|
||||
# Scope examples:
|
||||
# has_many :comments, -> { where(author_id: 1) }
|
||||
# has_many :employees, -> { joins(:address) }
|
||||
# has_many :posts, ->(post) { where("max_post_length > ?", post.length) }
|
||||
#
|
||||
# === Extensions
|
||||
#
|
||||
# The +extension+ argument allows you to pass a block into a has_many
|
||||
# association. This is useful for adding new finders, creators and other
|
||||
# factory-type methods to be used as part of the association.
|
||||
#
|
||||
# Extension examples:
|
||||
# has_many :employees do
|
||||
# def find_or_create_by_name(name)
|
||||
# first_name, last_name = name.split(" ", 2)
|
||||
# find_or_create_by(first_name: first_name, last_name: last_name)
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# === Options
|
||||
# [:class_name]
|
||||
# Specify the class name of the association. Use it only if that name can't be inferred
|
||||
@ -1257,6 +1284,17 @@ def has_many(name, scope = nil, options = {}, &extension)
|
||||
# * <tt>Account#create_beneficiary</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save; b</tt>)
|
||||
# * <tt>Account#create_beneficiary!</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save!; b</tt>)
|
||||
#
|
||||
# === Scopes
|
||||
#
|
||||
# You can pass a second argument +scope+ as a callable (i.e. proc or
|
||||
# lambda) to retrieve a specific record or customize the generated query
|
||||
# when you access the associated object.
|
||||
#
|
||||
# Scope examples:
|
||||
# has_one :author, -> { where(comment_id: 1) }
|
||||
# has_one :employer, -> { joins(:company) }
|
||||
# has_one :dob, ->(dob) { where("Date.new(2000, 01, 01) > ?", dob) }
|
||||
#
|
||||
# === Options
|
||||
#
|
||||
# The declaration can also include an +options+ hash to specialize the behavior of the association.
|
||||
@ -1554,6 +1592,33 @@ def belongs_to(name, scope = nil, options = {})
|
||||
# * <tt>Developer#projects.create</tt> (similar to <tt>c = Project.new("developer_id" => id); c.save; c</tt>)
|
||||
# The declaration may include an +options+ hash to specialize the behavior of the association.
|
||||
#
|
||||
# === Scopes
|
||||
#
|
||||
# You can pass a second argument +scope+ as a callable (i.e. proc or
|
||||
# lambda) to retrieve a specific set of records or customize the generated
|
||||
# query when you access the associated collection.
|
||||
#
|
||||
# Scope examples:
|
||||
# has_and_belongs_to_many :projects, -> { includes :milestones, :manager }
|
||||
# has_and_belongs_to_many :categories, ->(category) {
|
||||
# where("default_category = ?", category.name)
|
||||
# }
|
||||
#
|
||||
# === Extensions
|
||||
#
|
||||
# The +extension+ argument allows you to pass a block into a
|
||||
# has_and_belongs_to_many association. This is useful for adding new
|
||||
# finders, creators and other factory-type methods to be used as part of
|
||||
# the association.
|
||||
#
|
||||
# Extension examples:
|
||||
# has_and_belongs_to_many :contractors do
|
||||
# def find_or_create_by_name(name)
|
||||
# first_name, last_name = name.split(" ", 2)
|
||||
# find_or_create_by(first_name: first_name, last_name: last_name)
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# === Options
|
||||
#
|
||||
# [:class_name]
|
||||
|
@ -783,7 +783,7 @@ def empty?
|
||||
# person.pets.count # => 0
|
||||
# person.pets.any? # => true
|
||||
#
|
||||
# You can also pass a block to define criteria. The behavior
|
||||
# You can also pass a +block+ to define criteria. The behavior
|
||||
# is the same, it returns true if the collection based on the
|
||||
# criteria is not empty.
|
||||
#
|
||||
@ -817,7 +817,7 @@ def any?(&block)
|
||||
# person.pets.count # => 2
|
||||
# person.pets.many? # => true
|
||||
#
|
||||
# You can also pass a block to define criteria. The
|
||||
# You can also pass a +block+ to define criteria. The
|
||||
# behavior is the same, it returns true if the collection
|
||||
# based on the criteria has more than one record.
|
||||
#
|
||||
@ -841,7 +841,7 @@ def many?(&block)
|
||||
@association.many?(&block)
|
||||
end
|
||||
|
||||
# Returns +true+ if the given object is present in the collection.
|
||||
# Returns +true+ if the given +record+ is present in the collection.
|
||||
#
|
||||
# class Person < ActiveRecord::Base
|
||||
# has_many :pets
|
||||
@ -879,7 +879,7 @@ def scope
|
||||
|
||||
# Equivalent to <tt>Array#==</tt>. Returns +true+ if the two arrays
|
||||
# contain the same number of elements and if each element is equal
|
||||
# to the corresponding element in the other array, otherwise returns
|
||||
# to the corresponding element in the +other+ array, otherwise returns
|
||||
# +false+.
|
||||
#
|
||||
# class Person < ActiveRecord::Base
|
||||
|
@ -63,12 +63,12 @@ def insert_record(record, validate = true, raise = false)
|
||||
|
||||
save_through_record(record)
|
||||
if has_cached_counter? && !through_reflection_updates_counter_cache?
|
||||
ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc)
|
||||
Automatic updating of counter caches on through associations has been
|
||||
deprecated, and will be removed in Rails 5.0. Instead, please set the
|
||||
appropriate counter_cache options on the has_many and belongs_to for
|
||||
your associations to #{through_reflection.name}.
|
||||
MESSAGE
|
||||
ActiveSupport::Deprecation.warn \
|
||||
"Automatic updating of counter caches on through associations has been " \
|
||||
"deprecated, and will be removed in Rails 5.0. Instead, please set the " \
|
||||
"appropriate counter_cache options on the has_many and belongs_to for " \
|
||||
"your associations to #{through_reflection.name}."
|
||||
|
||||
update_counter_in_database(1)
|
||||
end
|
||||
record
|
||||
|
@ -15,7 +15,11 @@ def target_scope
|
||||
scope = super
|
||||
reflection.chain.drop(1).each do |reflection|
|
||||
relation = reflection.klass.all
|
||||
relation.merge!(reflection.scope) if reflection.scope
|
||||
|
||||
reflection_scope = reflection.scope
|
||||
if reflection_scope && reflection_scope.arity.zero?
|
||||
relation.merge!(reflection_scope)
|
||||
end
|
||||
|
||||
scope.merge!(
|
||||
relation.except(:select, :create_with, :includes, :preload, :joins, :eager_load)
|
||||
|
@ -203,11 +203,9 @@ def attribute_names
|
||||
def column_for_attribute(name)
|
||||
column = columns_hash[name.to_s]
|
||||
if column.nil?
|
||||
ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc)
|
||||
`column_for_attribute` will return a null object for non-existent columns
|
||||
in Rails 5.0. Use `has_attribute?` if you need to check for an
|
||||
attribute's existence.
|
||||
MESSAGE
|
||||
ActiveSupport::Deprecation.warn \
|
||||
"`column_for_attribute` will return a null object for non-existent columns " \
|
||||
"in Rails 5.0. Use `has_attribute?` if you need to check for an attribute's existence."
|
||||
end
|
||||
column
|
||||
end
|
||||
|
@ -43,14 +43,6 @@ def initialize_dup(other) # :nodoc:
|
||||
calculate_changes_from_defaults
|
||||
end
|
||||
|
||||
def changed?
|
||||
super || changed_in_place.any?
|
||||
end
|
||||
|
||||
def changed
|
||||
super | changed_in_place
|
||||
end
|
||||
|
||||
def changes_applied
|
||||
super
|
||||
store_original_raw_attributes
|
||||
@ -62,7 +54,19 @@ def clear_changes_information
|
||||
end
|
||||
|
||||
def changed_attributes
|
||||
super.reverse_merge(attributes_changed_in_place).freeze
|
||||
# This should only be set by methods which will call changed_attributes
|
||||
# multiple times when it is known that the computed value cannot change.
|
||||
if defined?(@cached_changed_attributes)
|
||||
@cached_changed_attributes
|
||||
else
|
||||
super.reverse_merge(attributes_changed_in_place).freeze
|
||||
end
|
||||
end
|
||||
|
||||
def changes
|
||||
cache_changed_attributes do
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
@ -165,6 +169,13 @@ def store_original_raw_attributes
|
||||
store_original_raw_attribute(attr)
|
||||
end
|
||||
end
|
||||
|
||||
def cache_changed_attributes
|
||||
@cached_changed_attributes = changed_attributes
|
||||
yield
|
||||
ensure
|
||||
remove_instance_variable(:@cached_changed_attributes)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -46,9 +46,7 @@ module ClassMethods
|
||||
protected
|
||||
|
||||
def cached_attributes_deprecation_warning(method_name)
|
||||
ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc)
|
||||
Calling `#{method_name}` is no longer necessary. All attributes are cached.
|
||||
MESSAGE
|
||||
ActiveSupport::Deprecation.warn "Calling `#{method_name}` is no longer necessary. All attributes are cached."
|
||||
end
|
||||
|
||||
if Module.methods_transplantable?
|
||||
|
@ -54,10 +54,9 @@ def serialize(attr_name, class_name_or_coder = Object)
|
||||
end
|
||||
|
||||
def serialized_attributes
|
||||
ActiveSupport::Deprecation.warn(<<-WARNING.strip_heredoc)
|
||||
`serialized_attributes` is deprecated without replacement, and will
|
||||
be removed in Rails 5.0.
|
||||
WARNING
|
||||
ActiveSupport::Deprecation.warn "`serialized_attributes` is deprecated " \
|
||||
"without replacement, and will be removed in Rails 5.0."
|
||||
|
||||
@serialized_attributes ||= Hash[
|
||||
columns.select { |t| t.cast_type.is_a?(Type::Serialized) }.map { |c|
|
||||
[c.name, c.cast_type.coder]
|
||||
|
@ -61,12 +61,11 @@ module TimestampDefaultDeprecation # :nodoc:
|
||||
def emit_warning_if_null_unspecified(options)
|
||||
return if options.key?(:null)
|
||||
|
||||
ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc)
|
||||
`timestamp` was called without specifying an option for `null`. In Rails
|
||||
5.0, this behavior will change to `null: false`. You should manually
|
||||
specify `null: true` to prevent the behavior of your existing migrations
|
||||
from changing.
|
||||
MESSAGE
|
||||
ActiveSupport::Deprecation.warn \
|
||||
"`timestamp` was called without specifying an option for `null`. In Rails " \
|
||||
"5.0, this behavior will change to `null: false`. You should manually " \
|
||||
"specify `null: true` to prevent the behavior of your existing migrations " \
|
||||
"from changing."
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -25,10 +25,10 @@ def cast_value(value)
|
||||
if !infinity?(from) && extracted[:exclude_start]
|
||||
if from.respond_to?(:succ)
|
||||
from = from.succ
|
||||
ActiveSupport::Deprecation.warn <<-MESSAGE
|
||||
Excluding the beginning of a Range is only partialy supported through `#succ`.
|
||||
This is not reliable and will be removed in the future.
|
||||
MESSAGE
|
||||
ActiveSupport::Deprecation.warn \
|
||||
"Excluding the beginning of a Range is only partialy supported " \
|
||||
"through `#succ`. This is not reliable and will be removed in " \
|
||||
"the future."
|
||||
else
|
||||
raise ArgumentError, "The Ruby Range object does not support excluding the beginning of a Range. (unsupported value: '#{value}')"
|
||||
end
|
||||
|
@ -12,6 +12,8 @@ class Uuid < Type::Value # :nodoc:
|
||||
[a-fA-F0-9]{4}-?
|
||||
[a-fA-F0-9]{4}-?\}?\z}x
|
||||
|
||||
alias_method :type_cast_for_database, :type_cast_from_database
|
||||
|
||||
def type
|
||||
:uuid
|
||||
end
|
||||
|
@ -281,9 +281,9 @@ def client_min_messages=(level)
|
||||
def default_sequence_name(table_name, pk = nil) #:nodoc:
|
||||
result = serial_sequence(table_name, pk || 'id')
|
||||
return nil unless result
|
||||
Utils.extract_schema_qualified_name(result)
|
||||
Utils.extract_schema_qualified_name(result).to_s
|
||||
rescue ActiveRecord::StatementInvalid
|
||||
PostgreSQL::Name.new(nil, "#{table_name}_#{pk || 'id'}_seq")
|
||||
PostgreSQL::Name.new(nil, "#{table_name}_#{pk || 'id'}_seq").to_s
|
||||
end
|
||||
|
||||
def serial_sequence(table, column)
|
||||
@ -549,7 +549,8 @@ def columns_for_distinct(columns, orders) #:nodoc:
|
||||
# Convert Arel node to string
|
||||
s = s.to_sql unless s.is_a?(String)
|
||||
# Remove any ASC/DESC modifiers
|
||||
s.gsub(/\s+(?:ASC|DESC)?\s*(?:NULLS\s+(?:FIRST|LAST)\s*)?/i, '')
|
||||
s.gsub(/\s+(?:ASC|DESC)\b/i, '')
|
||||
.gsub(/\s+NULLS\s+(?:FIRST|LAST)\b/i, '')
|
||||
}.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
|
||||
|
||||
[super, *order_columns].join(', ')
|
||||
|
@ -151,7 +151,7 @@ def find(*ids)
|
||||
end
|
||||
|
||||
def find_by(*args)
|
||||
return super if current_scope || args.length > 1 || reflect_on_all_aggregations.any?
|
||||
return super if current_scope || !(Hash === args.first) || reflect_on_all_aggregations.any?
|
||||
|
||||
hash = args.first
|
||||
|
||||
@ -177,6 +177,10 @@ def find_by(*args)
|
||||
end
|
||||
end
|
||||
|
||||
def find_by!(*args)
|
||||
find_by(*args) or raise RecordNotFound
|
||||
end
|
||||
|
||||
def initialize_generated_modules
|
||||
super
|
||||
|
||||
|
@ -126,7 +126,7 @@ class FixtureClassNotFound < ActiveRecord::ActiveRecordError #:nodoc:
|
||||
# that is included in <tt>ActiveRecord::FixtureSet.context_class</tt>.
|
||||
#
|
||||
# - define a helper method in `test_helper.rb`
|
||||
# class FixtureFileHelpers
|
||||
# module FixtureFileHelpers
|
||||
# def file_sha(path)
|
||||
# Digest::SHA2.hexdigest(File.read(Rails.root.join('test/fixtures', path)))
|
||||
# end
|
||||
@ -870,11 +870,11 @@ def fixtures(*fixture_set_names)
|
||||
def try_to_load_dependency(file_name)
|
||||
require_dependency file_name
|
||||
rescue LoadError => e
|
||||
# Let's hope the developer has included it
|
||||
# Let's warn in case this is a subdependency, otherwise
|
||||
# subdependency error messages are totally cryptic
|
||||
if ActiveRecord::Base.logger
|
||||
ActiveRecord::Base.logger.warn("Unable to load #{file_name}, underlying cause #{e.message} \n\n #{e.backtrace.join("\n")}")
|
||||
unless fixture_class_names.key?(file_name.pluralize)
|
||||
if ActiveRecord::Base.logger
|
||||
ActiveRecord::Base.logger.warn("Unable to load #{file_name}, make sure you added it to ActiveSupport::TestCase.set_fixture_class")
|
||||
ActiveRecord::Base.logger.warn("underlying cause #{e.message} \n\n #{e.backtrace.join("\n")}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -161,21 +161,14 @@ def initialize
|
||||
# in the <tt>db/migrate/</tt> directory where <tt>timestamp</tt> is the
|
||||
# UTC formatted date and time that the migration was generated.
|
||||
#
|
||||
# You may then edit the <tt>up</tt> and <tt>down</tt> methods of
|
||||
# MyNewMigration.
|
||||
#
|
||||
# There is a special syntactic shortcut to generate migrations that add fields to a table.
|
||||
#
|
||||
# rails generate migration add_fieldname_to_tablename fieldname:string
|
||||
#
|
||||
# This will generate the file <tt>timestamp_add_fieldname_to_tablename</tt>, which will look like this:
|
||||
# class AddFieldnameToTablename < ActiveRecord::Migration
|
||||
# def up
|
||||
# add_column :tablenames, :fieldname, :string
|
||||
# end
|
||||
#
|
||||
# def down
|
||||
# remove_column :tablenames, :fieldname
|
||||
# def change
|
||||
# add_column :tablenames, :field, :string
|
||||
# end
|
||||
# end
|
||||
#
|
||||
@ -188,9 +181,12 @@ def initialize
|
||||
#
|
||||
# To roll the database back to a previous migration version, use
|
||||
# <tt>rake db:migrate VERSION=X</tt> where <tt>X</tt> is the version to which
|
||||
# you wish to downgrade. If any of the migrations throw an
|
||||
# <tt>ActiveRecord::IrreversibleMigration</tt> exception, that step will fail and you'll
|
||||
# have some manual work to do.
|
||||
# you wish to downgrade. Alternatively, you can also use the STEP option if you
|
||||
# wish to rollback last few migrations. <tt>rake db:migrate STEP=2</tt> will rollback
|
||||
# the latest two migrations.
|
||||
#
|
||||
# If any of the migrations throw an <tt>ActiveRecord::IrreversibleMigration</tt> exception,
|
||||
# that step will fail and you'll have some manual work to do.
|
||||
#
|
||||
# == Database support
|
||||
#
|
||||
|
@ -38,7 +38,7 @@ def self.add_aggregate_reflection(ar, name, reflection)
|
||||
ar.aggregate_reflections = ar.aggregate_reflections.merge(name.to_s => reflection)
|
||||
end
|
||||
|
||||
# \Reflection enables interrogating Active Record classes and objects
|
||||
# \Reflection enables interrogating of Active Record classes and objects
|
||||
# about their associations and aggregations. This information can,
|
||||
# for example, be used in a form builder that takes an Active Record object
|
||||
# and creates input fields for all of the attributes depending on their type
|
||||
@ -339,12 +339,13 @@ def check_preloadable!
|
||||
return unless scope
|
||||
|
||||
if scope.arity > 0
|
||||
ActiveSupport::Deprecation.warn <<-WARNING
|
||||
The association scope '#{name}' is instance dependent (the scope block takes an argument).
|
||||
Preloading happens before the individual instances are created. This means that there is no instance
|
||||
being passed to the association scope. This will most likely result in broken or incorrect behavior.
|
||||
Joining, Preloading and eager loading of these associations is deprecated and will be removed in the future.
|
||||
WARNING
|
||||
ActiveSupport::Deprecation.warn \
|
||||
"The association scope '#{name}' is instance dependent (the scope " \
|
||||
"block takes an argument). Preloading happens before the individual " \
|
||||
"instances are created. This means that there is no instance being " \
|
||||
"passed to the association scope. This will most likely result in " \
|
||||
"broken or incorrect behavior. Joining, Preloading and eager loading " \
|
||||
"of these associations is deprecated and will be removed in the future."
|
||||
end
|
||||
end
|
||||
alias :check_eager_loadable! :check_preloadable!
|
||||
@ -787,15 +788,13 @@ def source_reflection_name # :nodoc:
|
||||
if names.length > 1
|
||||
example_options = options.dup
|
||||
example_options[:source] = source_reflection_names.first
|
||||
ActiveSupport::Deprecation.warn <<-eowarn
|
||||
Ambiguous source reflection for through association. Please specify a :source
|
||||
directive on your declaration like:
|
||||
|
||||
class #{active_record.name} < ActiveRecord::Base
|
||||
#{macro} :#{name}, #{example_options}
|
||||
end
|
||||
|
||||
eowarn
|
||||
ActiveSupport::Deprecation.warn \
|
||||
"Ambiguous source reflection for through association. Please " \
|
||||
"specify a :source directive on your declaration like:\n" \
|
||||
"\n" \
|
||||
" class #{active_record.name} < ActiveRecord::Base\n" \
|
||||
" #{macro} :#{name}, #{example_options}\n" \
|
||||
" end"
|
||||
end
|
||||
|
||||
@source_reflection_name = names.first
|
||||
|
@ -94,10 +94,8 @@ def #{name}_value=(value) # def readonly_value=(value)
|
||||
def check_cached_relation # :nodoc:
|
||||
if defined?(@arel) && @arel
|
||||
@arel = nil
|
||||
ActiveSupport::Deprecation.warn <<-WARNING
|
||||
Modifying already cached Relation. The cache will be reset.
|
||||
Use a cloned Relation to prevent this warning.
|
||||
WARNING
|
||||
ActiveSupport::Deprecation.warn "Modifying already cached Relation. The " \
|
||||
"cache will be reset. Use a cloned Relation to prevent this warning."
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -131,10 +131,12 @@ def migrate
|
||||
verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] == "true" : true
|
||||
version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil
|
||||
scope = ENV['SCOPE']
|
||||
Migration.verbose = verbose
|
||||
verbose_was = Migration.verbose
|
||||
Migrator.migrate(Migrator.migrations_paths, version) do |migration|
|
||||
scope.blank? || scope == migration.scope
|
||||
end
|
||||
ensure
|
||||
Migration.verbose = verbose_was
|
||||
end
|
||||
|
||||
def charset_current(environment = env)
|
||||
@ -184,10 +186,10 @@ def structure_load(*arguments)
|
||||
end
|
||||
|
||||
def load_schema(format = ActiveRecord::Base.schema_format, file = nil)
|
||||
ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc)
|
||||
This method will act on a specific connection in the future.
|
||||
To act on the current connection, use `load_schema_current` instead.
|
||||
MESSAGE
|
||||
ActiveSupport::Deprecation.warn \
|
||||
"This method will act on a specific connection in the future. " \
|
||||
"To act on the current connection, use `load_schema_current` instead."
|
||||
|
||||
load_schema_current(format, file)
|
||||
end
|
||||
|
||||
|
@ -3,15 +3,15 @@ module ActiveRecord
|
||||
module Transactions
|
||||
extend ActiveSupport::Concern
|
||||
ACTIONS = [:create, :destroy, :update]
|
||||
CALLBACK_WARN_MESSAGE = <<-EOF
|
||||
Currently, Active Record will rescue any errors raised within
|
||||
after_rollback/after_commit callbacks and print them to the logs. In the next
|
||||
version, these errors will no longer be rescued. Instead, they will simply
|
||||
bubble just like other Active Record callbacks.
|
||||
|
||||
You can opt into the new behavior and remove this warning by setting
|
||||
config.active_record.raise_in_transactional_callbacks to true.
|
||||
EOF
|
||||
CALLBACK_WARN_MESSAGE = "Currently, Active Record suppresses errors raised " \
|
||||
"within `after_rollback`/`after_commit` callbacks and only print them to " \
|
||||
"the logs. In the next version, these errors will no longer be suppressed. " \
|
||||
"Instead, the errors will propagate normally just like in other Active " \
|
||||
"Record callbacks.\n" \
|
||||
"\n" \
|
||||
"You can opt into the new behavior and remove this warning by setting:\n" \
|
||||
"\n" \
|
||||
" config.active_record.raise_in_transactional_callbacks = true"
|
||||
|
||||
included do
|
||||
define_callbacks :commit, :rollback,
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user