aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--actionmailer/lib/action_mailer/log_subscriber.rb1
-rw-r--r--actionmailer/lib/action_mailer/mail_helper.rb4
-rw-r--r--actionpack/lib/abstract_controller/base.rb2
-rw-r--r--actionpack/lib/action_controller/test_case.rb1
-rw-r--r--actionpack/lib/action_dispatch/http/url.rb12
-rw-r--r--actionpack/test/controller/new_base/bare_metal_test.rb2
-rw-r--r--actionpack/test/controller/render_test.rb6
-rw-r--r--actionpack/test/controller/send_file_test.rb1
-rw-r--r--actionview/CHANGELOG.md9
-rw-r--r--actionview/lib/action_view/helpers/asset_url_helper.rb20
-rw-r--r--actionview/lib/action_view/helpers/debug_helper.rb12
-rw-r--r--actionview/lib/action_view/helpers/form_tag_helper.rb10
-rw-r--r--actionview/lib/action_view/helpers/text_helper.rb31
-rw-r--r--actionview/test/template/debug_helper_test.rb2
-rw-r--r--actionview/test/template/text_helper_test.rb17
-rw-r--r--activemodel/test/cases/serializers/json_serialization_test.rb1
-rw-r--r--activerecord/CHANGELOG.md22
-rw-r--r--activerecord/lib/active_record.rb1
-rw-r--r--activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb13
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb4
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb26
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb5
-rw-r--r--activerecord/lib/active_record/associations/preloader/association.rb4
-rw-r--r--activerecord/lib/active_record/attribute_assignment.rb4
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb30
-rw-r--r--activerecord/lib/active_record/attribute_methods/before_type_cast.rb4
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb6
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb4
-rw-r--r--activerecord/lib/active_record/attribute_set.rb53
-rw-r--r--activerecord/lib/active_record/attributes.rb11
-rw-r--r--activerecord/lib/active_record/autosave_association.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb4
-rw-r--r--activerecord/lib/active_record/core.rb14
-rw-r--r--activerecord/lib/active_record/model_schema.rb14
-rw-r--r--activerecord/lib/active_record/persistence.rb10
-rw-r--r--activerecord/lib/active_record/querying.rb9
-rw-r--r--activerecord/lib/active_record/reflection.rb4
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb20
-rw-r--r--activerecord/lib/active_record/store.rb2
-rw-r--r--activerecord/lib/active_record/type/numeric.rb18
-rw-r--r--activerecord/lib/active_record/type/value.rb2
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb2
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb11
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb30
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb27
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb31
-rw-r--r--activerecord/test/cases/attribute_set_test.rb65
-rw-r--r--activerecord/test/cases/calculations_test.rb5
-rw-r--r--activerecord/test/cases/persistence_test.rb7
-rw-r--r--activerecord/test/cases/quoting_test.rb25
-rw-r--r--activerecord/test/cases/reflection_test.rb22
-rw-r--r--activerecord/test/models/club.rb7
-rw-r--r--activerecord/test/models/publisher/article.rb1
-rw-r--r--activerecord/test/schema/schema.rb5
-rw-r--r--activesupport/CHANGELOG.md3
-rw-r--r--activesupport/lib/active_support/dependencies.rb8
-rw-r--r--activesupport/test/caching_test.rb27
-rw-r--r--activesupport/test/dependencies_test.rb5
-rw-r--r--activesupport/test/json/decoding_test.rb30
-rw-r--r--activesupport/test/json/encoding_test.rb33
-rw-r--r--guides/assets/javascripts/guides.js6
-rw-r--r--guides/source/action_controller_overview.md55
-rw-r--r--guides/source/active_record_migrations.md3
-rw-r--r--guides/source/asset_pipeline.md2
-rw-r--r--guides/source/command_line.md25
-rw-r--r--guides/source/engines.md13
-rw-r--r--guides/source/getting_started.md2
-rw-r--r--guides/source/layouts_and_rendering.md2
-rw-r--r--guides/source/upgrading_ruby_on_rails.md6
73 files changed, 625 insertions, 277 deletions
diff --git a/actionmailer/lib/action_mailer/log_subscriber.rb b/actionmailer/lib/action_mailer/log_subscriber.rb
index eb6fb11d81..e4aaab34b1 100644
--- a/actionmailer/lib/action_mailer/log_subscriber.rb
+++ b/actionmailer/lib/action_mailer/log_subscriber.rb
@@ -21,6 +21,7 @@ module ActionMailer
# An email was generated.
def process(event)
+ return unless logger.debug?
mailer = event.payload[:mailer]
action = event.payload[:action]
debug("\n#{mailer}##{action}: processed outbound mail in #{event.duration.round(1)}ms")
diff --git a/actionmailer/lib/action_mailer/mail_helper.rb b/actionmailer/lib/action_mailer/mail_helper.rb
index 54ad9f066f..483277af04 100644
--- a/actionmailer/lib/action_mailer/mail_helper.rb
+++ b/actionmailer/lib/action_mailer/mail_helper.rb
@@ -11,8 +11,8 @@ module ActionMailer
}.join("\n\n")
# Make list points stand on their own line
- formatted.gsub!(/[ ]*([*]+) ([^*]*)/) { |s| " #{$1} #{$2.strip}\n" }
- formatted.gsub!(/[ ]*([#]+) ([^#]*)/) { |s| " #{$1} #{$2.strip}\n" }
+ formatted.gsub!(/[ ]*([*]+) ([^*]*)/) { " #{$1} #{$2.strip}\n" }
+ formatted.gsub!(/[ ]*([#]+) ([^#]*)/) { " #{$1} #{$2.strip}\n" }
formatted
end
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb
index acdfb33efa..15faabf977 100644
--- a/actionpack/lib/abstract_controller/base.rb
+++ b/actionpack/lib/abstract_controller/base.rb
@@ -255,7 +255,7 @@ module AbstractController
# Checks if the action name is valid and returns false otherwise.
def _valid_action_name?(action_name)
- action_name !~ Regexp.new(File::SEPARATOR)
+ !action_name.to_s.include? File::SEPARATOR
end
end
end
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index 849286a4a9..b117170514 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -593,7 +593,6 @@ module ActionController
unless @controller.respond_to?(:recycle!)
@controller.extend(Testing::Functional)
- @controller.class.class_eval { include Testing }
end
@request.recycle!
diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb
index a5858758c6..3997c6ee98 100644
--- a/actionpack/lib/action_dispatch/http/url.rb
+++ b/actionpack/lib/action_dispatch/http/url.rb
@@ -36,13 +36,13 @@ module ActionDispatch
path = options[:script_name].to_s.chomp("/")
path << options[:path].to_s
- add_trailing_slash(path) if options[:trailing_slash]
+ path = add_trailing_slash(path) if options[:trailing_slash]
- result = path
-
- unless options[:only_path]
- result.prepend build_host_url(options)
- end
+ result = if options[:only_path]
+ path
+ else
+ build_host_url(options).concat path
+ end
if options.key? :params
params = options[:params].is_a?(Hash) ?
diff --git a/actionpack/test/controller/new_base/bare_metal_test.rb b/actionpack/test/controller/new_base/bare_metal_test.rb
index 2ddc07ef72..246ba099af 100644
--- a/actionpack/test/controller/new_base/bare_metal_test.rb
+++ b/actionpack/test/controller/new_base/bare_metal_test.rb
@@ -2,6 +2,8 @@ require "abstract_unit"
module BareMetalTest
class BareController < ActionController::Metal
+ include ActionController::RackDelegation
+
def index
self.response_body = "Hello world"
end
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index 26806fb03f..9926130c02 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -242,6 +242,8 @@ class MetalTestController < ActionController::Metal
include AbstractController::Rendering
include ActionView::Rendering
include ActionController::Rendering
+ include ActionController::RackDelegation
+
def accessing_logger_in_template
render :inline => "<%= logger.class %>"
@@ -387,10 +389,6 @@ end
class EtagRenderTest < ActionController::TestCase
tests TestControllerWithExtraEtags
- def setup
- super
- end
-
def test_multiple_etags
@request.if_none_match = etag(["123", 'ab', :cde, [:f]])
get :fresh
diff --git a/actionpack/test/controller/send_file_test.rb b/actionpack/test/controller/send_file_test.rb
index 73ed33ce2d..c002cf4d8f 100644
--- a/actionpack/test/controller/send_file_test.rb
+++ b/actionpack/test/controller/send_file_test.rb
@@ -9,6 +9,7 @@ end
class SendFileController < ActionController::Base
include TestFileUtils
+ include ActionController::Testing
layout "layouts/standard" # to make sure layouts don't interfere
attr_writer :options
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index d825d3b627..03ac155848 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -1,3 +1,12 @@
+* The `highlight` helper now accepts a block to be used instead of the `highlighter`
+ option.
+
+ *Lucas Mazza*
+
+* The `except` and `highlight` helpers now accept regular expressions.
+
+ *Jan Szumiec*
+
* Flatten the array parameter in `safe_join`, so it behaves consistently with
`Array#join`.
diff --git a/actionview/lib/action_view/helpers/asset_url_helper.rb b/actionview/lib/action_view/helpers/asset_url_helper.rb
index 006b15be91..4a682ce4e2 100644
--- a/actionview/lib/action_view/helpers/asset_url_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_url_helper.rb
@@ -7,10 +7,10 @@ module ActionView
# urls.
#
# image_path("rails.png")
- # # => "/images/rails.png"
+ # # => "/assets/rails.png"
#
# image_url("rails.png")
- # # => "http://www.example.com/images/rails.png"
+ # # => "http://www.example.com/assets/rails.png"
#
# === Using asset hosts
#
@@ -155,7 +155,7 @@ module ActionView
#
# All other options provided are forwarded to +asset_path+ call.
#
- # asset_url "application.js" # => http://example.com/application.js
+ # asset_url "application.js" # => http://example.com/assets/application.js
# asset_url "application.js", host: "http://cdn.example.com" # => http://cdn.example.com/assets/application.js
#
def asset_url(source, options = {})
@@ -236,8 +236,8 @@ module ActionView
# Full paths from the document root will be passed through.
# Used internally by +javascript_include_tag+ to build the script path.
#
- # javascript_path "xmlhr" # => /javascripts/xmlhr.js
- # javascript_path "dir/xmlhr.js" # => /javascripts/dir/xmlhr.js
+ # javascript_path "xmlhr" # => /assets/xmlhr.js
+ # javascript_path "dir/xmlhr.js" # => /assets/dir/xmlhr.js
# javascript_path "/dir/xmlhr" # => /dir/xmlhr.js
# javascript_path "http://www.example.com/js/xmlhr" # => http://www.example.com/js/xmlhr
# javascript_path "http://www.example.com/js/xmlhr.js" # => http://www.example.com/js/xmlhr.js
@@ -258,8 +258,8 @@ module ActionView
# Full paths from the document root will be passed through.
# Used internally by +stylesheet_link_tag+ to build the stylesheet path.
#
- # stylesheet_path "style" # => /stylesheets/style.css
- # stylesheet_path "dir/style.css" # => /stylesheets/dir/style.css
+ # stylesheet_path "style" # => /assets/style.css
+ # stylesheet_path "dir/style.css" # => /assets/dir/style.css
# stylesheet_path "/dir/style.css" # => /dir/style.css
# stylesheet_path "http://www.example.com/css/style" # => http://www.example.com/css/style
# stylesheet_path "http://www.example.com/css/style.css" # => http://www.example.com/css/style.css
@@ -279,9 +279,9 @@ module ActionView
# Full paths from the document root will be passed through.
# Used internally by +image_tag+ to build the image path:
#
- # image_path("edit") # => "/images/edit"
- # image_path("edit.png") # => "/images/edit.png"
- # image_path("icons/edit.png") # => "/images/icons/edit.png"
+ # image_path("edit") # => "/assets/edit"
+ # image_path("edit.png") # => "/assets/edit.png"
+ # image_path("icons/edit.png") # => "/assets/icons/edit.png"
# image_path("/icons/edit.png") # => "/icons/edit.png"
# image_path("http://www.example.com/img/edit.png") # => "http://www.example.com/img/edit.png"
#
diff --git a/actionview/lib/action_view/helpers/debug_helper.rb b/actionview/lib/action_view/helpers/debug_helper.rb
index 16cddec339..ba47eee9ba 100644
--- a/actionview/lib/action_view/helpers/debug_helper.rb
+++ b/actionview/lib/action_view/helpers/debug_helper.rb
@@ -16,15 +16,15 @@ module ActionView
# # =>
# <pre class='debug_dump'>--- !ruby/object:User
# attributes:
- # &nbsp; updated_at:
- # &nbsp; username: testing
- # &nbsp; age: 42
- # &nbsp; password: xyz
- # &nbsp; created_at:
+ # updated_at:
+ # username: testing
+ # age: 42
+ # password: xyz
+ # created_at:
# </pre>
def debug(object)
Marshal::dump(object)
- object = ERB::Util.html_escape(object.to_yaml).gsub(" ", "&nbsp; ").html_safe
+ object = ERB::Util.html_escape(object.to_yaml)
content_tag(:pre, object, :class => "debug_dump")
rescue Exception # errors from Marshal or YAML
# Object couldn't be dumped, perhaps because of singleton methods -- this is the fallback
diff --git a/actionview/lib/action_view/helpers/form_tag_helper.rb b/actionview/lib/action_view/helpers/form_tag_helper.rb
index 9c0c43d096..b18f578183 100644
--- a/actionview/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/form_tag_helper.rb
@@ -508,19 +508,19 @@ module ActionView
#
# ==== Examples
# image_submit_tag("login.png")
- # # => <input alt="Login" src="/images/login.png" type="image" />
+ # # => <input alt="Login" src="/assets/login.png" type="image" />
#
# image_submit_tag("purchase.png", disabled: true)
- # # => <input alt="Purchase" disabled="disabled" src="/images/purchase.png" type="image" />
+ # # => <input alt="Purchase" disabled="disabled" src="/assets/purchase.png" type="image" />
#
# image_submit_tag("search.png", class: 'search_button', alt: 'Find')
- # # => <input alt="Find" class="search_button" src="/images/search.png" type="image" />
+ # # => <input alt="Find" class="search_button" src="/assets/search.png" type="image" />
#
# image_submit_tag("agree.png", disabled: true, class: "agree_disagree_button")
- # # => <input alt="Agree" class="agree_disagree_button" disabled="disabled" src="/images/agree.png" type="image" />
+ # # => <input alt="Agree" class="agree_disagree_button" disabled="disabled" src="/assets/agree.png" type="image" />
#
# image_submit_tag("save.png", data: { confirm: "Are you sure?" })
- # # => <input alt="Save" src="/images/save.png" data-confirm="Are you sure?" type="image" />
+ # # => <input alt="Save" src="/assets/save.png" data-confirm="Are you sure?" type="image" />
def image_submit_tag(source, options = {})
options = options.stringify_keys
tag :input, { "alt" => image_alt(source), "type" => "image", "src" => path_to_image(source) }.update(options)
diff --git a/actionview/lib/action_view/helpers/text_helper.rb b/actionview/lib/action_view/helpers/text_helper.rb
index 7cfbca5b6f..cf5c1b0e81 100644
--- a/actionview/lib/action_view/helpers/text_helper.rb
+++ b/actionview/lib/action_view/helpers/text_helper.rb
@@ -103,11 +103,14 @@ module ActionView
# Highlights one or more +phrases+ everywhere in +text+ by inserting it into
# a <tt>:highlighter</tt> string. The highlighter can be specialized by passing <tt>:highlighter</tt>
# as a single-quoted string with <tt>\1</tt> where the phrase is to be inserted (defaults to
- # '<mark>\1</mark>')
+ # '<mark>\1</mark>') or passing a block that receives each matched term.
#
# highlight('You searched for: rails', 'rails')
# # => You searched for: <mark>rails</mark>
#
+ # highlight('You searched for: rails', /for|rails/)
+ # # => You searched <mark>for</mark>: <mark>rails</mark>
+ #
# highlight('You searched for: ruby, rails, dhh', 'actionpack')
# # => You searched for: ruby, rails, dhh
#
@@ -116,15 +119,25 @@ module ActionView
#
# highlight('You searched for: rails', 'rails', highlighter: '<a href="search?q=\1">\1</a>')
# # => You searched for: <a href="search?q=rails">rails</a>
+ #
+ # highlight('You searched for: rails', 'rails') { |match| link_to(search_path(q: match, match)) }
+ # # => You searched for: <a href="search?q=rails">rails</a>
def highlight(text, phrases, options = {})
text = sanitize(text) if options.fetch(:sanitize, true)
if text.blank? || phrases.blank?
text
else
- highlighter = options.fetch(:highlighter, '<mark>\1</mark>')
- match = Array(phrases).map { |p| Regexp.escape(p) }.join('|')
- text.gsub(/(#{match})(?![^<]*?>)/i, highlighter)
+ match = Array(phrases).map do |p|
+ Regexp === p ? p.to_s : Regexp.escape(p)
+ end.join('|')
+
+ if block_given?
+ text.gsub(/(#{match})(?![^<]*?>)/i) { |found| yield found }
+ else
+ highlighter = options.fetch(:highlighter, '<mark>\1</mark>')
+ text.gsub(/(#{match})(?![^<]*?>)/i, highlighter)
+ end
end.html_safe
end
@@ -155,9 +168,13 @@ module ActionView
def excerpt(text, phrase, options = {})
return unless text && phrase
- separator = options[:separator] || ''
- phrase = Regexp.escape(phrase)
- regex = /#{phrase}/i
+ separator = options.fetch(:separator, nil) || ""
+ case phrase
+ when Regexp
+ regex = phrase
+ else
+ regex = /#{Regexp.escape(phrase)}/i
+ end
return unless matches = text.match(regex)
phrase = matches[0]
diff --git a/actionview/test/template/debug_helper_test.rb b/actionview/test/template/debug_helper_test.rb
index 42d06bd9ff..5609694cd5 100644
--- a/actionview/test/template/debug_helper_test.rb
+++ b/actionview/test/template/debug_helper_test.rb
@@ -3,6 +3,6 @@ require 'active_record_unit'
class DebugHelperTest < ActionView::TestCase
def test_debug
company = Company.new(name: "firebase")
- assert_match "&nbsp; name: firebase", debug(company)
+ assert_match "name: firebase", debug(company)
end
end
diff --git a/actionview/test/template/text_helper_test.rb b/actionview/test/template/text_helper_test.rb
index a514bba83d..858232a2b9 100644
--- a/actionview/test/template/text_helper_test.rb
+++ b/actionview/test/template/text_helper_test.rb
@@ -222,6 +222,11 @@ class TextHelperTest < ActionView::TestCase
)
end
+ def test_highlight_accepts_regexp
+ assert_equal("This day was challenging for judge <mark>Allen</mark> and his colleagues.",
+ highlight("This day was challenging for judge Allen and his colleagues.", /\ballen\b/i))
+ end
+
def test_highlight_with_multiple_phrases_in_one_pass
assert_equal %(<em>wow</em> <em>em</em>), highlight('wow em', %w(wow em), :highlighter => '<em>\1</em>')
end
@@ -260,6 +265,13 @@ class TextHelperTest < ActionView::TestCase
assert_equal options, passed_options
end
+ def test_highlight_with_block
+ assert_equal(
+ "<b>one</b> <b>two</b> <b>three</b>",
+ highlight("one two three", ["one", "two", "three"]) { |word| "<b>#{word}</b>" }
+ )
+ end
+
def test_excerpt
assert_equal("...is a beautiful morn...", excerpt("This is a beautiful morning", "beautiful", :radius => 5))
assert_equal("This is a...", excerpt("This is a beautiful morning", "this", :radius => 5))
@@ -267,6 +279,11 @@ class TextHelperTest < ActionView::TestCase
assert_nil excerpt("This is a beautiful morning", "day")
end
+ def test_excerpt_with_regex
+ assert_equal("...udge Allen and...", excerpt("This day was challenging for judge Allen and his colleagues.", /\ballen\b/i, :radius => 5))
+ assert_equal("...judge Allen and...", excerpt("This day was challenging for judge Allen and his colleagues.", /\ballen\b/i, :radius => 1, :separator => ' '))
+ end
+
def test_excerpt_should_not_be_html_safe
assert !excerpt('This is a beautiful! morning', 'beautiful', :radius => 5).html_safe?
end
diff --git a/activemodel/test/cases/serializers/json_serialization_test.rb b/activemodel/test/cases/serializers/json_serialization_test.rb
index 60414a6570..734656b749 100644
--- a/activemodel/test/cases/serializers/json_serialization_test.rb
+++ b/activemodel/test/cases/serializers/json_serialization_test.rb
@@ -1,6 +1,5 @@
require 'cases/helper'
require 'models/contact'
-require 'models/automobile'
require 'active_support/core_ext/object/instance_variables'
class Contact
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 823597fc92..33dec99ec7 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,22 @@
+* `reload` no longer merges with the existing attributes.
+ The attribute hash is fully replaced. The record is put into the same state
+ as it would be with `Model.find(model.id)`.
+
+ *Sean Griffin*
+
+* The object returned from `select_all` must respond to `column_types`.
+ If this is not the case a `NoMethodError` is raised.
+
+ *Sean Griffin*
+
+* `has_many :through` associations will no longer save the through record
+ twice when added in an `after_create` callback defined before the
+ associations.
+
+ Fixes #3798.
+
+ *Sean Griffin*
+
* Detect in-place modifications of PG array types
*Sean Griffin*
@@ -98,7 +117,8 @@
*Lauro Caetano*, *Carlos Antonio da Silva*
-* Return a null column from `column_for_attribute` when no column exists.
+* Deprecate returning `nil` from `column_for_attribute` when no column exists.
+ It will return a null object in Rails 5.0
*Sean Griffin*
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index 53fa132219..ab85414277 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -32,6 +32,7 @@ module ActiveRecord
extend ActiveSupport::Autoload
autoload :Attribute
+ autoload :AttributeSet
autoload :Base
autoload :Callbacks
autoload :Core
diff --git a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
index 0ad5206980..34a555dfd4 100644
--- a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
+++ b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
@@ -15,7 +15,10 @@ module ActiveRecord::Associations::Builder
end
private
- def klass; @rhs_class_name.constantize; end
+
+ def klass
+ @lhs_class.send(:compute_type, @rhs_class_name)
+ end
end
def self.build(lhs_class, name, options)
@@ -23,13 +26,7 @@ module ActiveRecord::Associations::Builder
KnownTable.new options[:join_table].to_s
else
class_name = options.fetch(:class_name) {
- model_name = name.to_s.camelize.singularize
-
- if lhs_class.parent_name
- model_name.prepend("#{lhs_class.parent_name}::")
- end
-
- model_name
+ name.to_s.camelize.singularize
}
KnownClass.new lhs_class, class_name
end
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 306588ac66..065a2cff01 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -55,9 +55,9 @@ module ActiveRecord
# Implements the ids writer method, e.g. foo.item_ids= for Foo.has_many :items
def ids_writer(ids)
- pk_column = reflection.primary_key_column
+ pk_type = reflection.primary_key_type
ids = Array(ids).reject { |id| id.blank? }
- ids.map! { |i| pk_column.type_cast_from_user(i) }
+ ids.map! { |i| pk_type.type_cast_from_user(i) }
replace(klass.find(ids).index_by { |r| r.id }.values_at(*ids))
end
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index 2727e23870..477888228d 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -83,6 +83,13 @@ module ActiveRecord
if has_cached_counter?(reflection)
counter = cached_counter_attribute_name(reflection)
owner.class.update_counters(owner.id, counter => difference)
+ update_counter_in_memory(difference, reflection)
+ end
+ end
+
+ def update_counter_in_memory(difference, reflection = reflection())
+ if has_cached_counter?(reflection)
+ counter = cached_counter_attribute_name(reflection)
owner[counter] += difference
owner.changed_attributes.delete(counter) # eww
end
@@ -137,6 +144,25 @@ module ActiveRecord
false
end
end
+
+ def concat_records(records, *)
+ update_counter_if_success(super, records.length)
+ end
+
+ def _create_record(attributes, *)
+ if attributes.is_a?(Array)
+ super
+ else
+ update_counter_if_success(super, 1)
+ end
+ end
+
+ def update_counter_if_success(saved_successfully, difference)
+ if saved_successfully
+ update_counter_in_memory(difference)
+ end
+ saved_successfully
+ end
end
end
end
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
index 175019a72b..af38f2f6dd 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -63,7 +63,6 @@ module ActiveRecord
end
save_through_record(record)
- update_counter(1)
record
end
@@ -93,7 +92,9 @@ module ActiveRecord
end
def through_scope_attributes
- scope.where_values_hash(through_association.reflection.name.to_s).except!(through_association.reflection.foreign_key)
+ scope.where_values_hash(through_association.reflection.name.to_s).
+ except!(through_association.reflection.foreign_key,
+ through_association.reflection.klass.inheritance_column)
end
def save_through_record(record)
diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb
index 33c8619359..441768f302 100644
--- a/activerecord/lib/active_record/associations/preloader/association.rb
+++ b/activerecord/lib/active_record/associations/preloader/association.rb
@@ -104,11 +104,11 @@ module ActiveRecord
end
def association_key_type
- @klass.column_for_attribute(association_key_name).type
+ @klass.type_for_attribute(association_key_name.to_s).type
end
def owner_key_type
- @model.column_for_attribute(owner_key_name).type
+ @model.type_for_attribute(owner_key_name.to_s).type
end
def load_slices(slices)
diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb
index 40e2918777..833ffa158b 100644
--- a/activerecord/lib/active_record/attribute_assignment.rb
+++ b/activerecord/lib/active_record/attribute_assignment.rb
@@ -134,7 +134,7 @@ module ActiveRecord
elsif klass == Date
read_date
else
- read_other(klass)
+ read_other
end
end
@@ -181,7 +181,7 @@ module ActiveRecord
end
end
- def read_other(klass)
+ def read_other
max_position = extract_max_param
positions = (1..max_position)
validate_required_parameters!(positions)
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index b4d75d6556..51f6a009db 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -186,8 +186,7 @@ module ActiveRecord
end
# Returns the column object for the named attribute.
- # Returns a +ActiveRecord::ConnectionAdapters::NullColumn+ if the
- # named attribute does not exist.
+ # Returns nil if the named attribute does not exist.
#
# class Person < ActiveRecord::Base
# end
@@ -197,12 +196,17 @@ module ActiveRecord
# # => #<ActiveRecord::ConnectionAdapters::SQLite3Column:0x007ff4ab083980 @name="name", @sql_type="varchar(255)", @null=true, ...>
#
# person.column_for_attribute(:nothing)
- # # => #<ActiveRecord::ConnectionAdapters::NullColumn:0xXXX @name=nil, @sql_type=nil, @cast_type=#<Type::Value>, ...>
+ # # => nil
def column_for_attribute(name)
- name = name.to_s
- columns_hash.fetch(name) do
- ConnectionAdapters::NullColumn.new(name)
+ column = columns_hash[name.to_s]
+ if column.nil?
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc)
+ `column_for_attribute` will return a null object for non-existent columns
+ in Rails 5.0. If you would like to continue to receive `nil`, you should
+ instead call `model.class.columns_hash[name]`
+ MESSAGE
end
+ column
end
end
@@ -230,7 +234,7 @@ module ActiveRecord
# For queries selecting a subset of columns, return false for unselected columns.
# We check defined?(@attributes) not to issue warnings if called on objects that
# have been allocated but not yet initialized.
- if defined?(@attributes) && @attributes.any? && self.class.column_names.include?(name)
+ if defined?(@attributes) && self.class.column_names.include?(name)
return has_attribute?(name)
end
@@ -247,7 +251,7 @@ module ActiveRecord
# person.has_attribute?('age') # => true
# person.has_attribute?(:nothing) # => false
def has_attribute?(attr_name)
- @attributes.has_key?(attr_name.to_s)
+ @attributes.include?(attr_name.to_s)
end
# Returns an array of names for the attributes available on this object.
@@ -271,9 +275,7 @@ module ActiveRecord
# person.attributes
# # => {"id"=>3, "created_at"=>Sun, 21 Oct 2012 04:53:04, "updated_at"=>Sun, 21 Oct 2012 04:53:04, "name"=>"Francesco", "age"=>22}
def attributes
- attribute_names.each_with_object({}) { |name, attrs|
- attrs[name] = read_attribute(name)
- }
+ @attributes.to_hash
end
# Returns an <tt>#inspect</tt>-like string for the value of the
@@ -367,12 +369,6 @@ module ActiveRecord
protected
- def clone_attributes # :nodoc:
- @attributes.each_with_object({}) do |(name, attr), h|
- h[name] = attr.dup
- end
- end
-
def clone_attribute_value(reader_method, attribute_name) # :nodoc:
value = send(reader_method, attribute_name)
value.duplicable? ? value.clone : value
diff --git a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
index d057f0941a..9ee9a7815f 100644
--- a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
+++ b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
@@ -43,9 +43,7 @@ module ActiveRecord
# task.read_attribute_before_type_cast('completed_on') # => "2012-10-21"
# task.read_attribute_before_type_cast(:completed_on) # => "2012-10-21"
def read_attribute_before_type_cast(attr_name)
- if attr = @attributes[attr_name.to_s]
- attr.value_before_type_cast
- end
+ @attributes[attr_name.to_s].value_before_type_cast
end
# Returns a hash of attributes before typecasting and deserialization.
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index ca71834641..e1a86fd3aa 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -129,7 +129,7 @@ module ActiveRecord
end
def _field_changed?(attr, old_value)
- attribute_named(attr).changed_from?(old_value)
+ @attributes[attr].changed_from?(old_value)
end
def changed_in_place
@@ -140,7 +140,7 @@ module ActiveRecord
def changed_in_place?(attr_name)
old_value = original_raw_attribute(attr_name)
- attribute_named(attr_name).changed_in_place_from?(old_value)
+ @attributes[attr_name].changed_in_place_from?(old_value)
end
def original_raw_attribute(attr_name)
@@ -154,7 +154,7 @@ module ActiveRecord
end
def store_original_raw_attribute(attr_name)
- original_raw_attributes[attr_name] = attribute_named(attr_name).value_for_database
+ original_raw_attributes[attr_name] = @attributes[attr_name].value_for_database
end
def store_original_raw_attributes
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index 525c46970a..8c1cc128f7 100644
--- a/activerecord/lib/active_record/attribute_methods/read.rb
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -99,10 +99,6 @@ module ActiveRecord
def attribute(attribute_name)
read_attribute(attribute_name)
end
-
- def attribute_named(attribute_name)
- @attributes.fetch(attribute_name, Attribute::Null)
- end
end
end
end
diff --git a/activerecord/lib/active_record/attribute_set.rb b/activerecord/lib/active_record/attribute_set.rb
new file mode 100644
index 0000000000..5e5f7ca1b5
--- /dev/null
+++ b/activerecord/lib/active_record/attribute_set.rb
@@ -0,0 +1,53 @@
+module ActiveRecord
+ class AttributeSet # :nodoc:
+ delegate :[], :[]=, :fetch, :include?, :keys, :each_with_object, to: :attributes
+
+ def initialize(attributes)
+ @attributes = attributes
+ end
+
+ def to_hash
+ attributes.each_with_object({}) { |(k, v), h| h[k] = v.value }
+ end
+ alias_method :to_h, :to_hash
+
+ def freeze
+ @attributes.freeze
+ super
+ end
+
+ def initialize_dup(_)
+ @attributes = attributes.dup
+ attributes.each do |key, attr|
+ attributes[key] = attr.dup
+ end
+
+ super
+ end
+
+ def initialize_clone(_)
+ @attributes = attributes.clone
+ super
+ end
+
+ class Builder
+ def initialize(types)
+ @types = types
+ end
+
+ def build_from_database(values, additional_types = {})
+ attributes = Hash.new(Attribute::Null)
+ values.each_with_object(attributes) do |(name, value), hash|
+ type = additional_types.fetch(name, @types[name])
+ hash[name] = Attribute.from_database(value, type)
+ end
+ AttributeSet.new(attributes)
+ end
+ end
+
+ protected
+
+ attr_reader :attributes
+
+ end
+end
diff --git a/activerecord/lib/active_record/attributes.rb b/activerecord/lib/active_record/attributes.rb
index 9c80121d70..9a329ac564 100644
--- a/activerecord/lib/active_record/attributes.rb
+++ b/activerecord/lib/active_record/attributes.rb
@@ -27,7 +27,7 @@ module ActiveRecord
#
# ==== Examples
#
- # The type detected by Active Record can be overriden.
+ # The type detected by Active Record can be overridden.
#
# # db/schema.rb
# create_table :store_listings, force: true do |t|
@@ -109,13 +109,14 @@ module ActiveRecord
end
def clear_caches_calculated_from_columns
- @columns = nil
- @columns_hash = nil
- @column_types = nil
+ @attributes_builder = nil
@column_defaults = nil
- @raw_column_defaults = nil
@column_names = nil
+ @column_types = nil
+ @columns = nil
+ @columns_hash = nil
@content_columns = nil
+ @raw_column_defaults = nil
end
end
end
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index b3c3e26c9f..dd92e29199 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -184,9 +184,7 @@ module ActiveRecord
before_save :before_save_collection_association
define_non_cyclic_method(save_method) { save_collection_association(reflection) }
- # 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
+ after_save 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
@@ -364,6 +362,7 @@ module ActiveRecord
raise ActiveRecord::Rollback unless saved
end
+ @new_record_before_save = false
end
# reconstruct the scope now that we know the owner's id
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index db80c0faee..cb75070e3a 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -364,7 +364,7 @@ module ActiveRecord
conn.expire
end
- release conn, owner
+ release owner
@available.add conn
end
@@ -377,7 +377,7 @@ module ActiveRecord
@connections.delete conn
@available.delete conn
- release conn, conn.owner
+ release conn.owner
@available.add checkout_new_connection if @available.any_waiting?
end
@@ -425,7 +425,7 @@ module ActiveRecord
end
end
- def release(conn, owner)
+ def release(owner)
thread_id = owner.object_id
@reserved_connections.delete thread_id
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index 04ae67234f..ff92375820 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -14,8 +14,8 @@ module ActiveRecord
# value. Is this really the only case? Are we missing tests for other types?
# We should have a real column object passed (or nil) here, and check for that
# instead
- if column.respond_to?(:type_cast_for_database)
- value = column.type_cast_for_database(value)
+ if column.respond_to?(:cast_type)
+ value = column.cast_type.type_cast_for_database(value)
end
_quote(value)
@@ -34,8 +34,8 @@ module ActiveRecord
# value. Is this really the only case? Are we missing tests for other types?
# We should have a real column object passed (or nil) here, and check for that
# instead
- if column.respond_to?(:type_cast_for_database)
- value = column.type_cast_for_database(value)
+ if column.respond_to?(:cast_type)
+ value = column.cast_type.type_cast_for_database(value)
end
_type_cast(value)
diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index 8be4678ace..1f1e2c46f4 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -57,12 +57,6 @@ module ActiveRecord
end
end
end
-
- class NullColumn < Column
- def initialize(name)
- super name, nil, Type::Value.new
- end
- end
end
# :startdoc:
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
index 17fabe5af6..3fea8f490d 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
@@ -179,8 +179,8 @@ module ActiveRecord
end
def array_column(column)
- if column.array && !column.respond_to?(:type_cast_for_database)
- OID::Array.new(AdapterProxyType.new(column, self))
+ if column.array && !column.respond_to?(:cast_type)
+ Column.new('', nil, OID::Array.new(AdapterProxyType.new(column, self)))
else
column
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 71b05cdbae..6d5151dfe4 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -306,10 +306,6 @@ module ActiveRecord
self.client_min_messages = old
end
- def supports_insert_with_returning?
- true
- end
-
def supports_ddl_transactions?
true
end
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index d39e5fddfe..10b9e27b7d 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -16,7 +16,6 @@ module ActiveRecord
mattr_accessor :logger, instance_writer: false
##
- # :singleton-method:
# Contains the database configuration - as is typically stored in config/database.yml -
# as a Hash.
#
@@ -251,12 +250,10 @@ module ActiveRecord
def initialize(attributes = nil, options = {})
defaults = {}
self.class.raw_column_defaults.each do |k, v|
- default = v.duplicable? ? v.dup : v
- defaults[k] = Attribute.from_database(default, type_for_attribute(k))
+ defaults[k] = v.duplicable? ? v.dup : v
end
- @attributes = defaults
- @column_types = self.class.column_types
+ @attributes = self.class.attributes_builder.build_from_database(defaults)
init_internals
initialize_internals_callback
@@ -282,7 +279,6 @@ module ActiveRecord
# post.title # => 'hello world'
def init_with(coder)
@attributes = coder['attributes']
- @column_types = self.class.column_types
init_internals
@@ -325,7 +321,7 @@ module ActiveRecord
##
def initialize_dup(other) # :nodoc:
pk = self.class.primary_key
- @attributes = other.clone_attributes
+ @attributes = @attributes.dup
@attributes[pk] = Attribute.from_database(nil, type_for_attribute(pk))
run_callbacks(:initialize) unless _initialize_callbacks.empty?
@@ -525,7 +521,9 @@ module ActiveRecord
def init_internals
pk = self.class.primary_key
- @attributes[pk] ||= Attribute.from_database(nil, type_for_attribute(pk))
+ unless @attributes.include?(pk)
+ @attributes[pk] = Attribute.from_database(nil, type_for_attribute(pk))
+ end
@aggregation_cache = {}
@association_cache = {}
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index fad7eae461..099042cab2 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -219,19 +219,25 @@ module ActiveRecord
connection.schema_cache.table_exists?(table_name)
end
+ def attributes_builder # :nodoc:
+ @attributes_builder ||= AttributeSet::Builder.new(column_types)
+ end
+
def column_types # :nodoc:
- @column_types ||= Hash[columns.map { |column| [column.name, column.cast_type] }]
+ @column_types ||= Hash.new(Type::Value.new).tap do |column_types|
+ columns.each { |column| column_types[column.name] = column.cast_type }
+ end
end
def type_for_attribute(attr_name) # :nodoc:
- column_types.fetch(attr_name) { Type::Value.new }
+ column_types[attr_name]
end
# Returns a hash where the keys are column names and the values are
# default values when instantiating the AR object for this table.
def column_defaults
- @column_defaults ||= Hash[columns_hash.map { |name, column|
- [name, column.type_cast_from_database(column.default)]
+ @column_defaults ||= Hash[raw_column_defaults.map { |name, default|
+ [name, type_for_attribute(name).type_cast_from_database(default)]
}]
end
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 5c744762d9..ee634d7bb3 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -48,11 +48,7 @@ module ActiveRecord
# how this "single-table" inheritance mapping is implemented.
def instantiate(attributes, column_types = {})
klass = discriminate_class_for_record(attributes)
-
- attributes = attributes.each_with_object({}) do |(name, value), h|
- type = column_types.fetch(name) { klass.type_for_attribute(name) }
- h[name] = Attribute.from_database(value, type)
- end
+ attributes = klass.attributes_builder.build_from_database(attributes, column_types)
klass.allocate.init_with('attributes' => attributes, 'new_record' => false)
end
@@ -399,9 +395,7 @@ module ActiveRecord
self.class.unscoped { self.class.find(id) }
end
- @attributes.update(fresh_object.instance_variable_get('@attributes'))
-
- @column_types = self.class.column_types
+ @attributes = fresh_object.instance_variable_get('@attributes')
self
end
diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb
index 39817703cd..a9ddd9141f 100644
--- a/activerecord/lib/active_record/querying.rb
+++ b/activerecord/lib/active_record/querying.rb
@@ -37,14 +37,7 @@ module ActiveRecord
# Post.find_by_sql ["SELECT body FROM comments WHERE author = :user_id OR approved_by = :user_id", { :user_id => user_id }]
def find_by_sql(sql, binds = [])
result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds)
- column_types = {}
-
- if result_set.respond_to? :column_types
- column_types = result_set.column_types.except(*columns_hash.keys)
- else
- ActiveSupport::Deprecation.warn "the object returned from `select_all` must respond to `column_types`"
- end
-
+ column_types = result_set.column_types.except(*columns_hash.keys)
result_set.map { |record| instantiate(record, column_types) }
end
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 51c96373ee..28c39bdd5c 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -290,8 +290,8 @@ module ActiveRecord
@foreign_key ||= options[:foreign_key] || derive_foreign_key
end
- def primary_key_column
- klass.columns_hash[klass.primary_key]
+ def primary_key_type
+ klass.type_for_attribute(klass.primary_key)
end
def association_foreign_key
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 316a59e0bb..c80ffbae92 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -273,7 +273,7 @@ module ActiveRecord
row = result.first
value = row && row.values.first
column = result.column_types.fetch(column_alias) do
- column_for(column_name)
+ type_for(column_name)
end
type_cast_calculated_value(value, column, operation)
@@ -336,14 +336,14 @@ module ActiveRecord
Hash[calculated_data.map do |row|
key = group_columns.map { |aliaz, col_name|
column = calculated_data.column_types.fetch(aliaz) do
- column_for(col_name)
+ type_for(col_name)
end
type_cast_calculated_value(row[aliaz], column)
}
key = key.first if key.size == 1
key = key_records[key] if associated
- column_type = calculated_data.column_types.fetch(aggregate_alias) { column_for(column_name) }
+ column_type = calculated_data.column_types.fetch(aggregate_alias) { type_for(column_name) }
[key, type_cast_calculated_value(row[aggregate_alias], column_type, operation)]
end]
end
@@ -370,24 +370,20 @@ module ActiveRecord
@klass.connection.table_alias_for(table_name)
end
- def column_for(field)
+ def type_for(field)
field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split('.').last
- @klass.columns_hash[field_name]
+ @klass.type_for_attribute(field_name)
end
- def type_cast_calculated_value(value, column, operation = nil)
+ def type_cast_calculated_value(value, type, operation = nil)
case operation
when 'count' then value.to_i
- when 'sum' then type_cast_using_column(value || 0, column)
+ when 'sum' then type.type_cast_from_database(value || 0)
when 'average' then value.respond_to?(:to_d) ? value.to_d : value
- else type_cast_using_column(value, column)
+ else type.type_cast_from_database(value)
end
end
- def type_cast_using_column(value, column)
- column ? column.type_cast_from_database(value) : value
- end
-
# TODO: refactor to allow non-string `select_values` (eg. Arel nodes).
def select_for_count
if select_values.present?
diff --git a/activerecord/lib/active_record/store.rb b/activerecord/lib/active_record/store.rb
index 7014bc6d45..219e19dbb8 100644
--- a/activerecord/lib/active_record/store.rb
+++ b/activerecord/lib/active_record/store.rb
@@ -129,7 +129,7 @@ module ActiveRecord
private
def store_accessor_for(store_attribute)
- @column_types[store_attribute.to_s].accessor
+ type_for_attribute(store_attribute.to_s).accessor
end
class HashAccessor
diff --git a/activerecord/lib/active_record/type/numeric.rb b/activerecord/lib/active_record/type/numeric.rb
index 137c9e4c99..a7bf0657b9 100644
--- a/activerecord/lib/active_record/type/numeric.rb
+++ b/activerecord/lib/active_record/type/numeric.rb
@@ -16,26 +16,20 @@ module ActiveRecord
end
def changed?(old_value, _new_value, new_value_before_type_cast) # :nodoc:
- # 0 => 'wibble' should mark as changed so numericality validations run
- if nil_or_zero?(old_value) && non_numeric_string?(new_value_before_type_cast)
- # nil => '' should not mark as changed
- old_value != new_value_before_type_cast.presence
- else
- super
- end
+ super || zero_to_non_number?(old_value, new_value_before_type_cast)
end
private
+ def zero_to_non_number?(old_value, new_value_before_type_cast)
+ old_value == 0 && non_numeric_string?(new_value_before_type_cast)
+ end
+
def non_numeric_string?(value)
# 'wibble'.to_i will give zero, we want to make sure
# that we aren't marking int zero to string zero as
# changed.
- value !~ /\A\d+\.?\d*\z/
- end
-
- def nil_or_zero?(value)
- value.nil? || value == 0
+ value.to_s !~ /\A\d+\.?\d*\z/
end
end
end
diff --git a/activerecord/lib/active_record/type/value.rb b/activerecord/lib/active_record/type/value.rb
index 875fb98c4b..063139e620 100644
--- a/activerecord/lib/active_record/type/value.rb
+++ b/activerecord/lib/active_record/type/value.rb
@@ -63,7 +63,7 @@ module ActiveRecord
private
# Takes an input from the database, or from attribute setters,
# and casts it to a type appropriate for this object. This method
- # should not be overriden by subclasses. Instead, override `cast_value`.
+ # should not be overridden by subclasses. Instead, override `cast_value`.
def type_cast(value) # :api: public
cast_value(value) unless value.nil?
end
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index 2e7b1d7206..04e28a0cfe 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -48,7 +48,7 @@ module ActiveRecord
def build_relation(klass, table, attribute, value) #:nodoc:
if reflection = klass._reflect_on_association(attribute)
attribute = reflection.foreign_key
- value = value.attributes[reflection.primary_key_column.name] unless value.nil?
+ value = value.attributes[reflection.klass.primary_key] unless value.nil?
end
attribute_name = attribute.to_s
diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
index 080c499444..cc58a4a1a2 100644
--- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
@@ -860,7 +860,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_equal 'edges', Vertex.reflect_on_association(:sources).join_table
end
- def test_namespaced_habtm
+ def test_has_and_belongs_to_many_in_a_namespaced_model_pointing_to_a_namespaced_model
magazine = Publisher::Magazine.create
article = Publisher::Article.create
magazine.articles << article
@@ -869,6 +869,15 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_includes magazine.articles, article
end
+ def test_has_and_belongs_to_many_in_a_namespaced_model_pointing_to_a_non_namespaced_model
+ article = Publisher::Article.create
+ tag = Tag.create
+ article.tags << tag
+ article.save
+
+ assert_includes article.tags, tag
+ end
+
def test_redefine_habtm
child = SubDeveloper.new("name" => "Aredridel")
child.special_projects << SpecialProject.new("name" => "Special Project")
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 5f01352ab4..3c0b735607 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -772,6 +772,36 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal topic.replies.to_a.size, topic.replies_count
end
+ def test_counter_cache_updates_in_memory_after_concat
+ topic = Topic.create title: "Zoom-zoom-zoom"
+
+ topic.replies << Reply.create(title: "re: zoom", content: "speedy quick!")
+ assert_equal 1, topic.replies_count
+ assert_equal 1, topic.replies.size
+ assert_equal 1, topic.reload.replies.size
+ end
+
+ def test_counter_cache_updates_in_memory_after_create
+ topic = Topic.create title: "Zoom-zoom-zoom"
+
+ topic.replies.create!(title: "re: zoom", content: "speedy quick!")
+ assert_equal 1, topic.replies_count
+ assert_equal 1, topic.replies.size
+ assert_equal 1, topic.reload.replies.size
+ end
+
+ def test_counter_cache_updates_in_memory_after_create_with_array
+ topic = Topic.create title: "Zoom-zoom-zoom"
+
+ topic.replies.create!([
+ { title: "re: zoom", content: "speedy quick!" },
+ { title: "re: zoom 2", content: "OMG lol!" },
+ ])
+ assert_equal 2, topic.replies_count
+ assert_equal 2, topic.replies.size
+ assert_equal 2, topic.reload.replies.size
+ end
+
def test_pushing_association_updates_counter_cache
topic = Topic.order("id ASC").first
reply = Reply.create!
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 8641584c0c..0fa34e829e 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -1139,4 +1139,31 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert_equal 2, post.lazy_readers_unscope_skimmers.to_a.size
assert_equal 2, post.lazy_people_unscope_skimmers.to_a.size
end
+
+ def test_has_many_through_add_with_sti_middle_relation
+ club = SuperClub.create!(name: 'Fight Club')
+ member = Member.create!(name: 'Tyler Durden')
+
+ club.members << member
+ assert_equal 1, SuperMembership.where(member_id: member.id, club_id: club.id).count
+ end
+
+ class ClubWithCallbacks < ActiveRecord::Base
+ self.table_name = 'clubs'
+ after_create :add_a_member
+
+ has_many :memberships, inverse_of: :club, foreign_key: :club_id
+ has_many :members, through: :memberships
+
+ def add_a_member
+ members << Member.last
+ end
+ end
+
+ def test_has_many_with_callback_before_association
+ Member.create!
+ club = ClubWithCallbacks.create!
+
+ assert_equal 1, club.reload.memberships.count
+ end
end
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index f832ca3451..7566af920f 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -831,6 +831,37 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
end
+ def test_attribute_method?
+ assert @target.attribute_method?(:title)
+ assert @target.attribute_method?(:title=)
+ assert_not @target.attribute_method?(:wibble)
+ end
+
+ def test_attribute_method_returns_false_if_table_does_not_exist
+ @target.table_name = 'wibble'
+ assert_not @target.attribute_method?(:title)
+ end
+
+ def test_attribute_names_on_new_record
+ model = @target.new
+
+ assert_equal @target.column_names, model.attribute_names
+ end
+
+ def test_attribute_names_on_queried_record
+ model = @target.last!
+
+ assert_equal @target.column_names, model.attribute_names
+ end
+
+ def test_attribute_names_with_custom_select
+ model = @target.select('id').last!
+
+ assert_equal ['id'], model.attribute_names
+ # Sanity check, make sure other columns exist
+ assert_not_equal ['id'], @target.column_names
+ end
+
private
def new_topic_like_ar_class(&block)
diff --git a/activerecord/test/cases/attribute_set_test.rb b/activerecord/test/cases/attribute_set_test.rb
new file mode 100644
index 0000000000..402a611efa
--- /dev/null
+++ b/activerecord/test/cases/attribute_set_test.rb
@@ -0,0 +1,65 @@
+require 'cases/helper'
+
+module ActiveRecord
+ class AttributeSetTest < ActiveRecord::TestCase
+ test "building a new set from raw attributes" do
+ builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Float.new)
+ attributes = builder.build_from_database(foo: '1.1', bar: '2.2')
+
+ assert_equal 1, attributes[:foo].value
+ assert_equal 2.2, attributes[:bar].value
+ end
+
+ test "building with custom types" do
+ builder = AttributeSet::Builder.new(foo: Type::Float.new)
+ attributes = builder.build_from_database({ foo: '3.3', bar: '4.4' }, { bar: Type::Integer.new })
+
+ assert_equal 3.3, attributes[:foo].value
+ assert_equal 4, attributes[:bar].value
+ end
+
+ test "[] returns a null object" do
+ builder = AttributeSet::Builder.new(foo: Type::Float.new)
+ attributes = builder.build_from_database(foo: '3.3')
+
+ assert_equal '3.3', attributes[:foo].value_before_type_cast
+ assert_equal nil, attributes[:bar].value_before_type_cast
+ end
+
+ test "duping creates a new hash and dups each attribute" do
+ builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::String.new)
+ attributes = builder.build_from_database(foo: 1, bar: 'foo')
+
+ # Ensure the type cast value is cached
+ attributes[:foo].value
+ attributes[:bar].value
+
+ duped = attributes.dup
+ duped[:foo] = Attribute.from_database(2, Type::Integer.new)
+ duped[:bar].value << 'bar'
+
+ assert_equal 1, attributes[:foo].value
+ assert_equal 2, duped[:foo].value
+ assert_equal 'foo', attributes[:bar].value
+ assert_equal 'foobar', duped[:bar].value
+ end
+
+ test "freezing cloned set does not freeze original" do
+ attributes = AttributeSet.new({})
+ clone = attributes.clone
+
+ clone.freeze
+
+ assert clone.frozen?
+ assert_not attributes.frozen?
+ end
+
+ test "to_hash returns a hash of the type cast values" do
+ builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Float.new)
+ attributes = builder.build_from_database(foo: '1.1', bar: '2.2')
+
+ assert_equal({ foo: 1, bar: 2.2 }, attributes.to_hash)
+ assert_equal({ foo: 1, bar: 2.2 }, attributes.to_h)
+ end
+ end
+end
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index 222b1505a8..319ea9260a 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -53,11 +53,6 @@ class CalculationsTest < ActiveRecord::TestCase
assert_nil NumericData.average(:bank_balance)
end
- def test_type_cast_calculated_value_should_convert_db_averages_of_fixnum_class_to_decimal
- assert_equal 0, NumericData.all.send(:type_cast_calculated_value, 0, nil, 'avg')
- assert_equal 53.0, NumericData.all.send(:type_cast_calculated_value, 53, nil, 'avg')
- end
-
def test_should_get_maximum_of_field
assert_equal 60, Account.maximum(:credit_limit)
end
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index 28341d0b42..1192ecd6b4 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -858,4 +858,11 @@ class PersistenceTest < ActiveRecord::TestCase
post.body
end
end
+
+ def test_reload_removes_custom_selects
+ post = Post.select('posts.*, 1 as wibble').last!
+
+ assert_equal 1, post[:wibble]
+ assert_nil post.reload[:wibble]
+ end
end
diff --git a/activerecord/test/cases/quoting_test.rb b/activerecord/test/cases/quoting_test.rb
index bbd5298da1..70d9b9dbf5 100644
--- a/activerecord/test/cases/quoting_test.rb
+++ b/activerecord/test/cases/quoting_test.rb
@@ -93,12 +93,10 @@ module ActiveRecord
def test_quote_true
assert_equal @quoter.quoted_true, @quoter.quote(true, nil)
- assert_equal '1', @quoter.quote(true, Type::Integer.new)
end
def test_quote_false
assert_equal @quoter.quoted_false, @quoter.quote(false, nil)
- assert_equal '0', @quoter.quote(false, Type::Integer.new)
end
def test_quote_float
@@ -157,25 +155,6 @@ module ActiveRecord
assert_equal "'lo\\\\l'", @quoter.quote(string, nil)
end
- def test_quote_string_int_column
- assert_equal "1", @quoter.quote('1', Type::Integer.new)
- assert_equal "1", @quoter.quote('1.2', Type::Integer.new)
- end
-
- def test_quote_string_float_column
- assert_equal "1.0", @quoter.quote('1', Type::Float.new)
- assert_equal "1.2", @quoter.quote('1.2', Type::Float.new)
- end
-
- def test_quote_as_mb_chars_binary_column
- string = ActiveSupport::Multibyte::Chars.new('lo\l')
- assert_equal "'lo\\\\l'", @quoter.quote(string, Type::Binary.new)
- end
-
- def test_quote_binary_without_string_to_binary
- assert_equal "'lo\\\\l'", @quoter.quote('lo\l', Type::Binary.new)
- end
-
def test_string_with_crazy_column
assert_equal "'lo\\\\l'", @quoter.quote('lo\l')
end
@@ -183,10 +162,6 @@ module ActiveRecord
def test_quote_duration
assert_equal "1800", @quoter.quote(30.minutes)
end
-
- def test_quote_duration_int_column
- assert_equal "7200", @quoter.quote(2.hours, Type::Integer.new)
- end
end
end
end
diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb
index c6e73f78cc..acbd065649 100644
--- a/activerecord/test/cases/reflection_test.rb
+++ b/activerecord/test/cases/reflection_test.rb
@@ -80,24 +80,10 @@ class ReflectionTest < ActiveRecord::TestCase
assert_equal :integer, @first.column_for_attribute("id").type
end
- def test_non_existent_columns_return_null_object
- column = @first.column_for_attribute("attribute_that_doesnt_exist")
- assert_instance_of ActiveRecord::ConnectionAdapters::NullColumn, column
- assert_equal "attribute_that_doesnt_exist", column.name
- assert_equal nil, column.sql_type
- assert_equal nil, column.type
- assert_not column.number?
- assert_not column.text?
- assert_not column.binary?
- end
-
- def test_non_existent_columns_are_identity_types
- column = @first.column_for_attribute("attribute_that_doesnt_exist")
- object = Object.new
-
- assert_equal object, column.type_cast_from_database(object)
- assert_equal object, column.type_cast_from_user(object)
- assert_equal object, column.type_cast_for_database(object)
+ def test_non_existent_columns_return_nil
+ assert_deprecated do
+ assert_nil @first.column_for_attribute("attribute_that_doesnt_exist")
+ end
end
def test_reflection_klass_for_nested_class_name
diff --git a/activerecord/test/models/club.rb b/activerecord/test/models/club.rb
index a762ad4bb5..6ceafe5858 100644
--- a/activerecord/test/models/club.rb
+++ b/activerecord/test/models/club.rb
@@ -14,3 +14,10 @@ class Club < ActiveRecord::Base
"I'm sorry sir, this is a *private* club, not a *pirate* club"
end
end
+
+class SuperClub < ActiveRecord::Base
+ self.table_name = "clubs"
+
+ has_many :memberships, class_name: 'SuperMembership', foreign_key: 'club_id'
+ has_many :members, through: :memberships
+end
diff --git a/activerecord/test/models/publisher/article.rb b/activerecord/test/models/publisher/article.rb
index 03a277bbdd..d73a8eb936 100644
--- a/activerecord/test/models/publisher/article.rb
+++ b/activerecord/test/models/publisher/article.rb
@@ -1,3 +1,4 @@
class Publisher::Article < ActiveRecord::Base
has_and_belongs_to_many :magazines
+ has_and_belongs_to_many :tags
end
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index a7c7fc70bc..fd85050dd4 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -62,6 +62,11 @@ ActiveRecord::Schema.define do
t.references :magazine
end
+ create_table :articles_tags, force: true do |t|
+ t.references :article
+ t.references :tag
+ end
+
create_table :audit_logs, force: true do |t|
t.column :message, :string, null: false
t.column :developer_id, :integer, null: false
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index d513874aee..d0a5ddcc1e 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,3 +1,6 @@
+* Make Dependencies pass a name to NameError error.
+ *arthurnn*
+
* Fixed `ActiveSupport::Cache::FileStore` exploding with long paths.
*Adam Panzer / Michael Grosser*
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index 8a545e4386..a8d12366cc 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -187,7 +187,7 @@ module ActiveSupport #:nodoc:
# top-level constant.
def guess_for_anonymous(const_name)
if Object.const_defined?(const_name)
- raise NameError, "#{const_name} cannot be autoloaded from an anonymous class or module"
+ raise NameError.new "#{const_name} cannot be autoloaded from an anonymous class or module", const_name.to_s
else
Object
end
@@ -516,9 +516,9 @@ module ActiveSupport #:nodoc:
end
end
- raise NameError,
- "uninitialized constant #{qualified_name}",
- caller.reject { |l| l.starts_with? __FILE__ }
+ name_error = NameError.new("uninitialized constant #{qualified_name}", qualified_name)
+ name_error.set_backtrace(caller.reject {|l| l.starts_with? __FILE__ })
+ raise name_error
end
# Remove the constants that have been autoloaded, and those that have been
diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb
index d55cc5d3b0..8287e62f4c 100644
--- a/activesupport/test/caching_test.rb
+++ b/activesupport/test/caching_test.rb
@@ -60,36 +60,25 @@ class CacheKeyTest < ActiveSupport::TestCase
end
def test_expand_cache_key_with_rails_cache_id
- begin
- ENV['RAILS_CACHE_ID'] = 'c99'
+ with_env('RAILS_CACHE_ID' => 'c99') do
assert_equal 'c99/foo', ActiveSupport::Cache.expand_cache_key(:foo)
assert_equal 'c99/foo', ActiveSupport::Cache.expand_cache_key([:foo])
assert_equal 'c99/foo/bar', ActiveSupport::Cache.expand_cache_key([:foo, :bar])
assert_equal 'nm/c99/foo', ActiveSupport::Cache.expand_cache_key(:foo, :nm)
assert_equal 'nm/c99/foo', ActiveSupport::Cache.expand_cache_key([:foo], :nm)
assert_equal 'nm/c99/foo/bar', ActiveSupport::Cache.expand_cache_key([:foo, :bar], :nm)
- ensure
- ENV['RAILS_CACHE_ID'] = nil
end
end
def test_expand_cache_key_with_rails_app_version
- begin
- ENV['RAILS_APP_VERSION'] = 'rails3'
+ with_env('RAILS_APP_VERSION' => 'rails3') do
assert_equal 'rails3/foo', ActiveSupport::Cache.expand_cache_key(:foo)
- ensure
- ENV['RAILS_APP_VERSION'] = nil
end
end
def test_expand_cache_key_rails_cache_id_should_win_over_rails_app_version
- begin
- ENV['RAILS_CACHE_ID'] = 'c99'
- ENV['RAILS_APP_VERSION'] = 'rails3'
+ with_env('RAILS_CACHE_ID' => 'c99', 'RAILS_APP_VERSION' => 'rails3') do
assert_equal 'c99/foo', ActiveSupport::Cache.expand_cache_key(:foo)
- ensure
- ENV['RAILS_CACHE_ID'] = nil
- ENV['RAILS_APP_VERSION'] = nil
end
end
@@ -124,6 +113,16 @@ class CacheKeyTest < ActiveSupport::TestCase
def test_expand_cache_key_of_array_like_object
assert_equal 'foo/bar/baz', ActiveSupport::Cache.expand_cache_key(%w{foo bar baz}.to_enum)
end
+
+ private
+
+ def with_env(kv)
+ old_values = {}
+ kv.each { |key, value| old_values[key], ENV[key] = ENV[key], value }
+ yield
+ ensure
+ old_values.each { |key, value| ENV[key] = value}
+ end
end
class CacheStoreSettingTest < ActiveSupport::TestCase
diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb
index 4ca63b3417..ef0955e1a8 100644
--- a/activesupport/test/dependencies_test.rb
+++ b/activesupport/test/dependencies_test.rb
@@ -367,9 +367,11 @@ class DependenciesTest < ActiveSupport::TestCase
with_autoloading_fixtures do
e = assert_raise(NameError) { A::DoesNotExist.nil? }
assert_equal "uninitialized constant A::DoesNotExist", e.message
+ assert_equal "A::DoesNotExist", e.name
e = assert_raise(NameError) { A::B::DoesNotExist.nil? }
assert_equal "uninitialized constant A::B::DoesNotExist", e.message
+ assert_equal "A::B::DoesNotExist", e.name
end
end
@@ -537,6 +539,7 @@ class DependenciesTest < ActiveSupport::TestCase
mod = Module.new
e = assert_raise(NameError) { mod::E }
assert_equal 'E cannot be autoloaded from an anonymous class or module', e.message
+ assert_equal 'E', e.name
end
end
@@ -954,7 +957,7 @@ class DependenciesTest < ActiveSupport::TestCase
assert_kind_of Class, A::B # Necessary to load A::B for the test
ActiveSupport::Dependencies.mark_for_unload(A::B)
ActiveSupport::Dependencies.remove_unloadable_constants!
-
+
A::B # Make sure no circular dependency error
end
end
diff --git a/activesupport/test/json/decoding_test.rb b/activesupport/test/json/decoding_test.rb
index 07d7e530ca..80bf255080 100644
--- a/activesupport/test/json/decoding_test.rb
+++ b/activesupport/test/json/decoding_test.rb
@@ -73,22 +73,20 @@ class TestJSONDecoding < ActiveSupport::TestCase
TESTS.each_with_index do |(json, expected), index|
test "json decodes #{index}" do
- prev = ActiveSupport.parse_json_times
- ActiveSupport.parse_json_times = true
- silence_warnings do
- assert_equal expected, ActiveSupport::JSON.decode(json), "JSON decoding \
- failed for #{json}"
+ with_parse_json_times(true) do
+ silence_warnings do
+ assert_equal expected, ActiveSupport::JSON.decode(json), "JSON decoding \
+ failed for #{json}"
+ end
end
- ActiveSupport.parse_json_times = prev
end
end
test "json decodes time json with time parsing disabled" do
- prev = ActiveSupport.parse_json_times
- ActiveSupport.parse_json_times = false
- expected = {"a" => "2007-01-01 01:12:34 Z"}
- assert_equal expected, ActiveSupport::JSON.decode(%({"a": "2007-01-01 01:12:34 Z"}))
- ActiveSupport.parse_json_times = prev
+ with_parse_json_times(false) do
+ expected = {"a" => "2007-01-01 01:12:34 Z"}
+ assert_equal expected, ActiveSupport::JSON.decode(%({"a": "2007-01-01 01:12:34 Z"}))
+ end
end
def test_failed_json_decoding
@@ -101,5 +99,15 @@ class TestJSONDecoding < ActiveSupport::TestCase
def test_cannot_pass_unsupported_options
assert_raise(ArgumentError) { ActiveSupport::JSON.decode("", create_additions: true) }
end
+
+ private
+
+ def with_parse_json_times(value)
+ old_value = ActiveSupport.parse_json_times
+ ActiveSupport.parse_json_times = value
+ yield
+ ensure
+ ActiveSupport.parse_json_times = old_value
+ end
end
diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb
index b4cf37b177..ad358ad21d 100644
--- a/activesupport/test/json/encoding_test.rb
+++ b/activesupport/test/json/encoding_test.rb
@@ -494,31 +494,28 @@ EXPECTED
def test_twz_to_json_with_custom_time_precision
with_standard_json_time_format(true) do
- ActiveSupport::JSON::Encoding.time_precision = 0
- zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
- time = ActiveSupport::TimeWithZone.new(Time.utc(2000), zone)
- assert_equal "\"1999-12-31T19:00:00-05:00\"", ActiveSupport::JSON.encode(time)
+ with_time_precision(0) do
+ zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
+ time = ActiveSupport::TimeWithZone.new(Time.utc(2000), zone)
+ assert_equal "\"1999-12-31T19:00:00-05:00\"", ActiveSupport::JSON.encode(time)
+ end
end
- ensure
- ActiveSupport::JSON::Encoding.time_precision = 3
end
def test_time_to_json_with_custom_time_precision
with_standard_json_time_format(true) do
- ActiveSupport::JSON::Encoding.time_precision = 0
- assert_equal "\"2000-01-01T00:00:00Z\"", ActiveSupport::JSON.encode(Time.utc(2000))
+ with_time_precision(0) do
+ assert_equal "\"2000-01-01T00:00:00Z\"", ActiveSupport::JSON.encode(Time.utc(2000))
+ end
end
- ensure
- ActiveSupport::JSON::Encoding.time_precision = 3
end
def test_datetime_to_json_with_custom_time_precision
with_standard_json_time_format(true) do
- ActiveSupport::JSON::Encoding.time_precision = 0
- assert_equal "\"2000-01-01T00:00:00+00:00\"", ActiveSupport::JSON.encode(DateTime.new(2000))
+ with_time_precision(0) do
+ assert_equal "\"2000-01-01T00:00:00+00:00\"", ActiveSupport::JSON.encode(DateTime.new(2000))
+ end
end
- ensure
- ActiveSupport::JSON::Encoding.time_precision = 3
end
def test_twz_to_json_when_wrapping_a_date_time
@@ -539,4 +536,12 @@ EXPECTED
ensure
ActiveSupport.use_standard_json_time_format = old
end
+
+ def with_time_precision(value)
+ old_value = ActiveSupport::JSON::Encoding.time_precision
+ ActiveSupport::JSON::Encoding.time_precision = value
+ yield
+ ensure
+ ActiveSupport::JSON::Encoding.time_precision = old_value
+ end
end
diff --git a/guides/assets/javascripts/guides.js b/guides/assets/javascripts/guides.js
index e4d25dfb21..a9c7f0d016 100644
--- a/guides/assets/javascripts/guides.js
+++ b/guides/assets/javascripts/guides.js
@@ -51,3 +51,9 @@ var guidesIndex = {
window.location = url;
}
};
+
+// Disable autolink inside example code blocks of guides.
+$(document).ready(function() {
+ SyntaxHighlighter.defaults['auto-links'] = false;
+ SyntaxHighlighter.all();
+});
diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md
index 3d15319ca4..57097ab146 100644
--- a/guides/source/action_controller_overview.md
+++ b/guides/source/action_controller_overview.md
@@ -1166,6 +1166,61 @@ end
NOTE: Certain exceptions are only rescuable from the `ApplicationController` class, as they are raised before the controller gets initialized and the action gets executed. See Pratik Naik's [article](http://m.onkey.org/2008/7/20/rescue-from-dispatching) on the subject for more information.
+
+### Custom errors page
+
+You can customize the layout of your error handling using controllers and views.
+First define your app own routes to display the errors page.
+
+* `config/application.rb`
+
+ ```ruby
+ config.exceptions_app = self.routes
+ ```
+
+* `config/routes.rb`
+
+ ```ruby
+ get '/404', to: 'errors#not_found'
+ get '/422', to: 'errors#unprocessable_entity'
+ get '/500', to: 'errors#server_error'
+ ```
+
+Create the controller and views.
+
+* `app/controllers/errors_controller.rb`
+
+ ```ruby
+ class ErrorsController < ActionController::Base
+ layout 'error'
+
+ def not_found
+ render status: :not_found
+ end
+
+ def unprocessable_entity
+ render status: :unprocessable_entity
+ end
+
+ def server_error
+ render status: :server_error
+ end
+ end
+ ```
+
+* `app/views`
+
+ ```
+ errors/
+ not_found.html.erb
+ unprocessable_entity.html.erb
+ server_error.html.erb
+ layouts/
+ error.html.erb
+ ```
+
+Do not forget to set the correct status code on the controller as shown before. You should avoid using the database or any complex operations because the user is already on the error page. Generating another error while on an error page could cause issues.
+
Force HTTPS protocol
--------------------
diff --git a/guides/source/active_record_migrations.md b/guides/source/active_record_migrations.md
index 5a550d9e55..fd2125424b 100644
--- a/guides/source/active_record_migrations.md
+++ b/guides/source/active_record_migrations.md
@@ -434,6 +434,9 @@ change_column_default :products, :approved, false
This sets `:name` field on products to a `NOT NULL` column and the default
value of the `:approved` field to false.
+TIP: Unlike `change_column` (and `change_column_default`), `change_column_null`
+is reversible.
+
### Column Modifiers
Column modifiers can be applied when creating or changing a column:
diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md
index 2d1548f252..d984c5f27a 100644
--- a/guides/source/asset_pipeline.md
+++ b/guides/source/asset_pipeline.md
@@ -760,7 +760,7 @@ typical manifest file looks like:
"digest":"12b3c7dd74d2e9df37e7cbb1efa76a6d"},"application-1c5752789588ac18d7e1a50b1f0fd4c2.css":{"logical_path":"application.css","mtime":"2013-07-26T22:56:17-07:00","size":1591,
"digest":"1c5752789588ac18d7e1a50b1f0fd4c2"},"favicon-a9c641bf2b81f0476e876f7c5e375969.ico":{"logical_path":"favicon.ico","mtime":"2013-07-26T23:00:10-07:00","size":1406,
"digest":"a9c641bf2b81f0476e876f7c5e375969"},"my_image-231a680f23887d9dd70710ea5efd3c62.png":{"logical_path":"my_image.png","mtime":"2013-07-26T23:00:27-07:00","size":6646,
-"digest":"231a680f23887d9dd70710ea5efd3c62"}},"assets"{"application.js":
+"digest":"231a680f23887d9dd70710ea5efd3c62"}},"assets":{"application.js":
"application-723d1be6cc741a3aabb1cec24276d681.js","application.css":
"application-1c5752789588ac18d7e1a50b1f0fd4c2.css",
"favicon.ico":"favicona9c641bf2b81f0476e876f7c5e375969.ico","my_image.png":
diff --git a/guides/source/command_line.md b/guides/source/command_line.md
index 7e60f929a1..e283bcd0ef 100644
--- a/guides/source/command_line.md
+++ b/guides/source/command_line.md
@@ -294,6 +294,31 @@ Any modifications you make will be rolled back on exit
irb(main):001:0>
```
+#### The app and helper objects
+
+Inside the `rails console` you have access to the `app` and `helper` instances.
+
+With the `app` method you can access url and path helpers, as well as do requests.
+
+```bash
+>> app.root_path
+=> "/"
+
+>> app.get _
+Started GET "/" for 127.0.0.1 at 2014-06-19 10:41:57 -0300
+...
+```
+
+With the `helper` method it is possible to access Rails and your application's helpers.
+
+```bash
+>> helper.time_ago_in_words 30.days.ago
+=> "about 1 month"
+
+>> helper.my_custom_helper
+=> "my custom helper"
+```
+
### `rails dbconsole`
`rails dbconsole` figures out which database you're using and drops you into whichever command line interface you would use with it (and figures out the command line parameters to give to it, too!). It supports MySQL, PostgreSQL, SQLite and SQLite3.
diff --git a/guides/source/engines.md b/guides/source/engines.md
index e7f024f1fc..3145733d4c 100644
--- a/guides/source/engines.md
+++ b/guides/source/engines.md
@@ -82,8 +82,11 @@ The full list of options for the plugin generator may be seen by typing:
$ bin/rails plugin --help
```
-The `--full` option tells the generator that you want to create an engine,
-including a skeleton structure that provides the following:
+The `--mountable` option tells the generator that you want to create a
+"mountable" and namespace-isolated engine. This generator will provide the same
+skeleton structure as would the `--full` option. The `--full` option tells the
+generator that you want to create an engine, including a skeleton structure
+that provides the following:
* An `app` directory tree
* A `config/routes.rb` file:
@@ -94,7 +97,7 @@ including a skeleton structure that provides the following:
```
* A file at `lib/blorgh/engine.rb`, which is identical in function to a
- * standard Rails application's `config/application.rb` file:
+ standard Rails application's `config/application.rb` file:
```ruby
module Blorgh
@@ -103,9 +106,7 @@ including a skeleton structure that provides the following:
end
```
-The `--mountable` option tells the generator that you want to create a
-"mountable" and namespace-isolated engine. This generator will provide the same
-skeleton structure as would the `--full` option, and will add:
+The `--mountable` option will add to the `--full` option:
* Asset manifest files (`application.js` and `application.css`)
* A namespaced `ApplicationController` stub
diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md
index ea6c8cdd55..4a0600d02a 100644
--- a/guides/source/getting_started.md
+++ b/guides/source/getting_started.md
@@ -1873,7 +1873,7 @@ Then you make the `app/views/articles/show.html.erb` look like the following:
<%= render @article.comments %>
<h2>Add a comment:</h2>
-<%= render "comments/form" %>
+<%= render 'comments/form' %>
<%= link_to 'Edit Article', edit_article_path(@article) %> |
<%= link_to 'Back to Articles', articles_path %>
diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md
index 5b75540c05..f00f7bca1b 100644
--- a/guides/source/layouts_and_rendering.md
+++ b/guides/source/layouts_and_rendering.md
@@ -308,7 +308,7 @@ TIP: This option should be used only if you don't care about the content type of
the response. Using `:plain` or `:html` might be more appropriate in most of the
time.
-NOTE: Unless overriden, your response returned from this render option will be
+NOTE: Unless overridden, your response returned from this render option will be
`text/html`, as that is the default content type of Action Dispatch response.
#### Options for `render`
diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md
index 6260b39f5c..d1d24eac66 100644
--- a/guides/source/upgrading_ruby_on_rails.md
+++ b/guides/source/upgrading_ruby_on_rails.md
@@ -169,7 +169,7 @@ If you use the cookie session store, this would apply to the `session` and
Flash message keys are
[normalized to strings](https://github.com/rails/rails/commit/a668beffd64106a1e1fedb71cc25eaaa11baf0c1). They
-can still be accessed using either symbols or strings. Lopping through the flash
+can still be accessed using either symbols or strings. Looping through the flash
will always yield string keys:
```ruby
@@ -338,10 +338,10 @@ authors.compact!
### Changes on Default Scopes
-Default scopes are no longer overriden by chained conditions.
+Default scopes are no longer overridden by chained conditions.
In previous versions when you defined a `default_scope` in a model
-it was overriden by chained conditions in the same field. Now it
+it was overridden by chained conditions in the same field. Now it
is merged like any other scope.
Before: