aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.travis.yml14
-rw-r--r--Gemfile4
-rw-r--r--README.md16
-rw-r--r--actionmailer/CHANGELOG.md2
-rw-r--r--actionpack/CHANGELOG.md10
-rw-r--r--actionpack/lib/action_dispatch/journey/gtg/transition_table.rb4
-rw-r--r--actionpack/lib/action_dispatch/journey/scanner.rb10
-rw-r--r--actionpack/lib/action_dispatch/middleware/exception_wrapper.rb21
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb2
-rw-r--r--actionpack/test/controller/integration_test.rb8
-rw-r--r--actionpack/test/journey/route/definition/scanner_test.rb25
-rw-r--r--actionview/lib/action_view/helpers/date_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/form_tag_helper.rb30
-rw-r--r--actionview/test/template/form_tag_helper_test.rb7
-rw-r--r--activemodel/lib/active_model/errors.rb2
-rw-r--r--activemodel/test/cases/errors_test.rb11
-rw-r--r--activerecord/CHANGELOG.md7
-rw-r--r--activerecord/lib/active_record/associations.rb6
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb7
-rw-r--r--activerecord/lib/active_record/associations/join_dependency.rb15
-rw-r--r--activerecord/lib/active_record/associations/singular_association.rb7
-rw-r--r--activerecord/lib/active_record/attribute_methods/serialization.rb3
-rw-r--r--activerecord/lib/active_record/autosave_association.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb17
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb1
-rw-r--r--activerecord/lib/active_record/querying.rb11
-rw-r--r--activerecord/lib/active_record/reflection.rb6
-rw-r--r--activerecord/lib/active_record/relation.rb5
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb16
-rw-r--r--activerecord/lib/active_record/result.rb4
-rw-r--r--activerecord/lib/active_record/transactions.rb2
-rw-r--r--activerecord/test/cases/associations/eager_test.rb36
-rw-r--r--activerecord/test/cases/associations/has_one_through_associations_test.rb6
-rw-r--r--activerecord/test/cases/autosave_association_test.rb9
-rw-r--r--activerecord/test/cases/migration/change_schema_test.rb19
-rw-r--r--activerecord/test/cases/result_test.rb4
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb2
-rw-r--r--activerecord/test/cases/scoping/relation_scoping_test.rb20
-rw-r--r--activerecord/test/models/author.rb1
-rw-r--r--activerecord/test/models/member.rb3
-rw-r--r--activerecord/test/models/post.rb9
-rw-r--r--activesupport/CHANGELOG.md16
-rw-r--r--activesupport/lib/active_support/callbacks.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/file/atomic.rb2
-rw-r--r--activesupport/lib/active_support/json/decoding.rb2
-rw-r--r--activesupport/lib/active_support/json/encoding.rb2
-rw-r--r--activesupport/test/inflector_test.rb4
-rw-r--r--guides/source/credits.html.erb2
-rw-r--r--railties/CHANGELOG.md2
-rw-r--r--railties/lib/rails/commands/commands_tasks.rb2
-rw-r--r--railties/lib/rails/commands/update.rb9
-rw-r--r--railties/lib/rails/generators/app_base.rb4
-rw-r--r--railties/lib/rails/generators/rails/controller/controller_generator.rb2
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb3
-rw-r--r--railties/test/application/middleware/cache_test.rb12
-rw-r--r--railties/test/generators/plugin_generator_test.rb4
-rw-r--r--tools/README.md7
-rw-r--r--[-rwxr-xr-x]tools/line_statistics3
-rwxr-xr-xtools/profile5
59 files changed, 343 insertions, 128 deletions
diff --git a/.travis.yml b/.travis.yml
index 520c434f06..2823a5456f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,13 +3,6 @@ script: 'ci/travis.rb'
before_install:
- travis_retry gem install bundler
- "rvm current | grep 'jruby' && export AR_JDBC=true || echo"
-rvm:
- - 1.9.3
- - 2.0.0
- - 2.1
- - ruby-head
- - rbx-2
- - jruby
env:
global:
- JRUBY_OPTS='-J-Xmx1024M'
@@ -22,6 +15,13 @@ env:
- "GEM=ar:sqlite3"
- "GEM=ar:postgresql"
- "GEM=aj:integration"
+rvm:
+ - 1.9.3
+ - 2.0.0
+ - 2.1
+ - ruby-head
+ - rbx-2
+ - jruby
matrix:
allow_failures:
- rvm: 1.9.3
diff --git a/Gemfile b/Gemfile
index 9e486b0d9e..103fb0a35a 100644
--- a/Gemfile
+++ b/Gemfile
@@ -14,8 +14,8 @@ gem 'mocha', '~> 0.14', require: false
gem 'rack-cache', '~> 1.2'
gem 'jquery-rails', '~> 4.0.0.beta2'
-gem 'coffee-rails', '~> 4.0.0'
-gem 'turbolinks', '~> 2.2.3'
+gem 'coffee-rails', '~> 4.1.0'
+gem 'turbolinks'
# require: false so bcrypt is loaded only when has_secure_password is used.
# This is to avoid ActiveModel (and by extension the entire framework)
diff --git a/README.md b/README.md
index d786914d6d..7e1bcd02ea 100644
--- a/README.md
+++ b/README.md
@@ -13,10 +13,10 @@ Person, Post, etc.) and encapsulates the business logic that is specific to
your application. In Rails, database-backed model classes are derived from
`ActiveRecord::Base`. Active Record allows you to present the data from
database rows as objects and embellish these data objects with business logic
-methods. Although most Rails models are backed by a database, models can also
-be ordinary Ruby classes, or Ruby classes that implement a set of interfaces
-as provided by the Active Model module. You can read more about Active Record
-in its [README](activerecord/README.rdoc).
+methods. You can read more about Active Record in its [README](activerecord/README.rdoc).
+Although most Rails models are backed by a database, models can also be ordinary
+Ruby classes, or Ruby classes that implement a set of interfaces as provided by
+the Active Model module. You can read more about Active Model in its [README](activemodel/README.rdoc).
The _Controller layer_ is responsible for handling incoming HTTP requests and
providing a suitable response. Usually this means returning HTML, but Rails controllers
@@ -36,9 +36,11 @@ You can read more about Action View in its [README](actionview/README.rdoc).
Active Record, Action Pack, and Action View can each be used independently outside Rails.
In addition to them, Rails also comes with Action Mailer ([README](actionmailer/README.rdoc)), a library
-to generate and send emails; and Active Support ([README](activesupport/README.rdoc)), a collection of
-utility classes and standard library extensions that are useful for Rails, and may also be used
-independently outside Rails.
+to generate and send emails; Active Job ([README](activejob/README.md)), a
+framework for declaring jobs and making them run on a variety of queueing
+backends; and Active Support ([README](activesupport/README.rdoc)), a collection
+of utility classes and standard library extensions that are useful for Rails,
+and may also be used independently outside Rails.
## Getting Started
diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md
index e2900c2d10..5685871ac9 100644
--- a/actionmailer/CHANGELOG.md
+++ b/actionmailer/CHANGELOG.md
@@ -10,7 +10,7 @@
using the new Active Job framework in Rails, and will use whatever queue is
configured for Rails.
- *DHH/Abdelkader Boudih/Cristian Bica*
+ *DHH*, *Abdelkader Boudih*, *Cristian Bica*
* Make `ActionMailer::Previews` methods class methods. Previously they were
instance methods and `ActionMailer` tries to render a message when they
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index de9722c392..4626c2650a 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,3 +1,13 @@
+* Improve Journey compliance to RFC 3986.
+
+ The scanner in Journey failed to recognize routes that use literals
+ from the sub-delims section of RFC 3986. It's now able to parse those
+ authorized delimiters and route as expected.
+
+ Fixes #17212.
+
+ *Nicolas Cavigneaux*
+
* Deprecate implicit Array conversion for Response objects. It was added
(using `#to_ary`) so we could conveniently use implicit splatting:
diff --git a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb
index 990d2127ee..1b914f0637 100644
--- a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb
+++ b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb
@@ -88,13 +88,13 @@ module ActionDispatch
erb = File.read File.join(viz_dir, 'index.html.erb')
states = "function tt() { return #{to_json}; }"
- fun_routes = paths.shuffle.first(3).map do |ast|
+ fun_routes = paths.sample(3).map do |ast|
ast.map { |n|
case n
when Nodes::Symbol
case n.left
when ':id' then rand(100).to_s
- when ':format' then %w{ xml json }.shuffle.first
+ when ':format' then %w{ xml json }.sample
else
'omg'
end
diff --git a/actionpack/lib/action_dispatch/journey/scanner.rb b/actionpack/lib/action_dispatch/journey/scanner.rb
index 633be11a2d..19e0bc03d6 100644
--- a/actionpack/lib/action_dispatch/journey/scanner.rb
+++ b/actionpack/lib/action_dispatch/journey/scanner.rb
@@ -39,18 +39,18 @@ module ActionDispatch
[:SLASH, text]
when text = @ss.scan(/\*\w+/)
[:STAR, text]
- when text = @ss.scan(/\(/)
+ when text = @ss.scan(/(?<!\\)\(/)
[:LPAREN, text]
- when text = @ss.scan(/\)/)
+ when text = @ss.scan(/(?<!\\)\)/)
[:RPAREN, text]
when text = @ss.scan(/\|/)
[:OR, text]
when text = @ss.scan(/\./)
[:DOT, text]
- when text = @ss.scan(/:\w+/)
+ when text = @ss.scan(/(?<!\\):\w+/)
[:SYMBOL, text]
- when text = @ss.scan(/[\w%\-~]+/)
- [:LITERAL, text]
+ when text = @ss.scan(/(?:[\w%\-~!$&'*+,;=@]|\\:|\\\(|\\\))+/)
+ [:LITERAL, text.tr('\\', '')]
# any char
when text = @ss.scan(/./)
[:LITERAL, text]
diff --git a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
index b98b553c38..a6285848b5 100644
--- a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
+++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
@@ -6,16 +6,17 @@ module ActionDispatch
cattr_accessor :rescue_responses
@@rescue_responses = Hash.new(:internal_server_error)
@@rescue_responses.merge!(
- 'ActionController::RoutingError' => :not_found,
- 'AbstractController::ActionNotFound' => :not_found,
- 'ActionController::MethodNotAllowed' => :method_not_allowed,
- 'ActionController::UnknownHttpMethod' => :method_not_allowed,
- 'ActionController::NotImplemented' => :not_implemented,
- 'ActionController::UnknownFormat' => :not_acceptable,
- 'ActionController::InvalidAuthenticityToken' => :unprocessable_entity,
- 'ActionDispatch::ParamsParser::ParseError' => :bad_request,
- 'ActionController::BadRequest' => :bad_request,
- 'ActionController::ParameterMissing' => :bad_request
+ 'ActionController::RoutingError' => :not_found,
+ 'AbstractController::ActionNotFound' => :not_found,
+ 'ActionController::MethodNotAllowed' => :method_not_allowed,
+ 'ActionController::UnknownHttpMethod' => :method_not_allowed,
+ 'ActionController::NotImplemented' => :not_implemented,
+ 'ActionController::UnknownFormat' => :not_acceptable,
+ 'ActionController::InvalidAuthenticityToken' => :unprocessable_entity,
+ 'ActionController::InvalidCrossOriginRequest' => :unprocessable_entity,
+ 'ActionDispatch::ParamsParser::ParseError' => :bad_request,
+ 'ActionController::BadRequest' => :bad_request,
+ 'ActionController::ParameterMissing' => :bad_request
)
cattr_accessor :rescue_templates
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index fc28740828..a121fef663 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -434,7 +434,7 @@ module ActionDispatch
#
# Because requesting various HTTP verbs with a single action has security
# implications, you must either specify the actions in
- # the via options or use one of the HtttpHelpers[rdoc-ref:HttpHelpers]
+ # the via options or use one of the HttpHelpers[rdoc-ref:HttpHelpers]
# instead +match+
#
# === Options
diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb
index c6380c7ffd..d91a1657b3 100644
--- a/actionpack/test/controller/integration_test.rb
+++ b/actionpack/test/controller/integration_test.rb
@@ -615,6 +615,8 @@ class ApplicationIntegrationTest < ActionDispatch::IntegrationTest
get 'bar', :to => 'application_integration_test/test#index', :as => :bar
mount MountedApp => '/mounted', :as => "mounted"
+ get 'fooz' => proc { |env| [ 200, {'X-Cascade' => 'pass'}, [ "omg" ] ] }, :anchor => false
+ get 'fooz', :to => 'application_integration_test/test#index'
end
def app
@@ -631,6 +633,12 @@ class ApplicationIntegrationTest < ActionDispatch::IntegrationTest
assert_equal '/mounted/baz', mounted.baz_path
end
+ test "path after cascade pass" do
+ get '/fooz'
+ assert_equal 'index', response.body
+ assert_equal '/fooz', path
+ end
+
test "route helpers after controller access" do
get '/'
assert_equal '/', empty_string_path
diff --git a/actionpack/test/journey/route/definition/scanner_test.rb b/actionpack/test/journey/route/definition/scanner_test.rb
index 624e6df51a..7a510f1e07 100644
--- a/actionpack/test/journey/route/definition/scanner_test.rb
+++ b/actionpack/test/journey/route/definition/scanner_test.rb
@@ -11,12 +11,25 @@ module ActionDispatch
# /page/:id(/:action)(.:format)
def test_tokens
[
- ['/', [[:SLASH, '/']]],
- ['*omg', [[:STAR, '*omg']]],
- ['/page', [[:SLASH, '/'], [:LITERAL, 'page']]],
- ['/~page', [[:SLASH, '/'], [:LITERAL, '~page']]],
- ['/pa-ge', [[:SLASH, '/'], [:LITERAL, 'pa-ge']]],
- ['/:page', [[:SLASH, '/'], [:SYMBOL, ':page']]],
+ ['/', [[:SLASH, '/']]],
+ ['*omg', [[:STAR, '*omg']]],
+ ['/page', [[:SLASH, '/'], [:LITERAL, 'page']]],
+ ['/page!', [[:SLASH, '/'], [:LITERAL, 'page!']]],
+ ['/page$', [[:SLASH, '/'], [:LITERAL, 'page$']]],
+ ['/page&', [[:SLASH, '/'], [:LITERAL, 'page&']]],
+ ["/page'", [[:SLASH, '/'], [:LITERAL, "page'"]]],
+ ['/page*', [[:SLASH, '/'], [:LITERAL, 'page*']]],
+ ['/page+', [[:SLASH, '/'], [:LITERAL, 'page+']]],
+ ['/page,', [[:SLASH, '/'], [:LITERAL, 'page,']]],
+ ['/page;', [[:SLASH, '/'], [:LITERAL, 'page;']]],
+ ['/page=', [[:SLASH, '/'], [:LITERAL, 'page=']]],
+ ['/page@', [[:SLASH, '/'], [:LITERAL, 'page@']]],
+ ['/page\:', [[:SLASH, '/'], [:LITERAL, 'page:']]],
+ ['/page\(', [[:SLASH, '/'], [:LITERAL, 'page(']]],
+ ['/page\)', [[:SLASH, '/'], [:LITERAL, 'page)']]],
+ ['/~page', [[:SLASH, '/'], [:LITERAL, '~page']]],
+ ['/pa-ge', [[:SLASH, '/'], [:LITERAL, 'pa-ge']]],
+ ['/:page', [[:SLASH, '/'], [:SYMBOL, ':page']]],
['/(:page)', [
[:SLASH, '/'],
[:LPAREN, '('],
diff --git a/actionview/lib/action_view/helpers/date_helper.rb b/actionview/lib/action_view/helpers/date_helper.rb
index 9272bb5c10..01a9747035 100644
--- a/actionview/lib/action_view/helpers/date_helper.rb
+++ b/actionview/lib/action_view/helpers/date_helper.rb
@@ -1035,7 +1035,7 @@ module ActionView
def build_selects_from_types(order)
select = ''
first_visible = order.find { |type| !@options[:"discard_#{type}"] }
- order.reverse.each do |type|
+ order.reverse_each do |type|
separator = separator(type) unless type == first_visible # don't add before first visible field
select.insert(0, separator.to_s + send("select_#{type}").to_s)
end
diff --git a/actionview/lib/action_view/helpers/form_tag_helper.rb b/actionview/lib/action_view/helpers/form_tag_helper.rb
index ff5d278b85..69893b8abd 100644
--- a/actionview/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/form_tag_helper.rb
@@ -224,7 +224,7 @@ module ActionView
# # => <input id="collected_input" name="collected_input" onchange="alert('Input collected!')"
# # type="hidden" value="" />
def hidden_field_tag(name, value = nil, options = {})
- text_field_tag(name, value, options.stringify_keys.update("type" => "hidden"))
+ text_field_tag(name, value, options.merge(type: :hidden))
end
# Creates a file upload field. If you are using file uploads then you will also need
@@ -263,7 +263,7 @@ module ActionView
# file_field_tag 'file', accept: 'text/html', class: 'upload', value: 'index.html'
# # => <input accept="text/html" class="upload" id="file" name="file" type="file" value="index.html" />
def file_field_tag(name, options = {})
- text_field_tag(name, nil, options.update("type" => "file"))
+ text_field_tag(name, nil, options.merge(type: :file))
end
# Creates a password field, a masked text field that will hide the users input behind a mask character.
@@ -296,7 +296,7 @@ module ActionView
# password_field_tag 'pin', '1234', maxlength: 4, size: 6, class: "pin_input"
# # => <input class="pin_input" id="pin" maxlength="4" name="pin" size="6" type="password" value="1234" />
def password_field_tag(name = "password", value = nil, options = {})
- text_field_tag(name, value, options.update("type" => "password"))
+ text_field_tag(name, value, options.merge(type: :password))
end
# Creates a text input area; use a textarea for longer text inputs such as blog posts or descriptions.
@@ -571,7 +571,7 @@ module ActionView
# color_field_tag 'color', '#DEF726', class: 'special_input', disabled: true
# # => <input disabled="disabled" class="special_input" id="color" name="color" type="color" value="#DEF726" />
def color_field_tag(name, value = nil, options = {})
- text_field_tag(name, value, options.stringify_keys.update("type" => "color"))
+ text_field_tag(name, value, options.merge(type: :color))
end
# Creates a text field of type "search".
@@ -592,7 +592,7 @@ module ActionView
# search_field_tag 'search', 'Enter your search query here', class: 'special_input', disabled: true
# # => <input disabled="disabled" class="special_input" id="search" name="search" type="search" value="Enter your search query here" />
def search_field_tag(name, value = nil, options = {})
- text_field_tag(name, value, options.stringify_keys.update("type" => "search"))
+ text_field_tag(name, value, options.merge(type: :search))
end
# Creates a text field of type "tel".
@@ -613,7 +613,7 @@ module ActionView
# telephone_field_tag 'tel', '0123456789', class: 'special_input', disabled: true
# # => <input disabled="disabled" class="special_input" id="tel" name="tel" type="tel" value="0123456789" />
def telephone_field_tag(name, value = nil, options = {})
- text_field_tag(name, value, options.stringify_keys.update("type" => "tel"))
+ text_field_tag(name, value, options.merge(type: :tel))
end
alias phone_field_tag telephone_field_tag
@@ -635,7 +635,7 @@ module ActionView
# date_field_tag 'date', '01/01/2014', class: 'special_input', disabled: true
# # => <input disabled="disabled" class="special_input" id="date" name="date" type="date" value="01/01/2014" />
def date_field_tag(name, value = nil, options = {})
- text_field_tag(name, value, options.stringify_keys.update("type" => "date"))
+ text_field_tag(name, value, options.merge(type: :date))
end
# Creates a text field of type "time".
@@ -646,7 +646,7 @@ module ActionView
# * <tt>:step</tt> - The acceptable value granularity.
# * Otherwise accepts the same options as text_field_tag.
def time_field_tag(name, value = nil, options = {})
- text_field_tag(name, value, options.stringify_keys.update("type" => "time"))
+ text_field_tag(name, value, options.merge(type: :time))
end
# Creates a text field of type "datetime".
@@ -657,7 +657,7 @@ module ActionView
# * <tt>:step</tt> - The acceptable value granularity.
# * Otherwise accepts the same options as text_field_tag.
def datetime_field_tag(name, value = nil, options = {})
- text_field_tag(name, value, options.stringify_keys.update("type" => "datetime"))
+ text_field_tag(name, value, options.merge(type: :datetime))
end
# Creates a text field of type "datetime-local".
@@ -668,7 +668,7 @@ module ActionView
# * <tt>:step</tt> - The acceptable value granularity.
# * Otherwise accepts the same options as text_field_tag.
def datetime_local_field_tag(name, value = nil, options = {})
- text_field_tag(name, value, options.stringify_keys.update("type" => "datetime-local"))
+ text_field_tag(name, value, options.merge(type: 'datetime-local'))
end
# Creates a text field of type "month".
@@ -679,7 +679,7 @@ module ActionView
# * <tt>:step</tt> - The acceptable value granularity.
# * Otherwise accepts the same options as text_field_tag.
def month_field_tag(name, value = nil, options = {})
- text_field_tag(name, value, options.stringify_keys.update("type" => "month"))
+ text_field_tag(name, value, options.merge(type: :month))
end
# Creates a text field of type "week".
@@ -690,7 +690,7 @@ module ActionView
# * <tt>:step</tt> - The acceptable value granularity.
# * Otherwise accepts the same options as text_field_tag.
def week_field_tag(name, value = nil, options = {})
- text_field_tag(name, value, options.stringify_keys.update("type" => "week"))
+ text_field_tag(name, value, options.merge(type: :week))
end
# Creates a text field of type "url".
@@ -711,7 +711,7 @@ module ActionView
# url_field_tag 'url', 'http://rubyonrails.org', class: 'special_input', disabled: true
# # => <input disabled="disabled" class="special_input" id="url" name="url" type="url" value="http://rubyonrails.org" />
def url_field_tag(name, value = nil, options = {})
- text_field_tag(name, value, options.stringify_keys.update("type" => "url"))
+ text_field_tag(name, value, options.merge(type: :url))
end
# Creates a text field of type "email".
@@ -732,7 +732,7 @@ module ActionView
# email_field_tag 'email', 'email@example.com', class: 'special_input', disabled: true
# # => <input disabled="disabled" class="special_input" id="email" name="email" type="email" value="email@example.com" />
def email_field_tag(name, value = nil, options = {})
- text_field_tag(name, value, options.stringify_keys.update("type" => "email"))
+ text_field_tag(name, value, options.merge(type: :email))
end
# Creates a number field.
@@ -790,7 +790,7 @@ module ActionView
# ==== Options
# * Accepts the same options as number_field_tag.
def range_field_tag(name, value = nil, options = {})
- number_field_tag(name, value, options.stringify_keys.update("type" => "range"))
+ number_field_tag(name, value, options.merge(type: :range))
end
# Creates the hidden UTF8 enforcer tag. Override this method in a helper
diff --git a/actionview/test/template/form_tag_helper_test.rb b/actionview/test/template/form_tag_helper_test.rb
index 771e3fefc3..2d89332841 100644
--- a/actionview/test/template/form_tag_helper_test.rb
+++ b/actionview/test/template/form_tag_helper_test.rb
@@ -170,6 +170,13 @@ class FormTagHelperTest < ActionView::TestCase
assert_dom_equal expected, actual
end
+ def test_multiple_field_tags_with_same_options
+ options = {class: 'important'}
+ assert_dom_equal %(<input name="title" type="file" id="title" class="important"/>), file_field_tag("title", options)
+ assert_dom_equal %(<input type="password" name="title" id="title" value="Hello!" class="important" />), password_field_tag("title", "Hello!", options)
+ assert_dom_equal %(<input type="text" name="title" id="title" value="Hello!" class="important" />), text_field_tag("title", "Hello!", options)
+ end
+
def test_radio_button_tag
actual = radio_button_tag "people", "david"
expected = %(<input id="people_david" name="people" type="radio" value="david" />)
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index 1b46727351..9105ef5dd6 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -98,6 +98,8 @@ module ActiveModel
end
# aliases include?
alias :has_key? :include?
+ # aliases include?
+ alias :key? :include?
# Get messages for +key+.
#
diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb
index 42d0365521..efedd9055f 100644
--- a/activemodel/test/cases/errors_test.rb
+++ b/activemodel/test/cases/errors_test.rb
@@ -59,6 +59,17 @@ class ErrorsTest < ActiveModel::TestCase
assert_equal false, errors.has_key?(:name), 'errors should not have key :name'
end
+ def test_key?
+ errors = ActiveModel::Errors.new(self)
+ errors[:foo] = 'omg'
+ assert_equal true, errors.key?(:foo), 'errors should have key :foo'
+ end
+
+ def test_no_key
+ errors = ActiveModel::Errors.new(self)
+ assert_equal false, errors.key?(:name), 'errors should not have key :name'
+ end
+
test "clear errors" do
person = Person.new
person.validate!
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 6bdb53ac5a..b12d048169 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,10 @@
+* Fix regression causing `after_create` callbacks to run before associated
+ records are autosaved.
+
+ Fixes #17209.
+
+ *Agis Anastasopoulos*
+
* Honor overridden `rack.test` in Rack environment for the connection
management middleware.
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 12ca3a48a9..8911506694 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -46,6 +46,12 @@ module ActiveRecord
end
end
+ class HasOneAssociationPolymorphicThroughError < ActiveRecordError #:nodoc:
+ def initialize(owner_class_name, reflection)
+ super("Cannot have a has_one :through association '#{owner_class_name}##{reflection.name}' which goes through the polymorphic association '#{owner_class_name}##{reflection.through_reflection.name}'.")
+ end
+ end
+
class HasManyThroughSourceAssociationNotFoundError < ActiveRecordError #:nodoc:
def initialize(reflection)
through_reflection = reflection.through_reflection
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 1836ff0910..bdfd569be2 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -407,7 +407,12 @@ module ActiveRecord
private
def get_records
- return scope.to_a if reflection.scope_chain.any?(&:any?) || scope.eager_loading?
+ if reflection.scope_chain.any?(&:any?) ||
+ scope.eager_loading? ||
+ klass.current_scope
+
+ return scope.to_a
+ end
conn = klass.connection
sc = reflection.association_scope_cache(conn, owner) do
diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb
index ec5c189cd3..c5c4edd090 100644
--- a/activerecord/lib/active_record/associations/join_dependency.rb
+++ b/activerecord/lib/active_record/associations/join_dependency.rb
@@ -142,11 +142,20 @@ module ActiveRecord
parents = model_cache[join_root]
column_aliases = aliases.column_aliases join_root
- result_set.each { |row_hash|
- parent = parents[row_hash[primary_key]] ||= join_root.instantiate(row_hash, column_aliases)
- construct(parent, join_root, row_hash, result_set, seen, model_cache, aliases)
+ message_bus = ActiveSupport::Notifications.instrumenter
+
+ payload = {
+ record_count: result_set.length,
+ class_name: join_root.base_klass.name
}
+ message_bus.instrument('instantiation.active_record', payload) do
+ result_set.each { |row_hash|
+ parent = parents[row_hash[primary_key]] ||= join_root.instantiate(row_hash, column_aliases)
+ construct(parent, join_root, row_hash, result_set, seen, model_cache, aliases)
+ }
+ end
+
parents.values
end
diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb
index b9326b9683..c360ef1b2c 100644
--- a/activerecord/lib/active_record/associations/singular_association.rb
+++ b/activerecord/lib/active_record/associations/singular_association.rb
@@ -39,7 +39,12 @@ module ActiveRecord
end
def get_records
- return scope.limit(1).to_a if reflection.scope_chain.any?(&:any?) || scope.eager_loading?
+ if reflection.scope_chain.any?(&:any?) ||
+ scope.eager_loading? ||
+ klass.current_scope
+
+ return scope.limit(1).to_a
+ end
conn = klass.connection
sc = reflection.association_scope_cache(conn, owner) do
diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb
index 100d6d4229..04a1df37c6 100644
--- a/activerecord/lib/active_record/attribute_methods/serialization.rb
+++ b/activerecord/lib/active_record/attribute_methods/serialization.rb
@@ -11,9 +11,6 @@ module ActiveRecord
# serialized object must be of that class on retrieval or
# <tt>SerializationTypeMismatch</tt> will be raised.
#
- # A notable side effect of serialized attributes is that the model will
- # be updated on every save, even if it is not dirty.
- #
# ==== Parameters
#
# * +attr_name+ - The field name that should be serialized.
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index c384e8c413..a0d70435fa 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -184,7 +184,9 @@ module ActiveRecord
before_save :before_save_collection_association
define_non_cyclic_method(save_method) { save_collection_association(reflection) }
- after_save save_method
+ # Doesn't use after_save as that would save associations added in after_create/after_update twice
+ after_create save_method
+ after_update save_method
elsif reflection.has_one?
define_method(save_method) { save_has_one_association(reflection) } unless method_defined?(save_method)
# Configures two callbacks instead of a single after_save so that
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index fe00f9d750..adb9fcaeb8 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -256,7 +256,7 @@ module ActiveRecord
name = name.to_s
type = type.to_sym
- if primary_key_column_name == name
+ if @columns_hash[name] && @columns_hash[name].primary_key?
raise ArgumentError, "you can't redefine the primary key column '#{name}'. To define a custom primary key, pass { id: false } to create_table."
end
@@ -270,7 +270,7 @@ module ActiveRecord
@columns_hash.delete name.to_s
end
- [:string, :text, :integer, :float, :decimal, :datetime, :timestamp, :time, :date, :binary, :boolean].each do |column_type|
+ [:string, :text, :integer, :bigint, :float, :decimal, :datetime, :timestamp, :time, :date, :binary, :boolean].each do |column_type|
define_method column_type do |*args|
options = args.extract_options!
column_names = args
@@ -318,7 +318,7 @@ module ActiveRecord
alias :belongs_to :references
def new_column_definition(name, type, options) # :nodoc:
- type = aliased_types[type] || type
+ type = aliased_types(type.to_s, type)
column = create_column_definition name, type
limit = options.fetch(:limit) do
native[type][:limit] if native[type].is_a?(Hash)
@@ -340,19 +340,12 @@ module ActiveRecord
ColumnDefinition.new name, type
end
- def primary_key_column_name
- primary_key_column = columns.detect { |c| c.primary_key? }
- primary_key_column && primary_key_column.name
- end
-
def native
@native
end
- def aliased_types
- HashWithIndifferentAccess.new(
- timestamp: :datetime,
- )
+ def aliased_types(name, fallback)
+ 'timestamp' == name ? :datetime : fallback
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 0d74cb6707..fe7648291d 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -94,6 +94,7 @@ module ActiveRecord
int8range: { name: "int8range" },
binary: { name: "bytea" },
boolean: { name: "boolean" },
+ bigint: { name: "bigint" },
xml: { name: "xml" },
tsvector: { name: "tsvector" },
hstore: { name: "hstore" },
diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb
index 45b6b1c925..e8de4db3a7 100644
--- a/activerecord/lib/active_record/querying.rb
+++ b/activerecord/lib/active_record/querying.rb
@@ -39,7 +39,16 @@ module ActiveRecord
result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds)
column_types = result_set.column_types.dup
columns_hash.each_key { |k| column_types.delete k }
- result_set.map { |record| instantiate(record, column_types) }
+ message_bus = ActiveSupport::Notifications.instrumenter
+
+ payload = {
+ record_count: result_set.length,
+ class_name: name
+ }
+
+ message_bus.instrument('instantiation.active_record', payload) do
+ result_set.map { |record| instantiate(record, column_types) }
+ end
end
# Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part.
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 6b5a592ee5..22aa175ce2 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -821,7 +821,11 @@ module ActiveRecord
end
if through_reflection.polymorphic?
- raise HasManyThroughAssociationPolymorphicThroughError.new(active_record.name, self)
+ if has_one?
+ raise HasOneAssociationPolymorphicThroughError.new(active_record.name, self)
+ else
+ raise HasManyThroughAssociationPolymorphicThroughError.new(active_record.name, self)
+ end
end
if source_reflection.nil?
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index ad54d84665..7f51e4134d 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -4,8 +4,6 @@ require 'arel/collectors/bind'
module ActiveRecord
# = Active Record Relation
class Relation
- JoinOperation = Struct.new(:relation, :join_class, :on)
-
MULTI_VALUE_METHODS = [:includes, :eager_load, :preload, :select, :group,
:order, :joins, :where, :having, :bind, :references,
:extending, :unscope]
@@ -305,7 +303,8 @@ module ActiveRecord
# Updates all records with details given if they match a set of conditions supplied, limits and order can
# also be supplied. This method constructs a single SQL UPDATE statement and sends it straight to the
# database. It does not instantiate the involved models and it does not trigger Active Record callbacks
- # or validations.
+ # or validations. Values passed to `update_all` will not go through ActiveRecord's type-casting behavior.
+ # It should receive only values that can be passed as-is to the SQL database.
#
# ==== Parameters
#
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index ed56369f86..c95ec2522b 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -138,7 +138,7 @@ module ActiveRecord
# Same as +first+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
# is found. Note that <tt>first!</tt> accepts no arguments.
def first!
- first or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql}]")
+ find_nth! 0
end
# Find the last record (or last N records if a parameter is supplied).
@@ -187,7 +187,7 @@ module ActiveRecord
# Same as +second+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
# is found.
def second!
- second or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql}]")
+ find_nth! 1
end
# Find the third record.
@@ -203,7 +203,7 @@ module ActiveRecord
# Same as +third+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
# is found.
def third!
- third or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql}]")
+ find_nth! 2
end
# Find the fourth record.
@@ -219,7 +219,7 @@ module ActiveRecord
# Same as +fourth+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
# is found.
def fourth!
- fourth or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql}]")
+ find_nth! 3
end
# Find the fifth record.
@@ -235,7 +235,7 @@ module ActiveRecord
# Same as +fifth+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
# is found.
def fifth!
- fifth or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql}]")
+ find_nth! 4
end
# Find the forty-second record. Also known as accessing "the reddit".
@@ -251,7 +251,7 @@ module ActiveRecord
# Same as +forty_two+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
# is found.
def forty_two!
- forty_two or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql}]")
+ find_nth! 41
end
# Returns +true+ if a record exists in the table that matches the +id+ or
@@ -489,6 +489,10 @@ module ActiveRecord
end
end
+ def find_nth!(index)
+ find_nth(index, offset_index) or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql}]")
+ end
+
def find_nth_with_limit(offset, limit)
relation = if order_values.empty? && primary_key
order(arel_table[primary_key].asc)
diff --git a/activerecord/lib/active_record/result.rb b/activerecord/lib/active_record/result.rb
index 8405fdaeb9..3a3e65ef32 100644
--- a/activerecord/lib/active_record/result.rb
+++ b/activerecord/lib/active_record/result.rb
@@ -42,6 +42,10 @@ module ActiveRecord
@column_types = column_types
end
+ def length
+ @rows.length
+ end
+
def each
if block_given?
hash_rows.each { |row| yield row }
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index e53297d0ab..bb06d0304b 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -11,7 +11,7 @@ module ActiveRecord
"\n" \
"You can opt into the new behavior and remove this warning by setting:\n" \
"\n" \
- " config.active_record.raise_in_transactional_callbacks = true"
+ " config.active_record.raise_in_transactional_callbacks = true\n\n"
included do
define_callbacks :commit, :rollback,
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index b852bd3536..8234ee95be 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -935,6 +935,42 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_equal 3, authors(:david).posts_with_comments.where("length(comments.body) > 15").references(:comments).count
end
+ def test_association_loading_notification
+ notifications = messages_for('instantiation.active_record') do
+ Developer.all.merge!(:includes => 'projects', :where => { 'developers_projects.access_level' => 1 }, :limit => 5).to_a.size
+ end
+
+ message = notifications.first
+ payload = message.last
+ count = Developer.all.merge!(:includes => 'projects', :where => { 'developers_projects.access_level' => 1 }, :limit => 5).to_a.size
+
+ # eagerloaded row count should be greater than just developer count
+ assert_operator payload[:record_count], :>, count
+ assert_equal Developer.name, payload[:class_name]
+ end
+
+ def test_base_messages
+ notifications = messages_for('instantiation.active_record') do
+ Developer.all.to_a
+ end
+ message = notifications.first
+ payload = message.last
+
+ assert_equal Developer.all.to_a.count, payload[:record_count]
+ assert_equal Developer.name, payload[:class_name]
+ end
+
+ def messages_for(name)
+ notifications = []
+ ActiveSupport::Notifications.subscribe(name) do |*args|
+ notifications << args
+ end
+ yield
+ notifications
+ ensure
+ ActiveSupport::Notifications.unsubscribe(name)
+ end
+
def test_load_with_sti_sharing_association
assert_queries(2) do #should not do 1 query per subclass
Comment.includes(:post).to_a
diff --git a/activerecord/test/cases/associations/has_one_through_associations_test.rb b/activerecord/test/cases/associations/has_one_through_associations_test.rb
index 089cb0a3a2..19d1aa87a8 100644
--- a/activerecord/test/cases/associations/has_one_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb
@@ -289,6 +289,12 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
end
end
+ def test_has_one_through_polymorphic_association
+ assert_raise(ActiveRecord::HasOneAssociationPolymorphicThroughError) do
+ @member.premium_club
+ end
+ end
+
def test_has_one_through_belongs_to_should_update_when_the_through_foreign_key_changes
minivan = minivans(:cool_first)
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index b2a7d3956d..734fd5fe18 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -1,5 +1,6 @@
require 'cases/helper'
require 'models/bird'
+require 'models/comment'
require 'models/company'
require 'models/customer'
require 'models/developer'
@@ -616,6 +617,14 @@ class TestDefaultAutosaveAssociationOnNewRecord < ActiveRecord::TestCase
firm.save!
assert !account.persisted?
end
+
+ def test_autosave_new_record_with_after_create_callback
+ post = PostWithAfterCreateCallback.new(title: 'Captain Murphy', body: 'is back')
+ post.comments.build(body: 'foo')
+ post.save!
+
+ assert_not_nil post.author_id
+ end
end
class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/migration/change_schema_test.rb b/activerecord/test/cases/migration/change_schema_test.rb
index bd3dd29f4d..d91e7142b3 100644
--- a/activerecord/test/cases/migration/change_schema_test.rb
+++ b/activerecord/test/cases/migration/change_schema_test.rb
@@ -97,6 +97,25 @@ module ActiveRecord
end
end
+ def test_create_table_with_bigint
+ connection.create_table :testings do |t|
+ t.bigint :eight_int
+ end
+ columns = connection.columns(:testings)
+ eight = columns.detect { |c| c.name == "eight_int" }
+
+ if current_adapter?(:OracleAdapter)
+ assert_equal 'NUMBER(8)', eight.sql_type
+ elsif current_adapter?(:SQLite3Adapter)
+ assert_equal 'bigint', eight.sql_type
+ else
+ assert_equal :integer, eight.type
+ assert_equal 8, eight.limit
+ end
+ ensure
+ connection.drop_table :testings
+ end
+
def test_create_table_with_limits
connection.create_table :testings do |t|
t.column :foo, :string, :limit => 255
diff --git a/activerecord/test/cases/result_test.rb b/activerecord/test/cases/result_test.rb
index d6decafad9..dec01dfa76 100644
--- a/activerecord/test/cases/result_test.rb
+++ b/activerecord/test/cases/result_test.rb
@@ -10,6 +10,10 @@ module ActiveRecord
])
end
+ test "length" do
+ assert_equal 3, result.length
+ end
+
test "to_hash returns row_hashes" do
assert_equal [
{'col_1' => 'row 1 col 1', 'col_2' => 'row 1 col 2'},
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index 93fb502410..2241c41f36 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -19,7 +19,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
def test_dump_schema_information_outputs_lexically_ordered_versions
versions = %w{ 20100101010101 20100201010101 20100301010101 }
- versions.reverse.each do |v|
+ versions.reverse_each do |v|
ActiveRecord::SchemaMigration.create!(:version => v)
end
diff --git a/activerecord/test/cases/scoping/relation_scoping_test.rb b/activerecord/test/cases/scoping/relation_scoping_test.rb
index 8e512e118a..73835c85a8 100644
--- a/activerecord/test/cases/scoping/relation_scoping_test.rb
+++ b/activerecord/test/cases/scoping/relation_scoping_test.rb
@@ -15,6 +15,26 @@ class RelationScopingTest < ActiveRecord::TestCase
developers(:david)
end
+ def test_unscoped_breaks_caching
+ author = authors :mary
+ assert_nil author.first_post
+ post = FirstPost.unscoped do
+ author.reload.first_post
+ end
+ assert post
+ end
+
+ def test_scope_breaks_caching_on_collections
+ author = authors :david
+ ids = author.reload.special_posts_with_default_scope.map(&:id)
+ assert_equal [1,5,6], ids.sort
+ scoped_posts = SpecialPostWithDefaultScope.unscoped do
+ author = authors :david
+ author.reload.special_posts_with_default_scope.to_a
+ end
+ assert_equal author.posts.map(&:id).sort, scoped_posts.map(&:id).sort
+ end
+
def test_reverse_order
assert_equal Developer.order("id DESC").to_a.reverse, Developer.order("id DESC").reverse_order
end
diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb
index 8949cf5826..3f34d09a04 100644
--- a/activerecord/test/models/author.rb
+++ b/activerecord/test/models/author.rb
@@ -44,6 +44,7 @@ class Author < ActiveRecord::Base
has_many :special_posts
has_many :special_post_comments, :through => :special_posts, :source => :comments
+ has_many :special_posts_with_default_scope, :class_name => 'SpecialPostWithDefaultScope'
has_many :sti_posts, :class_name => 'StiPost'
has_many :sti_post_comments, :through => :sti_posts, :source => :comments
diff --git a/activerecord/test/models/member.rb b/activerecord/test/models/member.rb
index 72095f9236..dc0566d8a7 100644
--- a/activerecord/test/models/member.rb
+++ b/activerecord/test/models/member.rb
@@ -27,6 +27,9 @@ class Member < ActiveRecord::Base
has_many :clubs, :through => :current_memberships
has_one :club_through_many, :through => :current_memberships, :source => :club
+
+ belongs_to :admittable, polymorphic: true
+ has_one :premium_club, through: :admittable
end
class SelfMember < ActiveRecord::Base
diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb
index 256b720c9a..67027cbc22 100644
--- a/activerecord/test/models/post.rb
+++ b/activerecord/test/models/post.rb
@@ -219,6 +219,15 @@ class PostThatLoadsCommentsInAnAfterSaveHook < ActiveRecord::Base
end
end
+class PostWithAfterCreateCallback < ActiveRecord::Base
+ self.table_name = 'posts'
+ has_many :comments, foreign_key: :post_id
+
+ after_create do |post|
+ update_attribute(:author_id, comments.first.id)
+ end
+end
+
class PostWithCommentWithDefaultScopeReferencesAssociation < ActiveRecord::Base
self.table_name = 'posts'
has_many :comment_with_default_scope_references_associations, foreign_key: :post_id
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 74d57180fe..a935d33686 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -23,8 +23,8 @@
*Joost Lubach*
-* Time#change can now change nanoseconds (:nsec) as a higher-precision
- alternative to microseconds (:usec).
+* `Time#change` can now change nanoseconds (`:nsec`) as a higher-precision
+ alternative to microseconds (`:usec`).
*Agis Anastasooulos*
@@ -39,14 +39,14 @@
*Akira Matsuda*, *Godfrey Chan*
-* Fixed a bug in Inflector#underscore where acroynms in nested constant names
+* Fixed a bug in `Inflector#underscore` where acroynms in nested constant names
are incorrectly parsed as camelCase.
Fixes #8015.
*Fred Wu*, *Matthew Draper*
-* Make Time#change throw an exception if the :usec option is out of range and
+* Make `Time#change` throw an exception if the `:usec` option is out of range and
the time has an offset other than UTC or local.
*Agis Anastasopoulos*
@@ -65,11 +65,11 @@
* Added instance_eval version to Object#try, so you can do this:
- person.try { name.first }
+ person.try { name.first }
instead of:
- person.try { |person| person.name.first }
+ person.try { |person| person.name.first }
*DHH*
@@ -79,12 +79,12 @@
*Robin Dupret*
-* Fix rounding errors with #travel_to by resetting the usec on any passed time to zero, so we only travel
+* Fix rounding errors with `#travel_to` by resetting the usec on any passed time to zero, so we only travel
with per-second precision, not anything deeper than that.
*DHH*
-* Fix DateTime comparison with DateTime::Infinity object.
+* Fix DateTime comparison with `DateTime::Infinity` object.
*Rafael Mendonça França*
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index 4bc13f20ca..45231bc101 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -559,7 +559,7 @@ module ActiveSupport
# This is used internally to append, prepend and skip callbacks to the
# CallbackChain.
def __update_callbacks(name) #:nodoc:
- ([self] + ActiveSupport::DescendantsTracker.descendants(self)).reverse.each do |target|
+ ([self] + ActiveSupport::DescendantsTracker.descendants(self)).reverse_each do |target|
chain = target.get_callbacks name
yield target, chain.dup
end
diff --git a/activesupport/lib/active_support/core_ext/file/atomic.rb b/activesupport/lib/active_support/core_ext/file/atomic.rb
index 0e7e3ba378..38374af388 100644
--- a/activesupport/lib/active_support/core_ext/file/atomic.rb
+++ b/activesupport/lib/active_support/core_ext/file/atomic.rb
@@ -40,7 +40,7 @@ class File
chown(old_stat.uid, old_stat.gid, file_name)
# This operation will affect filesystem ACL's
chmod(old_stat.mode, file_name)
- rescue Errno::EPERM
+ rescue Errno::EPERM, Errno::EACCES
# Changing file ownership failed, moving on.
end
end
diff --git a/activesupport/lib/active_support/json/decoding.rb b/activesupport/lib/active_support/json/decoding.rb
index 8b5fc70dee..35548f3f56 100644
--- a/activesupport/lib/active_support/json/decoding.rb
+++ b/activesupport/lib/active_support/json/decoding.rb
@@ -12,7 +12,7 @@ module ActiveSupport
class << self
# Parses a JSON string (JavaScript Object Notation) into a hash.
- # See www.json.org for more info.
+ # See http://www.json.org for more info.
#
# ActiveSupport::JSON.decode("{\"team\":\"rails\",\"players\":\"36\"}")
# => {"team" => "rails", "players" => "36"}
diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb
index f29d42276d..a14ed7ee94 100644
--- a/activesupport/lib/active_support/json/encoding.rb
+++ b/activesupport/lib/active_support/json/encoding.rb
@@ -13,7 +13,7 @@ module ActiveSupport
module JSON
# Dumps objects in JSON (JavaScript Object Notation).
- # See www.json.org for more info.
+ # See http://www.json.org for more info.
#
# ActiveSupport::JSON.encode({ team: 'rails', players: '36' })
# # => "{\"team\":\"rails\",\"players\":\"36\"}"
diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb
index 5446c5ec3c..be68bb2e2e 100644
--- a/activesupport/test/inflector_test.rb
+++ b/activesupport/test/inflector_test.rb
@@ -490,8 +490,8 @@ class InflectorTest < ActiveSupport::TestCase
assert_equal [], inflect.uncountables
# restore all the inflections
- singulars.reverse.each { |singular| inflect.singular(*singular) }
- plurals.reverse.each { |plural| inflect.plural(*plural) }
+ singulars.reverse_each { |singular| inflect.singular(*singular) }
+ plurals.reverse_each { |plural| inflect.plural(*plural) }
inflect.uncountable(uncountables)
assert_equal singulars, inflect.singulars
diff --git a/guides/source/credits.html.erb b/guides/source/credits.html.erb
index 8767fbecce..61ea0b44ef 100644
--- a/guides/source/credits.html.erb
+++ b/guides/source/credits.html.erb
@@ -40,7 +40,7 @@ Oscar Del Ben is a software engineer at <a href="http://www.wildfireapp.com/">Wi
<% end %>
<%= author('Tore Darell', 'toretore') do %>
- Tore Darell is an independent developer based in Menton, France who specialises in cruft-free web applications using Ruby, Rails and unobtrusive JavaScript. His home on the Internet is his blog <a href="http://tore.darell.no">Sneaky Abstractions</a>.
+ Tore Darell is an independent developer based in Menton, France who specialises in cruft-free web applications using Ruby, Rails and unobtrusive JavaScript. You can follow him on <a href="http://twitter.com/toretore">Twitter</a>.
<% end %>
<%= author('Jeff Dean', 'zilkey') do %>
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index 1e1afef26d..e1a11482e6 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,4 +1,4 @@
-* Remove --skip-action-view option from Rails::Generators::AppBase
+* Remove `--skip-action-view` option from `Rails::Generators::AppBase`.
Fixes #17023.
diff --git a/railties/lib/rails/commands/commands_tasks.rb b/railties/lib/rails/commands/commands_tasks.rb
index 6cfbc70c51..8bae08e44e 100644
--- a/railties/lib/rails/commands/commands_tasks.rb
+++ b/railties/lib/rails/commands/commands_tasks.rb
@@ -127,7 +127,7 @@ EOT
require 'rails/generators'
require_application_and_environment!
Rails.application.load_generators
- require "rails/commands/#{command}"
+ require_command!(command)
end
# Change to the application's path if there is no config.ru file in current directory.
diff --git a/railties/lib/rails/commands/update.rb b/railties/lib/rails/commands/update.rb
deleted file mode 100644
index 59fae5c337..0000000000
--- a/railties/lib/rails/commands/update.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-require File.expand_path(File.join(File.dirname(__FILE__), '..', 'generators'))
-
-if ARGV.size == 0
- Rails::Generators.help
- exit
-end
-
-name = ARGV.shift
-Rails::Generators.invoke name, ARGV, behavior: :skip
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index 0d95bb48e0..1eca86bd30 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -265,11 +265,11 @@ module Rails
end
def coffee_gemfile_entry
- comment = 'Use CoffeeScript for .js.coffee assets and views'
+ comment = 'Use CoffeeScript for .coffee assets and views'
if options.dev? || options.edge?
GemfileEntry.github 'coffee-rails', 'rails/coffee-rails', comment
else
- GemfileEntry.version 'coffee-rails', '~> 4.0.0', comment
+ GemfileEntry.version 'coffee-rails', '~> 4.1.0', comment
end
end
diff --git a/railties/lib/rails/generators/rails/controller/controller_generator.rb b/railties/lib/rails/generators/rails/controller/controller_generator.rb
index a48cc13ed7..df615c88b5 100644
--- a/railties/lib/rails/generators/rails/controller/controller_generator.rb
+++ b/railties/lib/rails/generators/rails/controller/controller_generator.rb
@@ -12,7 +12,7 @@ module Rails
def add_routes
unless options[:skip_routes]
- actions.reverse.each do |action|
+ actions.reverse_each do |action|
route generate_routing_code(action)
end
end
diff --git a/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb b/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb
index 28cdfecf81..d492e68357 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb
+++ b/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb
@@ -4,6 +4,9 @@ ENV["RAILS_ENV"] = "test"
require File.expand_path("../../<%= options[:dummy_path] -%>/config/environment.rb", __FILE__)
<% unless options[:skip_active_record] -%>
ActiveRecord::Migrator.migrations_paths = [File.expand_path("../../<%= options[:dummy_path] -%>/db/migrate", __FILE__)]
+<% if options[:mountable] -%>
+ActiveRecord::Migrator.migrations_paths << File.expand_path('../../db/migrate', __FILE__)
+<% end -%>
<% end -%>
require "rails/test_help"
diff --git a/railties/test/application/middleware/cache_test.rb b/railties/test/application/middleware/cache_test.rb
index b4db840e68..c951dabd6c 100644
--- a/railties/test/application/middleware/cache_test.rb
+++ b/railties/test/application/middleware/cache_test.rb
@@ -81,8 +81,8 @@ module ApplicationTests
add_to_config "config.action_dispatch.rack_cache = true"
get "/expires/expires_header"
- assert_equal "miss, ignore, store", last_response.headers["X-Rack-Cache"]
- assert_equal "max-age=10, public", last_response.headers["Cache-Control"]
+ assert_equal "miss, store", last_response.headers["X-Rack-Cache"]
+ assert_equal "max-age=10, public", last_response.headers["Cache-Control"]
body = last_response.body
@@ -115,8 +115,8 @@ module ApplicationTests
add_to_config "config.action_dispatch.rack_cache = true"
get "/expires/expires_etag"
- assert_equal "miss, ignore, store", last_response.headers["X-Rack-Cache"]
- assert_equal "public", last_response.headers["Cache-Control"]
+ assert_equal "miss, store", last_response.headers["X-Rack-Cache"]
+ assert_equal "public", last_response.headers["Cache-Control"]
body = last_response.body
etag = last_response.headers["ETag"]
@@ -149,8 +149,8 @@ module ApplicationTests
add_to_config "config.action_dispatch.rack_cache = true"
get "/expires/expires_last_modified"
- assert_equal "miss, ignore, store", last_response.headers["X-Rack-Cache"]
- assert_equal "public", last_response.headers["Cache-Control"]
+ assert_equal "miss, store", last_response.headers["X-Rack-Cache"]
+ assert_equal "public", last_response.headers["Cache-Control"]
body = last_response.body
last = last_response.headers["Last-Modified"]
diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb
index ed4e100a9b..4329c6e1a4 100644
--- a/railties/test/generators/plugin_generator_test.rb
+++ b/railties/test/generators/plugin_generator_test.rb
@@ -245,6 +245,10 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_match(/stylesheet_link_tag\s+['"]bukkits\/application['"]/, contents)
assert_match(/javascript_include_tag\s+['"]bukkits\/application['"]/, contents)
end
+ assert_file "test/test_helper.rb" do |content|
+ assert_match(/ActiveRecord::Migrator\.migrations_paths.+\.\.\/test\/dummy\/db\/migrate/, content)
+ assert_match(/ActiveRecord::Migrator\.migrations_paths.+<<.+\.\.\/db\/migrate/, content)
+ end
end
def test_creating_gemspec
diff --git a/tools/README.md b/tools/README.md
new file mode 100644
index 0000000000..13be763dac
--- /dev/null
+++ b/tools/README.md
@@ -0,0 +1,7 @@
+## Rails dev tools
+
+This is a collection of utilities used for Rails internal development.
+They aren't used by Rails apps directly.
+
+ * `console` drops you in irb and loads this local Rails repos
+ * `profile` profiles `Kernel#require` to help reduce startup times
diff --git a/tools/line_statistics b/tools/line_statistics
index 5eb5cfdc3d..bfa921b095 100755..100644
--- a/tools/line_statistics
+++ b/tools/line_statistics
@@ -1,4 +1,5 @@
-#!/usr/bin/env ruby
+# Class used to calculates LOC for a provided file list.
+#
# Example:
# files = FileList["lib/active_record/**/*.rb"]
# CodeTools::LineStatistics.new(files).print_loc
diff --git a/tools/profile b/tools/profile
index a35dd18b77..eb7fc7792b 100755
--- a/tools/profile
+++ b/tools/profile
@@ -1,4 +1,7 @@
#!/usr/bin/env ruby
+# Profile require calls giving information about the time and the files that are called
+# when loading the provided file.
+#
# Example:
# tools/profile activesupport/lib/active_support.rb [ruby-prof mode] [ruby-prof printer]
ENV['NO_RELOAD'] ||= '1'
@@ -65,7 +68,7 @@ module CodeTools
private
def assert_ruby_file_exists(path)
- fail Error.new("No such file") unless File.exists?(path)
+ fail Error.new("No such file") unless File.exist?(path)
fail Error.new("#{path} is a directory") if File.directory?(path)
ruby_extension = File.extname(path) == '.rb'
ruby_executable = File.open(path, 'rb') {|f| f.readline } =~ [/\A#!.*ruby/]