aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--actionpack/lib/abstract_controller/callbacks.rb64
-rw-r--r--actionpack/lib/action_controller/metal/renderers.rb4
-rw-r--r--actionview/CHANGELOG.md15
-rw-r--r--actionview/lib/action_view/helpers/asset_tag_helper.rb6
-rw-r--r--actionview/lib/action_view/helpers/form_helper.rb1
-rw-r--r--actionview/test/template/asset_tag_helper_test.rb6
-rw-r--r--actionview/test/template/form_helper_test.rb7
-rw-r--r--activerecord/CHANGELOG.md15
-rw-r--r--activerecord/lib/active_record/associations/preloader/association.rb2
-rw-r--r--activerecord/lib/active_record/associations/through_association.rb6
-rw-r--r--activerecord/lib/active_record/relation/delegation.rb2
-rw-r--r--activerecord/lib/active_record/relation/merger.rb10
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb4
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb20
-rw-r--r--activerecord/test/cases/connection_adapters/abstract_adapter_test.rb56
-rw-r--r--activerecord/test/cases/connection_adapters/adapter_leasing_test.rb54
-rw-r--r--activerecord/test/cases/connection_adapters/connection_handler_test.rb193
-rw-r--r--activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb192
-rw-r--r--activerecord/test/cases/relation/mutation_test.rb7
-rw-r--r--activerecord/test/models/post.rb4
-rw-r--r--activerecord/test/models/reader.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/inflections.rb2
-rw-r--r--activesupport/lib/active_support/inflector/methods.rb2
-rw-r--r--activesupport/test/core_ext/string_ext_test.rb5
-rw-r--r--guides/source/getting_started.md126
25 files changed, 444 insertions, 361 deletions
diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb
index d6c941832f..69aca308d6 100644
--- a/actionpack/lib/abstract_controller/callbacks.rb
+++ b/actionpack/lib/abstract_controller/callbacks.rb
@@ -178,41 +178,35 @@ module AbstractController
# set up before_action, prepend_before_action, skip_before_action, etc.
# for each of before, after, and around.
[:before, :after, :around].each do |callback|
- class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
- # Append a before, after or around callback. See _insert_callbacks
- # for details on the allowed parameters.
- def #{callback}_action(*names, &blk) # def before_action(*names, &blk)
- _insert_callbacks(names, blk) do |name, options| # _insert_callbacks(names, blk) do |name, options|
- set_callback(:process_action, :#{callback}, name, options) # set_callback(:process_action, :before, name, options)
- end # end
- end # end
-
- alias_method :#{callback}_filter, :#{callback}_action
-
- # Prepend a before, after or around callback. See _insert_callbacks
- # for details on the allowed parameters.
- def prepend_#{callback}_action(*names, &blk) # def prepend_before_action(*names, &blk)
- _insert_callbacks(names, blk) do |name, options| # _insert_callbacks(names, blk) do |name, options|
- set_callback(:process_action, :#{callback}, name, options.merge(:prepend => true)) # set_callback(:process_action, :before, name, options.merge(:prepend => true))
- end # end
- end # end
-
- alias_method :prepend_#{callback}_filter, :prepend_#{callback}_action
-
- # Skip a before, after or around callback. See _insert_callbacks
- # for details on the allowed parameters.
- def skip_#{callback}_action(*names) # def skip_before_action(*names)
- _insert_callbacks(names) do |name, options| # _insert_callbacks(names) do |name, options|
- skip_callback(:process_action, :#{callback}, name, options) # skip_callback(:process_action, :before, name, options)
- end # end
- end # end
-
- alias_method :skip_#{callback}_filter, :skip_#{callback}_action
-
- # *_action is the same as append_*_action
- alias_method :append_#{callback}_action, :#{callback}_action # alias_method :append_before_action, :before_action
- alias_method :append_#{callback}_filter, :#{callback}_action # alias_method :append_before_filter, :before_action
- RUBY_EVAL
+ define_method "#{callback}_action" do |*names, &blk|
+ _insert_callbacks(names, blk) do |name, options|
+ set_callback(:process_action, callback, name, options)
+ end
+ end
+
+ alias_method :"#{callback}_filter", :"#{callback}_action"
+
+ define_method "prepend_#{callback}_action" do |*names, &blk|
+ _insert_callbacks(names, blk) do |name, options|
+ set_callback(:process_action, callback, name, options.merge(:prepend => true))
+ end
+ end
+
+ alias_method :"prepend_#{callback}_filter", :"prepend_#{callback}_action"
+
+ # Skip a before, after or around callback. See _insert_callbacks
+ # for details on the allowed parameters.
+ define_method "skip_#{callback}_action" do |*names|
+ _insert_callbacks(names) do |name, options|
+ skip_callback(:process_action, callback, name, options)
+ end
+ end
+
+ alias_method :"skip_#{callback}_filter", :"skip_#{callback}_action"
+
+ # *_action is the same as append_*_action
+ alias_method :"append_#{callback}_action", :"#{callback}_action" # alias_method :append_before_action, :before_action
+ alias_method :"append_#{callback}_filter", :"#{callback}_action" # alias_method :append_before_filter, :before_action
end
end
end
diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb
index 6c7b4652d4..0443b73953 100644
--- a/actionpack/lib/action_controller/metal/renderers.rb
+++ b/actionpack/lib/action_controller/metal/renderers.rb
@@ -42,8 +42,8 @@ module ActionController
nil
end
- # Hash of available renderers, mapping a renderer name to its proc.
- # Default keys are <tt>:json</tt>, <tt>:js</tt>, <tt>:xml</tt>.
+ # A Set containing renderer names that correspond to available renderer procs.
+ # Default values are <tt>:json</tt>, <tt>:js</tt>, <tt>:xml</tt>.
RENDERERS = Set.new
# Adds a new renderer to call within controller actions.
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index 8578b43d78..50ca64d536 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -1,3 +1,18 @@
+* Change `favicon_link_tag` default mimetype from `image/vnd.microsoft.icon` to
+ `image/x-icon`.
+
+ Before:
+
+ #=> favicon_link_tag 'myicon.ico'
+ <link href="/assets/myicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" />
+
+ After:
+
+ #=> favicon_link_tag 'myicon.ico'
+ <link href="/assets/myicon.ico" rel="shortcut icon" type="image/x-icon" />
+
+ *Geoffroy Lorieux*
+
* Remove wrapping div with inline styles for hidden form fields.
We are dropping HTML 4.01 and XHTML strict compliance since input tags directly
diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb
index aa49f1edc1..413532826b 100644
--- a/actionview/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb
@@ -149,12 +149,12 @@ module ActionView
# ==== Options
#
# * <tt>:rel</tt> - Specify the relation of this link, defaults to 'shortcut icon'
- # * <tt>:type</tt> - Override the auto-generated mime type, defaults to 'image/vnd.microsoft.icon'
+ # * <tt>:type</tt> - Override the auto-generated mime type, defaults to 'image/x-icon'
#
# ==== Examples
#
# favicon_link_tag 'myicon.ico'
- # # => <link href="/assets/myicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" />
+ # # => <link href="/assets/myicon.ico" rel="shortcut icon" type="image/x-icon" />
#
# Mobile Safari looks for a different <link> tag, pointing to an image that
# will be used if you add the page to the home screen of an iPod Touch, iPhone, or iPad.
@@ -165,7 +165,7 @@ module ActionView
def favicon_link_tag(source='favicon.ico', options={})
tag('link', {
:rel => 'shortcut icon',
- :type => 'image/vnd.microsoft.icon',
+ :type => 'image/x-icon',
:href => path_to_image(source)
}.merge!(options.symbolize_keys))
end
diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb
index 1ff090f244..22bfd87d85 100644
--- a/actionview/lib/action_view/helpers/form_helper.rb
+++ b/actionview/lib/action_view/helpers/form_helper.rb
@@ -746,6 +746,7 @@ module ActionView
# label(:post, :terms) do
# 'Accept <a href="/terms">Terms</a>.'.html_safe
# end
+ # # => <label for="post_terms">Accept <a href="/terms">Terms</a>.</label>
def label(object_name, method, content_or_options = nil, options = nil, &block)
Tags::Label.new(object_name, method, self, content_or_options, options).render(&block)
end
diff --git a/actionview/test/template/asset_tag_helper_test.rb b/actionview/test/template/asset_tag_helper_test.rb
index 3ca71d3376..651978ed80 100644
--- a/actionview/test/template/asset_tag_helper_test.rb
+++ b/actionview/test/template/asset_tag_helper_test.rb
@@ -195,9 +195,9 @@ class AssetTagHelperTest < ActionView::TestCase
}
FaviconLinkToTag = {
- %(favicon_link_tag) => %(<link href="/images/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" />),
- %(favicon_link_tag 'favicon.ico') => %(<link href="/images/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" />),
- %(favicon_link_tag 'favicon.ico', :rel => 'foo') => %(<link href="/images/favicon.ico" rel="foo" type="image/vnd.microsoft.icon" />),
+ %(favicon_link_tag) => %(<link href="/images/favicon.ico" rel="shortcut icon" type="image/x-icon" />),
+ %(favicon_link_tag 'favicon.ico') => %(<link href="/images/favicon.ico" rel="shortcut icon" type="image/x-icon" />),
+ %(favicon_link_tag 'favicon.ico', :rel => 'foo') => %(<link href="/images/favicon.ico" rel="foo" type="image/x-icon" />),
%(favicon_link_tag 'favicon.ico', :rel => 'foo', :type => 'bar') => %(<link href="/images/favicon.ico" rel="foo" type="bar" />),
%(favicon_link_tag 'mb-icon.png', :rel => 'apple-touch-icon', :type => 'image/png') => %(<link href="/images/mb-icon.png" rel="apple-touch-icon" type="image/png" />)
}
diff --git a/actionview/test/template/form_helper_test.rb b/actionview/test/template/form_helper_test.rb
index 90fe9fdc6a..0ad0ae6b4b 100644
--- a/actionview/test/template/form_helper_test.rb
+++ b/actionview/test/template/form_helper_test.rb
@@ -265,6 +265,13 @@ class FormHelperTest < ActionView::TestCase
)
end
+ def test_label_with_block_and_html
+ assert_dom_equal(
+ '<label for="post_terms">Accept <a href="/terms">Terms</a>.</label>',
+ label(:post, :terms) { 'Accept <a href="/terms">Terms</a>.'.html_safe }
+ )
+ end
+
def test_label_with_block_and_options
assert_dom_equal(
'<label for="my_for">The title, please:</label>',
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 74f5666de0..cd47d0bbb4 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,18 @@
+* Fix name collision with `Array#select!` with `Relation#select!`.
+
+ Fixes #14752.
+
+ *Earl St Sauver*
+
+* Fixed unexpected behavior for `has_many :through` associations going through a scoped `has_many`.
+
+ If a `has_many` association is adjusted using a scope, and another `has_many :through`
+ uses this association, then the scope adjustment is unexpectedly neglected.
+
+ Fixes #14537.
+
+ *Jan Habermann*
+
* `@destroyed` should always be set to `false` when an object is duped.
*Kuldeep Aggarwal*
diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb
index 69b65982b3..e2b1cef463 100644
--- a/activerecord/lib/active_record/associations/preloader/association.rb
+++ b/activerecord/lib/active_record/associations/preloader/association.rb
@@ -116,7 +116,7 @@ module ActiveRecord
scope.where_values = Array(values[:where]) + Array(preload_values[:where])
scope.references_values = Array(values[:references]) + Array(preload_values[:references])
- scope.select! preload_values[:select] || values[:select] || table[Arel.star]
+ scope._select! preload_values[:select] || values[:select] || table[Arel.star]
scope.includes! preload_values[:includes] || values[:includes]
if preload_values.key? :order
diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb
index ba7d2a3782..f8a85b8a6f 100644
--- a/activerecord/lib/active_record/associations/through_association.rb
+++ b/activerecord/lib/active_record/associations/through_association.rb
@@ -14,9 +14,11 @@ module ActiveRecord
def target_scope
scope = super
chain.drop(1).each do |reflection|
+ relation = reflection.klass.all
+ relation.merge!(reflection.scope) if reflection.scope
+
scope.merge!(
- reflection.klass.all.
- except(:select, :create_with, :includes, :preload, :joins, :eager_load)
+ relation.except(:select, :create_with, :includes, :preload, :joins, :eager_load)
)
end
scope
diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb
index 21beed332f..9c666dcd3b 100644
--- a/activerecord/lib/active_record/relation/delegation.rb
+++ b/activerecord/lib/active_record/relation/delegation.rb
@@ -40,7 +40,7 @@ module ActiveRecord
BLACKLISTED_ARRAY_METHODS = [
:compact!, :flatten!, :reject!, :reverse!, :rotate!, :map!,
:shuffle!, :slice!, :sort!, :sort_by!, :delete_if,
- :keep_if, :pop, :shift, :delete_at, :compact
+ :keep_if, :pop, :shift, :delete_at, :compact, :select!
].to_set # :nodoc:
delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to_ary, to: :to_a
diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb
index be44fccad5..fcb28a18f6 100644
--- a/activerecord/lib/active_record/relation/merger.rb
+++ b/activerecord/lib/active_record/relation/merger.rb
@@ -30,6 +30,8 @@ module ActiveRecord
else
other.joins!(*v)
end
+ elsif k == :select
+ other._select!(v)
else
other.send("#{k}!", v)
end
@@ -62,7 +64,13 @@ module ActiveRecord
# expensive), most of the time the value is going to be `nil` or `.blank?`, the only catch is that
# `false.blank?` returns `true`, so there needs to be an extra check so that explicit `false` values
# don't fall through the cracks.
- relation.send("#{name}!", *value) unless value.nil? || (value.blank? && false != value)
+ unless value.nil? || (value.blank? && false != value)
+ if name == :select
+ relation._select!(*value)
+ else
+ relation.send("#{name}!", *value)
+ end
+ end
end
merge_multi_values
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 4287304945..5340fcb0b6 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -235,11 +235,11 @@ module ActiveRecord
to_a.select { |*block_args| yield(*block_args) }
else
raise ArgumentError, 'Call this with at least one field' if fields.empty?
- spawn.select!(*fields)
+ spawn._select!(*fields)
end
end
- def select!(*fields) # :nodoc:
+ def _select!(*fields) # :nodoc:
fields.flatten!
fields.map! do |field|
klass.attribute_alias?(field) ? klass.attribute_alias(field) : field
diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb
index 6675e19dd9..e68ad74f70 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -1082,7 +1082,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert_equal ["parrot", "bulbul"], owner.toys.map { |r| r.pet.name }
end
- test "has many through associations on new records use null relations" do
+ def test_has_many_through_associations_on_new_records_use_null_relations
person = Person.new
assert_no_queries do
@@ -1094,7 +1094,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
end
- test "has many through with default scope on the target" do
+ def test_has_many_through_with_default_scope_on_the_target
person = people(:michael)
assert_equal [posts(:thinking)], person.first_posts
@@ -1102,11 +1102,11 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert_equal [posts(:thinking)], person.reload.first_posts
end
- test "has many through with includes in through association scope" do
+ def test_has_many_through_with_includes_in_through_association_scope
assert_not_empty posts(:welcome).author_address_extra_with_address
end
- test "insert records via has_many_through association with scope" do
+ def test_insert_records_via_has_many_through_association_with_scope
club = Club.create!
member = Member.create!
Membership.create!(club: club, member: member)
@@ -1117,4 +1117,16 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
club.reload
assert_equal [member], club.favourites
end
+
+ def test_has_many_through_unscope_default_scope
+ post = Post.create!(:title => 'Beaches', :body => "I like beaches!")
+ Reader.create! :person => people(:david), :post => post
+ LazyReader.create! :person => people(:susan), :post => post
+
+ assert_equal 2, post.people.to_a.size
+ assert_equal 1, post.lazy_people.to_a.size
+
+ assert_equal 2, post.lazy_readers_unscope_skimmers.to_a.size
+ assert_equal 2, post.lazy_people_unscope_skimmers.to_a.size
+ end
end
diff --git a/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb b/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb
deleted file mode 100644
index deed226eab..0000000000
--- a/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb
+++ /dev/null
@@ -1,56 +0,0 @@
-require "cases/helper"
-
-module ActiveRecord
- module ConnectionAdapters
- class ConnectionPool
- def insert_connection_for_test!(c)
- synchronize do
- @connections << c
- @available.add c
- end
- end
- end
-
- class AbstractAdapterTest < ActiveRecord::TestCase
- attr_reader :adapter
-
- def setup
- @adapter = AbstractAdapter.new nil, nil
- end
-
- def test_in_use?
- assert_not adapter.in_use?, 'adapter is not in use'
- assert adapter.lease, 'lease adapter'
- assert adapter.in_use?, 'adapter is in use'
- end
-
- def test_lease_twice
- assert adapter.lease, 'should lease adapter'
- assert_not adapter.lease, 'should not lease adapter'
- end
-
- def test_expire_mutates_in_use
- assert adapter.lease, 'lease adapter'
- assert adapter.in_use?, 'adapter is in use'
- adapter.expire
- assert_not adapter.in_use?, 'adapter is in use'
- end
-
- def test_close
- pool = ConnectionPool.new(ConnectionSpecification.new({}, nil))
- pool.insert_connection_for_test! adapter
- adapter.pool = pool
-
- # Make sure the pool marks the connection in use
- assert_equal adapter, pool.connection
- assert adapter.in_use?
-
- # Close should put the adapter back in the pool
- adapter.close
- assert_not adapter.in_use?
-
- assert_equal adapter, pool.connection
- end
- end
- end
-end
diff --git a/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb b/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb
new file mode 100644
index 0000000000..662e19f35e
--- /dev/null
+++ b/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb
@@ -0,0 +1,54 @@
+require "cases/helper"
+
+module ActiveRecord
+ module ConnectionAdapters
+ class AdapterLeasingTest < ActiveRecord::TestCase
+ class Pool < ConnectionPool
+ def insert_connection_for_test!(c)
+ synchronize do
+ @connections << c
+ @available.add c
+ end
+ end
+ end
+
+ def setup
+ @adapter = AbstractAdapter.new nil, nil
+ end
+
+ def test_in_use?
+ assert_not @adapter.in_use?, 'adapter is not in use'
+ assert @adapter.lease, 'lease adapter'
+ assert @adapter.in_use?, 'adapter is in use'
+ end
+
+ def test_lease_twice
+ assert @adapter.lease, 'should lease adapter'
+ assert_not @adapter.lease, 'should not lease adapter'
+ end
+
+ def test_expire_mutates_in_use
+ assert @adapter.lease, 'lease adapter'
+ assert @adapter.in_use?, 'adapter is in use'
+ @adapter.expire
+ assert_not @adapter.in_use?, 'adapter is in use'
+ end
+
+ def test_close
+ pool = Pool.new(ConnectionSpecification.new({}, nil))
+ pool.insert_connection_for_test! @adapter
+ @adapter.pool = pool
+
+ # Make sure the pool marks the connection in use
+ assert_equal @adapter, pool.connection
+ assert @adapter.in_use?
+
+ # Close should put the adapter back in the pool
+ @adapter.close
+ assert_not @adapter.in_use?
+
+ assert_equal @adapter, pool.connection
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
index f2d18e812d..3e33b30144 100644
--- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb
+++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
@@ -2,199 +2,6 @@ require "cases/helper"
module ActiveRecord
module ConnectionAdapters
-
- class MergeAndResolveDefaultUrlConfigTest < ActiveRecord::TestCase
-
- def klass
- ActiveRecord::ConnectionHandling::MergeAndResolveDefaultUrlConfig
- end
-
- def setup
- @previous_database_url = ENV.delete("DATABASE_URL")
- end
-
- teardown do
- ENV["DATABASE_URL"] = @previous_database_url
- end
-
- def resolve(spec, config)
- ConnectionSpecification::Resolver.new(klass.new(config).resolve).resolve(spec)
- end
-
- def spec(spec, config)
- ConnectionSpecification::Resolver.new(klass.new(config).resolve).spec(spec)
- end
-
- def test_resolver_with_database_uri_and_current_env_symbol_key
- ENV['DATABASE_URL'] = "postgres://localhost/foo"
- config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } }
- actual = resolve(:default_env, config)
- expected = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost" }
- assert_equal expected, actual
- end
-
- def test_resolver_with_database_uri_and_and_current_env_string_key
- ENV['DATABASE_URL'] = "postgres://localhost/foo"
- config = { "default_env" => { "adapter" => "not_postgres", "database" => "not_foo" } }
- actual = assert_deprecated { resolve("default_env", config) }
- expected = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost" }
- assert_equal expected, actual
- end
-
- def test_resolver_with_database_uri_and_known_key
- ENV['DATABASE_URL'] = "postgres://localhost/foo"
- config = { "production" => { "adapter" => "not_postgres", "database" => "not_foo", "host" => "localhost" } }
- actual = resolve(:production, config)
- expected = { "adapter"=>"not_postgres", "database"=>"not_foo", "host"=>"localhost" }
- assert_equal expected, actual
- end
-
- def test_resolver_with_database_uri_and_unknown_symbol_key
- ENV['DATABASE_URL'] = "postgres://localhost/foo"
- config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } }
- assert_raises AdapterNotSpecified do
- resolve(:production, config)
- end
- end
-
- def test_resolver_with_database_uri_and_unknown_string_key
- ENV['DATABASE_URL'] = "postgres://localhost/foo"
- config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } }
- assert_deprecated do
- assert_raises AdapterNotSpecified do
- spec("production", config)
- end
- end
- end
-
- def test_resolver_with_database_uri_and_supplied_url
- ENV['DATABASE_URL'] = "not-postgres://not-localhost/not_foo"
- config = { "production" => { "adapter" => "also_not_postgres", "database" => "also_not_foo" } }
- actual = resolve("postgres://localhost/foo", config)
- expected = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost" }
- assert_equal expected, actual
- end
-
- def test_jdbc_url
- config = { "production" => { "url" => "jdbc:postgres://localhost/foo" } }
- actual = klass.new(config).resolve
- assert_equal config, actual
- end
-
- def test_environment_does_not_exist_in_config_url_does_exist
- ENV['DATABASE_URL'] = "postgres://localhost/foo"
- config = { "not_default_env" => { "adapter" => "not_postgres", "database" => "not_foo" } }
- actual = klass.new(config).resolve
- expect_prod = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost" }
- assert_equal expect_prod, actual["default_env"]
- end
-
- def test_url_with_hyphenated_scheme
- ENV['DATABASE_URL'] = "ibm-db://localhost/foo"
- config = { "default_env" => { "adapter" => "not_postgres", "database" => "not_foo", "host" => "localhost" } }
- actual = resolve(:default_env, config)
- expected = { "adapter"=>"ibm_db", "database"=>"foo", "host"=>"localhost" }
- assert_equal expected, actual
- end
-
- def test_string_connection
- config = { "default_env" => "postgres://localhost/foo" }
- actual = klass.new(config).resolve
- expected = { "default_env" =>
- { "adapter" => "postgresql",
- "database" => "foo",
- "host" => "localhost"
- }
- }
- assert_equal expected, actual
- end
-
- def test_url_sub_key
- config = { "default_env" => { "url" => "postgres://localhost/foo" } }
- actual = klass.new(config).resolve
- expected = { "default_env" =>
- { "adapter" => "postgresql",
- "database" => "foo",
- "host" => "localhost"
- }
- }
- assert_equal expected, actual
- end
-
- def test_hash
- config = { "production" => { "adapter" => "postgres", "database" => "foo" } }
- actual = klass.new(config).resolve
- assert_equal config, actual
- end
-
- def test_blank
- config = {}
- actual = klass.new(config).resolve
- assert_equal config, actual
- end
-
- def test_blank_with_database_url
- ENV['DATABASE_URL'] = "postgres://localhost/foo"
-
- config = {}
- actual = klass.new(config).resolve
- expected = { "adapter" => "postgresql",
- "database" => "foo",
- "host" => "localhost" }
- assert_equal expected, actual["default_env"]
- assert_equal nil, actual["production"]
- assert_equal nil, actual["development"]
- assert_equal nil, actual["test"]
- assert_equal nil, actual[:production]
- assert_equal nil, actual[:development]
- assert_equal nil, actual[:test]
- end
-
- def test_url_sub_key_with_database_url
- ENV['DATABASE_URL'] = "NOT-POSTGRES://localhost/NOT_FOO"
-
- config = { "default_env" => { "url" => "postgres://localhost/foo" } }
- actual = klass.new(config).resolve
- expected = { "default_env" =>
- { "adapter" => "postgresql",
- "database" => "foo",
- "host" => "localhost"
- }
- }
- assert_equal expected, actual
- end
-
- def test_merge_no_conflicts_with_database_url
- ENV['DATABASE_URL'] = "postgres://localhost/foo"
-
- config = {"default_env" => { "pool" => "5" } }
- actual = klass.new(config).resolve
- expected = { "default_env" =>
- { "adapter" => "postgresql",
- "database" => "foo",
- "host" => "localhost",
- "pool" => "5"
- }
- }
- assert_equal expected, actual
- end
-
- def test_merge_conflicts_with_database_url
- ENV['DATABASE_URL'] = "postgres://localhost/foo"
-
- config = {"default_env" => { "adapter" => "NOT-POSTGRES", "database" => "NOT-FOO", "pool" => "5" } }
- actual = klass.new(config).resolve
- expected = { "default_env" =>
- { "adapter" => "postgresql",
- "database" => "foo",
- "host" => "localhost",
- "pool" => "5"
- }
- }
- assert_equal expected, actual
- end
- end
-
class ConnectionHandlerTest < ActiveRecord::TestCase
def setup
@klass = Class.new(Base) { def self.name; 'klass'; end }
diff --git a/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb b/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb
new file mode 100644
index 0000000000..da852aaa02
--- /dev/null
+++ b/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb
@@ -0,0 +1,192 @@
+require "cases/helper"
+
+module ActiveRecord
+ module ConnectionAdapters
+ class MergeAndResolveDefaultUrlConfigTest < ActiveRecord::TestCase
+ def setup
+ @previous_database_url = ENV.delete("DATABASE_URL")
+ end
+
+ teardown do
+ ENV["DATABASE_URL"] = @previous_database_url
+ end
+
+ def resolve_config(config)
+ ActiveRecord::ConnectionHandling::MergeAndResolveDefaultUrlConfig.new(config).resolve
+ end
+
+ def resolve_spec(spec, config)
+ ConnectionSpecification::Resolver.new(resolve_config(config)).resolve(spec)
+ end
+
+ def test_resolver_with_database_uri_and_current_env_symbol_key
+ ENV['DATABASE_URL'] = "postgres://localhost/foo"
+ config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } }
+ actual = resolve_spec(:default_env, config)
+ expected = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost" }
+ assert_equal expected, actual
+ end
+
+ def test_resolver_with_database_uri_and_and_current_env_string_key
+ ENV['DATABASE_URL'] = "postgres://localhost/foo"
+ config = { "default_env" => { "adapter" => "not_postgres", "database" => "not_foo" } }
+ actual = assert_deprecated { resolve_spec("default_env", config) }
+ expected = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost" }
+ assert_equal expected, actual
+ end
+
+ def test_resolver_with_database_uri_and_known_key
+ ENV['DATABASE_URL'] = "postgres://localhost/foo"
+ config = { "production" => { "adapter" => "not_postgres", "database" => "not_foo", "host" => "localhost" } }
+ actual = resolve_spec(:production, config)
+ expected = { "adapter"=>"not_postgres", "database"=>"not_foo", "host"=>"localhost" }
+ assert_equal expected, actual
+ end
+
+ def test_resolver_with_database_uri_and_unknown_symbol_key
+ ENV['DATABASE_URL'] = "postgres://localhost/foo"
+ config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } }
+ assert_raises AdapterNotSpecified do
+ resolve_spec(:production, config)
+ end
+ end
+
+ def test_resolver_with_database_uri_and_unknown_string_key
+ ENV['DATABASE_URL'] = "postgres://localhost/foo"
+ config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } }
+ assert_deprecated do
+ assert_raises AdapterNotSpecified do
+ resolve_spec("production", config)
+ end
+ end
+ end
+
+ def test_resolver_with_database_uri_and_supplied_url
+ ENV['DATABASE_URL'] = "not-postgres://not-localhost/not_foo"
+ config = { "production" => { "adapter" => "also_not_postgres", "database" => "also_not_foo" } }
+ actual = resolve_spec("postgres://localhost/foo", config)
+ expected = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost" }
+ assert_equal expected, actual
+ end
+
+ def test_jdbc_url
+ config = { "production" => { "url" => "jdbc:postgres://localhost/foo" } }
+ actual = resolve_config(config)
+ assert_equal config, actual
+ end
+
+ def test_environment_does_not_exist_in_config_url_does_exist
+ ENV['DATABASE_URL'] = "postgres://localhost/foo"
+ config = { "not_default_env" => { "adapter" => "not_postgres", "database" => "not_foo" } }
+ actual = resolve_config(config)
+ expect_prod = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost" }
+ assert_equal expect_prod, actual["default_env"]
+ end
+
+ def test_url_with_hyphenated_scheme
+ ENV['DATABASE_URL'] = "ibm-db://localhost/foo"
+ config = { "default_env" => { "adapter" => "not_postgres", "database" => "not_foo", "host" => "localhost" } }
+ actual = resolve_spec(:default_env, config)
+ expected = { "adapter"=>"ibm_db", "database"=>"foo", "host"=>"localhost" }
+ assert_equal expected, actual
+ end
+
+ def test_string_connection
+ config = { "default_env" => "postgres://localhost/foo" }
+ actual = resolve_config(config)
+ expected = { "default_env" =>
+ { "adapter" => "postgresql",
+ "database" => "foo",
+ "host" => "localhost"
+ }
+ }
+ assert_equal expected, actual
+ end
+
+ def test_url_sub_key
+ config = { "default_env" => { "url" => "postgres://localhost/foo" } }
+ actual = resolve_config(config)
+ expected = { "default_env" =>
+ { "adapter" => "postgresql",
+ "database" => "foo",
+ "host" => "localhost"
+ }
+ }
+ assert_equal expected, actual
+ end
+
+ def test_hash
+ config = { "production" => { "adapter" => "postgres", "database" => "foo" } }
+ actual = resolve_config(config)
+ assert_equal config, actual
+ end
+
+ def test_blank
+ config = {}
+ actual = resolve_config(config)
+ assert_equal config, actual
+ end
+
+ def test_blank_with_database_url
+ ENV['DATABASE_URL'] = "postgres://localhost/foo"
+
+ config = {}
+ actual = resolve_config(config)
+ expected = { "adapter" => "postgresql",
+ "database" => "foo",
+ "host" => "localhost" }
+ assert_equal expected, actual["default_env"]
+ assert_equal nil, actual["production"]
+ assert_equal nil, actual["development"]
+ assert_equal nil, actual["test"]
+ assert_equal nil, actual[:production]
+ assert_equal nil, actual[:development]
+ assert_equal nil, actual[:test]
+ end
+
+ def test_url_sub_key_with_database_url
+ ENV['DATABASE_URL'] = "NOT-POSTGRES://localhost/NOT_FOO"
+
+ config = { "default_env" => { "url" => "postgres://localhost/foo" } }
+ actual = resolve_config(config)
+ expected = { "default_env" =>
+ { "adapter" => "postgresql",
+ "database" => "foo",
+ "host" => "localhost"
+ }
+ }
+ assert_equal expected, actual
+ end
+
+ def test_merge_no_conflicts_with_database_url
+ ENV['DATABASE_URL'] = "postgres://localhost/foo"
+
+ config = {"default_env" => { "pool" => "5" } }
+ actual = resolve_config(config)
+ expected = { "default_env" =>
+ { "adapter" => "postgresql",
+ "database" => "foo",
+ "host" => "localhost",
+ "pool" => "5"
+ }
+ }
+ assert_equal expected, actual
+ end
+
+ def test_merge_conflicts_with_database_url
+ ENV['DATABASE_URL'] = "postgres://localhost/foo"
+
+ config = {"default_env" => { "adapter" => "NOT-POSTGRES", "database" => "NOT-FOO", "pool" => "5" } }
+ actual = resolve_config(config)
+ expected = { "default_env" =>
+ { "adapter" => "postgresql",
+ "database" => "foo",
+ "host" => "localhost",
+ "pool" => "5"
+ }
+ }
+ assert_equal expected, actual
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/relation/mutation_test.rb b/activerecord/test/cases/relation/mutation_test.rb
index c81a3002d6..1da5c36e1c 100644
--- a/activerecord/test/cases/relation/mutation_test.rb
+++ b/activerecord/test/cases/relation/mutation_test.rb
@@ -24,13 +24,18 @@ module ActiveRecord
@relation ||= Relation.new FakeKlass.new('posts'), Post.arel_table
end
- (Relation::MULTI_VALUE_METHODS - [:references, :extending, :order, :unscope]).each do |method|
+ (Relation::MULTI_VALUE_METHODS - [:references, :extending, :order, :unscope, :select]).each do |method|
test "##{method}!" do
assert relation.public_send("#{method}!", :foo).equal?(relation)
assert_equal [:foo], relation.public_send("#{method}_values")
end
end
+ test "#_select!" do
+ assert relation.public_send("_select!", :foo).equal?(relation)
+ assert_equal [:foo], relation.public_send("select_values")
+ end
+
test '#order!' do
assert relation.order!('name ASC').equal?(relation)
assert_equal ['name ASC'], relation.order_values
diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb
index 099e039255..629dfe4765 100644
--- a/activerecord/test/models/post.rb
+++ b/activerecord/test/models/post.rb
@@ -145,6 +145,10 @@ class Post < ActiveRecord::Base
has_many :lazy_readers
has_many :lazy_readers_skimmers_or_not, -> { where(skimmer: [ true, false ]) }, :class_name => 'LazyReader'
+ has_many :lazy_people, :through => :lazy_readers, :source => :person
+ has_many :lazy_readers_unscope_skimmers, -> { skimmers_or_not }, :class_name => 'LazyReader'
+ has_many :lazy_people_unscope_skimmers, :through => :lazy_readers_unscope_skimmers, :source => :person
+
def self.top(limit)
ranked_by_comments.limit_by(limit)
end
diff --git a/activerecord/test/models/reader.rb b/activerecord/test/models/reader.rb
index 3a6b7fad34..91afc1898c 100644
--- a/activerecord/test/models/reader.rb
+++ b/activerecord/test/models/reader.rb
@@ -16,6 +16,8 @@ class LazyReader < ActiveRecord::Base
self.table_name = "readers"
default_scope -> { where(skimmer: true) }
+ scope :skimmers_or_not, -> { unscope(:where => :skimmer) }
+
belongs_to :post
belongs_to :person
end
diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb
index 18273573e0..a943752f17 100644
--- a/activesupport/lib/active_support/core_ext/string/inflections.rb
+++ b/activesupport/lib/active_support/core_ext/string/inflections.rb
@@ -31,7 +31,7 @@ class String
def pluralize(count = nil, locale = :en)
locale = count if count.is_a?(Symbol)
if count == 1
- self
+ self.dup
else
ActiveSupport::Inflector.pluralize(self, locale)
end
diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
index 4fbbdd46d4..69f77453e7 100644
--- a/activesupport/lib/active_support/inflector/methods.rb
+++ b/activesupport/lib/active_support/inflector/methods.rb
@@ -155,7 +155,7 @@ module ActiveSupport
#
# Singular names are not handled correctly:
#
- # 'business'.classify # => "Busines"
+ # 'calculus'.classify # => "Calculu"
def classify(table_name)
# strip out any leading schema name
camelize(singularize(table_name.to_s.sub(/.*\./, '')))
diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb
index f8d4d0f0dc..95df173880 100644
--- a/activesupport/test/core_ext/string_ext_test.rb
+++ b/activesupport/test/core_ext/string_ext_test.rb
@@ -58,6 +58,11 @@ class StringInflectionsTest < ActiveSupport::TestCase
assert_equal("blargles", "blargle".pluralize(2))
end
+ test 'pluralize with count = 1 still returns new string' do
+ name = "Kuldeep"
+ assert_not_same name.pluralize(1), name
+ end
+
def test_singularize
SingularToPlural.each do |singular, plural|
assert_equal(singular, plural.singularize)
diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md
index e7880ca642..8966eef76a 100644
--- a/guides/source/getting_started.md
+++ b/guides/source/getting_started.md
@@ -1028,17 +1028,21 @@ something went wrong. To do that, you'll modify
```html+erb
<%= form_for :article, url: articles_path do |f| %>
+
<% if @article.errors.any? %>
- <div id="error_explanation">
- <h2><%= pluralize(@article.errors.count, "error") %> prohibited
- this article from being saved:</h2>
- <ul>
- <% @article.errors.full_messages.each do |msg| %>
- <li><%= msg %></li>
- <% end %>
- </ul>
- </div>
+ <div id="error_explanation">
+ <h2>
+ <%= pluralize(@article.errors.count, "error") %> prohibited
+ this article from being saved:
+ </h2>
+ <ul>
+ <% @article.errors.full_messages.each do |msg| %>
+ <li><%= msg %></li>
+ <% end %>
+ </ul>
+ </div>
<% end %>
+
<p>
<%= f.label :title %><br>
<%= f.text_field :title %>
@@ -1052,6 +1056,7 @@ something went wrong. To do that, you'll modify
<p>
<%= f.submit %>
</p>
+
<% end %>
<%= link_to 'Back', articles_path %>
@@ -1100,17 +1105,21 @@ it look as follows:
<h1>Editing article</h1>
<%= form_for :article, url: article_path(@article), method: :patch do |f| %>
+
<% if @article.errors.any? %>
- <div id="error_explanation">
- <h2><%= pluralize(@article.errors.count, "error") %> prohibited
- this article from being saved:</h2>
- <ul>
- <% @article.errors.full_messages.each do |msg| %>
- <li><%= msg %></li>
- <% end %>
- </ul>
- </div>
+ <div id="error_explanation">
+ <h2>
+ <%= pluralize(@article.errors.count, "error") %> prohibited
+ this article from being saved:
+ </h2>
+ <ul>
+ <% @article.errors.full_messages.each do |msg| %>
+ <li><%= msg %></li>
+ <% end %>
+ </ul>
+ </div>
<% end %>
+
<p>
<%= f.label :title %><br>
<%= f.text_field :title %>
@@ -1124,6 +1133,7 @@ it look as follows:
<p>
<%= f.submit %>
</p>
+
<% end %>
<%= link_to 'Back', articles_path %>
@@ -1187,14 +1197,14 @@ it appear next to the "Show" link:
<th colspan="2"></th>
</tr>
-<% @articles.each do |article| %>
- <tr>
- <td><%= article.title %></td>
- <td><%= article.text %></td>
- <td><%= link_to 'Show', article_path(article) %></td>
- <td><%= link_to 'Edit', edit_article_path(article) %></td>
- </tr>
-<% end %>
+ <% @articles.each do |article| %>
+ <tr>
+ <td><%= article.title %></td>
+ <td><%= article.text %></td>
+ <td><%= link_to 'Show', article_path(article) %></td>
+ <td><%= link_to 'Edit', edit_article_path(article) %></td>
+ </tr>
+ <% end %>
</table>
```
@@ -1228,17 +1238,21 @@ content:
```html+erb
<%= form_for @article do |f| %>
+
<% if @article.errors.any? %>
- <div id="error_explanation">
- <h2><%= pluralize(@article.errors.count, "error") %> prohibited
- this article from being saved:</h2>
- <ul>
- <% @article.errors.full_messages.each do |msg| %>
- <li><%= msg %></li>
- <% end %>
- </ul>
- </div>
+ <div id="error_explanation">
+ <h2>
+ <%= pluralize(@article.errors.count, "error") %> prohibited
+ this article from being saved:
+ </h2>
+ <ul>
+ <% @article.errors.full_messages.each do |msg| %>
+ <li><%= msg %></li>
+ <% end %>
+ </ul>
+ </div>
<% end %>
+
<p>
<%= f.label :title %><br>
<%= f.text_field :title %>
@@ -1252,6 +1266,7 @@ content:
<p>
<%= f.submit %>
</p>
+
<% end %>
```
@@ -1333,16 +1348,17 @@ together.
<th colspan="3"></th>
</tr>
-<% @articles.each do |article| %>
- <tr>
- <td><%= article.title %></td>
- <td><%= article.text %></td>
- <td><%= link_to 'Show', article_path(article) %></td>
- <td><%= link_to 'Edit', edit_article_path(article) %></td>
- <td><%= link_to 'Destroy', article_path(article),
- method: :delete, data: { confirm: 'Are you sure?' } %></td>
- </tr>
-<% end %>
+ <% @articles.each do |article| %>
+ <tr>
+ <td><%= article.title %></td>
+ <td><%= article.text %></td>
+ <td><%= link_to 'Show', article_path(article) %></td>
+ <td><%= link_to 'Edit', edit_article_path(article) %></td>
+ <td><%= link_to 'Destroy', article_path(article),
+ method: :delete,
+ data: { confirm: 'Are you sure?' } %></td>
+ </tr>
+ <% end %>
</table>
```
@@ -1552,8 +1568,8 @@ So first, we'll wire up the Article show template
</p>
<% end %>
-<%= link_to 'Back', articles_path %>
-| <%= link_to 'Edit', edit_article_path(@article) %>
+<%= link_to 'Back', articles_path %> |
+<%= link_to 'Edit', edit_article_path(@article) %>
```
This adds a form on the `Article` show page that creates a new comment by
@@ -1823,7 +1839,7 @@ database and send us back to the show action for the article.
### Deleting Associated Objects
-If you delete an article then its associated comments will also need to be
+If you delete an article, its associated comments will also need to be
deleted. Otherwise they would simply occupy space in the database. Rails allows
you to use the `dependent` option of an association to achieve this. Modify the
Article model, `app/models/article.rb`, as follows:
@@ -1841,21 +1857,21 @@ Security
### Basic Authentication
-If you were to publish your blog online, anybody would be able to add, edit and
+If you were to publish your blog online, anyone would be able to add, edit and
delete articles or delete comments.
Rails provides a very simple HTTP authentication system that will work nicely in
this situation.
-In the `ArticlesController` we need to have a way to block access to the various
-actions if the person is not authenticated, here we can use the Rails
-`http_basic_authenticate_with` method, allowing access to the requested
+In the `ArticlesController` we need to have a way to block access to the
+various actions if the person is not authenticated. Here we can use the Rails
+`http_basic_authenticate_with` method, which allows access to the requested
action if that method allows it.
To use the authentication system, we specify it at the top of our
-`ArticlesController`, in this case, we want the user to be authenticated on
-every action, except for `index` and `show`, so we write that in
-`app/controllers/articles_controller.rb`:
+`ArticlesController` in `app/controllers/articles_controller.rb`. In our case,
+we want the user to be authenticated on every action except `index` and `show`,
+so we write that:
```ruby
class ArticlesController < ApplicationController